Merge pull request #758 from vdemeester/experimental-cli

Add support for experimental Cli configuration
This commit is contained in:
Sebastiaan van Stijn 2017-12-22 14:56:40 +01:00 committed by GitHub
commit 70db7cc0fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 22 deletions

View File

@ -42,24 +42,25 @@ type Cli interface {
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
ClientInfo() ClientInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
}
// DockerCli is an instance the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
client client.APIClient
defaultVersion string
server ServerInfo
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
client client.APIClient
serverInfo ServerInfo
clientInfo ClientInfo
}
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
func (cli *DockerCli) DefaultVersion() string {
return cli.defaultVersion
return cli.clientInfo.DefaultVersion
}
// Client returns the APIClient
@ -104,7 +105,12 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
// ServerInfo returns the server version details for the host this client is
// connected to
func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.server
return cli.serverInfo
}
// ClientInfo returns the client details for the cli
func (cli *DockerCli) ClientInfo() ClientInfo {
return cli.clientInfo
}
// Initialize the dockerCli runs initialization that must happen after command
@ -125,17 +131,34 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil {
return err
}
hasExperimental, err := isEnabled(cli.configFile.Experimental)
if err != nil {
return errors.Wrap(err, "Experimental field")
}
cli.clientInfo = ClientInfo{
DefaultVersion: cli.client.ClientVersion(),
HasExperimental: hasExperimental,
}
cli.initializeFromClient()
return nil
}
func (cli *DockerCli) initializeFromClient() {
cli.defaultVersion = cli.client.ClientVersion()
func isEnabled(value string) (bool, error) {
switch value {
case "enabled":
return true, nil
case "", "disabled":
return false, nil
default:
return false, errors.Errorf("%q is not valid, should be either enabled or disabled", value)
}
}
func (cli *DockerCli) initializeFromClient() {
ping, err := cli.client.Ping(context.Background())
if err != nil {
// Default to true if we fail to connect to daemon
cli.server = ServerInfo{HasExperimental: true}
cli.serverInfo = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
@ -143,7 +166,7 @@ func (cli *DockerCli) initializeFromClient() {
return
}
cli.server = ServerInfo{
cli.serverInfo = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
}
@ -176,6 +199,12 @@ type ServerInfo struct {
OSType string
}
// ClientInfo stores details about the supported features of the client
type ClientInfo struct {
HasExperimental bool
DefaultVersion string
}
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}

View File

@ -1,17 +1,18 @@
package command
import (
"crypto/x509"
"os"
"testing"
"crypto/x509"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/flags"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/fs"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -124,13 +125,51 @@ func TestInitializeFromClient(t *testing.T) {
cli := &DockerCli{client: apiclient}
cli.initializeFromClient()
assert.Equal(t, defaultVersion, cli.defaultVersion)
assert.Equal(t, testcase.expectedServer, cli.server)
assert.Equal(t, testcase.expectedServer, cli.serverInfo)
assert.Equal(t, testcase.negotiated, apiclient.negotiated)
})
}
}
func TestExperimentalCLI(t *testing.T) {
defaultVersion := "v1.55"
var testcases = []struct {
doc string
configfile string
expectedExperimentalCLI bool
}{
{
doc: "default",
configfile: `{}`,
expectedExperimentalCLI: false,
},
{
doc: "experimental",
configfile: `{
"experimental": "enabled"
}`,
expectedExperimentalCLI: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
defer dir.Remove()
apiclient := &fakeClient{
version: defaultVersion,
}
cli := &DockerCli{client: apiclient, err: os.Stderr}
cliconfig.SetDir(dir.Path())
err := cli.Initialize(flags.NewClientOptions())
assert.NoError(t, err)
assert.Equal(t, testcase.expectedExperimentalCLI, cli.ClientInfo().HasExperimental)
})
}
}
func TestGetClientWithPassword(t *testing.T) {
expected := "password"

View File

@ -23,6 +23,7 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
Git commit: {{.GitCommit}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}
Experimental: {{.Experimental}}
{{- end}}
{{- if .ServerOK}}{{with .Server}}
@ -69,6 +70,7 @@ type clientVersion struct {
Os string
Arch string
BuildTime string `json:",omitempty"`
Experimental bool
}
// ServerOK returns true when the client could connect to the docker server
@ -133,6 +135,7 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
BuildTime: cli.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: dockerCli.ClientInfo().HasExperimental,
},
}
vd.Client.Platform.Name = cli.PlatformName

View File

@ -9,10 +9,11 @@ import (
// NewTrustCommand returns a cobra command for `trust` subcommands
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"experimentalCLI": ""},
}
cmd.AddCommand(
newViewCommand(dockerCli),

View File

@ -44,6 +44,7 @@ type ConfigFile struct {
NodesFormat string `json:"nodesFormat,omitempty"`
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
}
// ProxyConfig contains proxy configuration settings

View File

@ -193,6 +193,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
type versionDetails interface {
Client() client.APIClient
ClientInfo() command.ClientInfo
ServerInfo() command.ServerInfo
}
@ -200,6 +201,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental
cmd.Flags().VisitAll(func(f *pflag.Flag) {
// hide experimental flags
@ -208,6 +210,11 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
f.Hidden = true
}
}
if !hasExperimentalCLI {
if _, ok := f.Annotations["experimentalCLI"]; ok {
f.Hidden = true
}
}
// hide flags not supported by the server
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
@ -222,6 +229,11 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
subcmd.Hidden = true
}
}
if !hasExperimentalCLI {
if _, ok := subcmd.Annotations["experimentalCLI"]; ok {
subcmd.Hidden = true
}
}
// hide subcommands not supported by the server
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
@ -234,6 +246,7 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd; curr != nil; curr = curr.Parent() {
@ -243,6 +256,9 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
}
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
return fmt.Errorf("%s is only supported when experimental cli features are enabled", cmd.CommandPath())
}
}
errs := []string{}
@ -260,6 +276,9 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
}
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported when experimental cli features are enabled", f.Name))
}
}
})
if len(errs) > 0 {

View File

@ -32,7 +32,8 @@ func SetupConfigFile(t *testing.T) fs.Dir {
"https://notary-server:4443": {
"auth": "ZWlhaXM6cGFzc3dvcmQK"
}
}
},
"experimental": "enabled"
}
`))
return *dir