mirror of https://github.com/docker/cli.git
Merge pull request #758 from vdemeester/experimental-cli
Add support for experimental Cli configuration
This commit is contained in:
commit
70db7cc0fc
|
@ -42,24 +42,25 @@ type Cli interface {
|
||||||
SetIn(in *InStream)
|
SetIn(in *InStream)
|
||||||
ConfigFile() *configfile.ConfigFile
|
ConfigFile() *configfile.ConfigFile
|
||||||
ServerInfo() ServerInfo
|
ServerInfo() ServerInfo
|
||||||
|
ClientInfo() ClientInfo
|
||||||
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerCli is an instance the docker command line client.
|
// DockerCli is an instance the docker command line client.
|
||||||
// Instances of the client can be returned from NewDockerCli.
|
// Instances of the client can be returned from NewDockerCli.
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
configFile *configfile.ConfigFile
|
configFile *configfile.ConfigFile
|
||||||
in *InStream
|
in *InStream
|
||||||
out *OutStream
|
out *OutStream
|
||||||
err io.Writer
|
err io.Writer
|
||||||
client client.APIClient
|
client client.APIClient
|
||||||
defaultVersion string
|
serverInfo ServerInfo
|
||||||
server ServerInfo
|
clientInfo ClientInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
||||||
func (cli *DockerCli) DefaultVersion() string {
|
func (cli *DockerCli) DefaultVersion() string {
|
||||||
return cli.defaultVersion
|
return cli.clientInfo.DefaultVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns the APIClient
|
// 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
|
// ServerInfo returns the server version details for the host this client is
|
||||||
// connected to
|
// connected to
|
||||||
func (cli *DockerCli) ServerInfo() ServerInfo {
|
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
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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()
|
cli.initializeFromClient()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) initializeFromClient() {
|
func isEnabled(value string) (bool, error) {
|
||||||
cli.defaultVersion = cli.client.ClientVersion()
|
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())
|
ping, err := cli.client.Ping(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Default to true if we fail to connect to daemon
|
// Default to true if we fail to connect to daemon
|
||||||
cli.server = ServerInfo{HasExperimental: true}
|
cli.serverInfo = ServerInfo{HasExperimental: true}
|
||||||
|
|
||||||
if ping.APIVersion != "" {
|
if ping.APIVersion != "" {
|
||||||
cli.client.NegotiateAPIVersionPing(ping)
|
cli.client.NegotiateAPIVersionPing(ping)
|
||||||
|
@ -143,7 +166,7 @@ func (cli *DockerCli) initializeFromClient() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.server = ServerInfo{
|
cli.serverInfo = ServerInfo{
|
||||||
HasExperimental: ping.Experimental,
|
HasExperimental: ping.Experimental,
|
||||||
OSType: ping.OSType,
|
OSType: ping.OSType,
|
||||||
}
|
}
|
||||||
|
@ -176,6 +199,12 @@ type ServerInfo struct {
|
||||||
OSType string
|
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.
|
// 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 {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||||
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"crypto/x509"
|
cliconfig "github.com/docker/cli/cli/config"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/flags"
|
"github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/cli/internal/test/testutil"
|
"github.com/docker/cli/internal/test/testutil"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/gotestyourself/gotestyourself/fs"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -124,13 +125,51 @@ func TestInitializeFromClient(t *testing.T) {
|
||||||
|
|
||||||
cli := &DockerCli{client: apiclient}
|
cli := &DockerCli{client: apiclient}
|
||||||
cli.initializeFromClient()
|
cli.initializeFromClient()
|
||||||
assert.Equal(t, defaultVersion, cli.defaultVersion)
|
assert.Equal(t, testcase.expectedServer, cli.serverInfo)
|
||||||
assert.Equal(t, testcase.expectedServer, cli.server)
|
|
||||||
assert.Equal(t, testcase.negotiated, apiclient.negotiated)
|
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) {
|
func TestGetClientWithPassword(t *testing.T) {
|
||||||
expected := "password"
|
expected := "password"
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
|
||||||
Git commit: {{.GitCommit}}
|
Git commit: {{.GitCommit}}
|
||||||
Built: {{.BuildTime}}
|
Built: {{.BuildTime}}
|
||||||
OS/Arch: {{.Os}}/{{.Arch}}
|
OS/Arch: {{.Os}}/{{.Arch}}
|
||||||
|
Experimental: {{.Experimental}}
|
||||||
{{- end}}
|
{{- end}}
|
||||||
|
|
||||||
{{- if .ServerOK}}{{with .Server}}
|
{{- if .ServerOK}}{{with .Server}}
|
||||||
|
@ -69,6 +70,7 @@ type clientVersion struct {
|
||||||
Os string
|
Os string
|
||||||
Arch string
|
Arch string
|
||||||
BuildTime string `json:",omitempty"`
|
BuildTime string `json:",omitempty"`
|
||||||
|
Experimental bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerOK returns true when the client could connect to the docker server
|
// 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,
|
BuildTime: cli.BuildTime,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
|
Experimental: dockerCli.ClientInfo().HasExperimental,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
vd.Client.Platform.Name = cli.PlatformName
|
vd.Client.Platform.Name = cli.PlatformName
|
||||||
|
|
|
@ -9,10 +9,11 @@ import (
|
||||||
// NewTrustCommand returns a cobra command for `trust` subcommands
|
// NewTrustCommand returns a cobra command for `trust` subcommands
|
||||||
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
|
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "trust",
|
Use: "trust",
|
||||||
Short: "Manage trust on Docker images (experimental)",
|
Short: "Manage trust on Docker images (experimental)",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCli.Err()),
|
||||||
|
Annotations: map[string]string{"experimentalCLI": ""},
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newViewCommand(dockerCli),
|
newViewCommand(dockerCli),
|
||||||
|
|
|
@ -44,6 +44,7 @@ type ConfigFile struct {
|
||||||
NodesFormat string `json:"nodesFormat,omitempty"`
|
NodesFormat string `json:"nodesFormat,omitempty"`
|
||||||
PruneFilters []string `json:"pruneFilters,omitempty"`
|
PruneFilters []string `json:"pruneFilters,omitempty"`
|
||||||
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
|
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
|
||||||
|
Experimental string `json:"experimental,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyConfig contains proxy configuration settings
|
// ProxyConfig contains proxy configuration settings
|
||||||
|
|
|
@ -193,6 +193,7 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
|
||||||
|
|
||||||
type versionDetails interface {
|
type versionDetails interface {
|
||||||
Client() client.APIClient
|
Client() client.APIClient
|
||||||
|
ClientInfo() command.ClientInfo
|
||||||
ServerInfo() command.ServerInfo
|
ServerInfo() command.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +201,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
clientVersion := details.Client().ClientVersion()
|
clientVersion := details.Client().ClientVersion()
|
||||||
osType := details.ServerInfo().OSType
|
osType := details.ServerInfo().OSType
|
||||||
hasExperimental := details.ServerInfo().HasExperimental
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
|
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||||
|
|
||||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||||
// hide experimental flags
|
// hide experimental flags
|
||||||
|
@ -208,6 +210,11 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
f.Hidden = true
|
f.Hidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !hasExperimentalCLI {
|
||||||
|
if _, ok := f.Annotations["experimentalCLI"]; ok {
|
||||||
|
f.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hide flags not supported by the server
|
// hide flags not supported by the server
|
||||||
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
||||||
|
@ -222,6 +229,11 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||||
subcmd.Hidden = true
|
subcmd.Hidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !hasExperimentalCLI {
|
||||||
|
if _, ok := subcmd.Annotations["experimentalCLI"]; ok {
|
||||||
|
subcmd.Hidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hide subcommands not supported by the server
|
// hide subcommands not supported by the server
|
||||||
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
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()
|
clientVersion := details.Client().ClientVersion()
|
||||||
osType := details.ServerInfo().OSType
|
osType := details.ServerInfo().OSType
|
||||||
hasExperimental := details.ServerInfo().HasExperimental
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
|
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||||
|
|
||||||
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||||
for curr := cmd; curr != nil; curr = curr.Parent() {
|
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 {
|
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
|
||||||
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
|
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{}
|
errs := []string{}
|
||||||
|
@ -260,6 +276,9 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
|
||||||
if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
|
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))
|
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 {
|
if len(errs) > 0 {
|
||||||
|
|
|
@ -32,7 +32,8 @@ func SetupConfigFile(t *testing.T) fs.Dir {
|
||||||
"https://notary-server:4443": {
|
"https://notary-server:4443": {
|
||||||
"auth": "ZWlhaXM6cGFzc3dvcmQK"
|
"auth": "ZWlhaXM6cGFzc3dvcmQK"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"experimental": "enabled"
|
||||||
}
|
}
|
||||||
`))
|
`))
|
||||||
return *dir
|
return *dir
|
||||||
|
|
Loading…
Reference in New Issue