diff --git a/cli/command/cli.go b/cli/command/cli.go index 6057bc1dd1..2e4c0b55a6 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -25,7 +25,8 @@ import ( dopts "github.com/docker/cli/opts" "github.com/docker/docker/api" "github.com/docker/docker/api/types" - registrytypes "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "github.com/docker/go-connections/tlsconfig" "github.com/moby/term" @@ -132,6 +133,7 @@ func (cli *DockerCli) loadConfigFile() { // ServerInfo returns the server version details for the host this client is // connected to func (cli *DockerCli) ServerInfo() ServerInfo { + // TODO(thaJeztah) make ServerInfo() lazily load the info (ping only when needed) return cli.serverInfo } @@ -170,7 +172,7 @@ func (cli *DockerCli) ManifestStore() manifeststore.Store { // RegistryClient returns a client for communicating with a Docker distribution // registry func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient { - resolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { + resolver := func(ctx context.Context, index *registry.IndexInfo) types.AuthConfig { return ResolveAuthConfig(ctx, cli, index) } return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure) @@ -336,6 +338,7 @@ func (cli *DockerCli) initializeFromClient() { HasExperimental: ping.Experimental, OSType: ping.OSType, BuildkitVersion: ping.BuilderVersion, + SwarmStatus: ping.SwarmStatus, } cli.client.NegotiateAPIVersionPing(ping) } @@ -376,6 +379,15 @@ type ServerInfo struct { HasExperimental bool OSType string BuildkitVersion types.BuilderVersion + + // SwarmStatus provides information about the current swarm status of the + // engine, obtained from the "Swarm" header in the API response. + // + // It can be a nil struct if the API version does not provide this header + // in the ping response, or if an error occurred, in which case the client + // should use other ways to get the current swarm status, such as the /swarm + // endpoint. + SwarmStatus *swarm.Status } // NewDockerCli returns a DockerCli instance with all operators applied on it. diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index cc642e5420..ba157ebe4b 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -159,7 +159,7 @@ func TestInitializeFromClient(t *testing.T) { cli := &DockerCli{client: apiclient} cli.initializeFromClient() - assert.DeepEqual(t, cli.serverInfo, testcase.expectedServer) + assert.DeepEqual(t, cli.ServerInfo(), testcase.expectedServer) assert.Equal(t, apiclient.negotiated, testcase.negotiated) }) } diff --git a/cli/command/config/cmd.go b/cli/command/config/cmd.go index b241229798..cc19bb2325 100644 --- a/cli/command/config/cmd.go +++ b/cli/command/config/cmd.go @@ -16,7 +16,7 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.30", - "swarm": "", + "swarm": "manager", }, } cmd.AddCommand( diff --git a/cli/command/node/cmd.go b/cli/command/node/cmd.go index f96c9a6bf3..0e519ae440 100644 --- a/cli/command/node/cmd.go +++ b/cli/command/node/cmd.go @@ -20,7 +20,7 @@ func NewNodeCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.24", - "swarm": "", + "swarm": "manager", }, } cmd.AddCommand( diff --git a/cli/command/secret/cmd.go b/cli/command/secret/cmd.go index 937fcdb150..b7e38e3073 100644 --- a/cli/command/secret/cmd.go +++ b/cli/command/secret/cmd.go @@ -16,7 +16,7 @@ func NewSecretCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.25", - "swarm": "", + "swarm": "manager", }, } cmd.AddCommand( diff --git a/cli/command/service/cmd.go b/cli/command/service/cmd.go index 23132d9928..69472c52b2 100644 --- a/cli/command/service/cmd.go +++ b/cli/command/service/cmd.go @@ -16,7 +16,7 @@ func NewServiceCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.24", - "swarm": "", + "swarm": "manager", }, } cmd.AddCommand( diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 3c28523172..3503454312 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -17,7 +17,7 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.25", - "swarm": "", + "swarm": "manager", }, } defaultHelpFunc := cmd.HelpFunc() diff --git a/cli/command/swarm/ca.go b/cli/command/swarm/ca.go index 1c01f68460..62fba6a4cd 100644 --- a/cli/command/swarm/ca.go +++ b/cli/command/swarm/ca.go @@ -35,7 +35,10 @@ func newCACommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runCA(dockerCli, cmd.Flags(), opts) }, - Annotations: map[string]string{"version": "1.30"}, + Annotations: map[string]string{ + "version": "1.30", + "swarm": "manager", + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/cmd.go b/cli/command/swarm/cmd.go index 89bf5c3c73..e78e33d003 100644 --- a/cli/command/swarm/cmd.go +++ b/cli/command/swarm/cmd.go @@ -16,7 +16,7 @@ func NewSwarmCommand(dockerCli command.Cli) *cobra.Command { RunE: command.ShowHelp(dockerCli.Err()), Annotations: map[string]string{ "version": "1.24", - "swarm": "", + "swarm": "", // swarm command itself does not require swarm to be enabled (so swarm init and join is always available on API 1.24 and up) }, } cmd.AddCommand( diff --git a/cli/command/swarm/init.go b/cli/command/swarm/init.go index 683a90519a..333bf34039 100644 --- a/cli/command/swarm/init.go +++ b/cli/command/swarm/init.go @@ -39,6 +39,10 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runInit(dockerCli, cmd.Flags(), opts) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "", // swarm init does not require swarm to be active, and is always available on API 1.24 and up + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/join.go b/cli/command/swarm/join.go index e6c43f03de..59effb21d1 100644 --- a/cli/command/swarm/join.go +++ b/cli/command/swarm/join.go @@ -36,6 +36,10 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command { opts.remote = args[0] return runJoin(dockerCli, cmd.Flags(), opts) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "", // swarm join does not require swarm to be active, and is always available on API 1.24 and up + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/join_token.go b/cli/command/swarm/join_token.go index f8ed93cf00..afd64d55ab 100644 --- a/cli/command/swarm/join_token.go +++ b/cli/command/swarm/join_token.go @@ -28,6 +28,10 @@ func newJoinTokenCommand(dockerCli command.Cli) *cobra.Command { opts.role = args[0] return runJoinToken(dockerCli, opts) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "manager", + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/leave.go b/cli/command/swarm/leave.go index af6e0753b5..782f46c325 100644 --- a/cli/command/swarm/leave.go +++ b/cli/command/swarm/leave.go @@ -23,6 +23,10 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runLeave(dockerCli, opts) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "active", + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/unlock.go b/cli/command/swarm/unlock.go index 5cf266ab41..369852dfb2 100644 --- a/cli/command/swarm/unlock.go +++ b/cli/command/swarm/unlock.go @@ -24,6 +24,10 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runUnlock(dockerCli) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "manager", + }, } return cmd diff --git a/cli/command/swarm/unlock_key.go b/cli/command/swarm/unlock_key.go index be5d9ea284..672d333fa8 100644 --- a/cli/command/swarm/unlock_key.go +++ b/cli/command/swarm/unlock_key.go @@ -27,6 +27,10 @@ func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runUnlockKey(dockerCli, opts) }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "manager", + }, } flags := cmd.Flags() diff --git a/cli/command/swarm/update.go b/cli/command/swarm/update.go index 6b9ad728f6..c2bc6b3352 100644 --- a/cli/command/swarm/update.go +++ b/cli/command/swarm/update.go @@ -28,6 +28,10 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { } return nil }, + Annotations: map[string]string{ + "version": "1.24", + "swarm": "manager", + }, } cmd.Flags().BoolVar(&opts.autolock, flagAutolock, false, "Change manager autolocking setting (true|false)") diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index e28b95a3f4..a1ae91c19f 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -309,8 +309,33 @@ func hideSubcommandIf(subcmd *cobra.Command, condition func(string) bool, annota func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error { var ( - notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental } - notOSType = func(v string) bool { return v != details.ServerInfo().OSType } + notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental } + notOSType = func(v string) bool { return v != details.ServerInfo().OSType } + notSwarmStatus = func(v string) bool { + s := details.ServerInfo().SwarmStatus + if s == nil { + // engine did not return swarm status header + return false + } + switch v { + case "manager": + // requires the node to be a manager + return !s.ControlAvailable + case "active": + // requires swarm to be active on the node (e.g. for swarm leave) + // only hide the command if we're sure the node is "inactive" + // for any other status, assume the "leave" command can still + // be used. + return s.NodeState == "inactive" + case "": + // some swarm commands, such as "swarm init" and "swarm join" + // are swarm-related, but do not require swarm to be active + return false + default: + // ignore any other value for the "swarm" annotation + return false + } + } versionOlderThan = func(v string) bool { return versions.LessThan(details.Client().ClientVersion(), v) } ) @@ -328,12 +353,14 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error { hideFlagIf(f, notExperimental, "experimental") hideFlagIf(f, notOSType, "ostype") + hideFlagIf(f, notSwarmStatus, "swarm") hideFlagIf(f, versionOlderThan, "version") }) for _, subcmd := range cmd.Commands() { hideSubcommandIf(subcmd, notExperimental, "experimental") hideSubcommandIf(subcmd, notOSType, "ostype") + hideSubcommandIf(subcmd, notSwarmStatus, "swarm") hideSubcommandIf(subcmd, versionOlderThan, "version") } return nil