diff --git a/components/cli/cli/command/checkpoint/cmd.go b/components/cli/cli/command/checkpoint/cmd.go index 097d4dbf90..2a698e74e5 100644 --- a/components/cli/cli/command/checkpoint/cmd.go +++ b/components/cli/cli/command/checkpoint/cmd.go @@ -9,11 +9,15 @@ import ( // NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ - Use: "checkpoint", - Short: "Manage checkpoints", - Args: cli.NoArgs, - RunE: command.ShowHelp(dockerCli.Err()), - Annotations: map[string]string{"experimental": "", "version": "1.25"}, + Use: "checkpoint", + Short: "Manage checkpoints", + Args: cli.NoArgs, + RunE: command.ShowHelp(dockerCli.Err()), + Annotations: map[string]string{ + "experimental": "", + "ostype": "linux", + "version": "1.25", + }, } cmd.AddCommand( newCreateCommand(dockerCli), diff --git a/components/cli/cli/command/container/start.go b/components/cli/cli/command/container/start.go index 243097c0cd..e388302841 100644 --- a/components/cli/cli/command/container/start.go +++ b/components/cli/cli/command/container/start.go @@ -47,8 +47,10 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") flags.SetAnnotation("checkpoint", "experimental", nil) + flags.SetAnnotation("checkpoint", "ostype", []string{"linux"}) flags.StringVar(&opts.checkpointDir, "checkpoint-dir", "", "Use a custom checkpoint storage directory") flags.SetAnnotation("checkpoint-dir", "experimental", nil) + flags.SetAnnotation("checkpoint-dir", "ostype", []string{"linux"}) return cmd } diff --git a/components/cli/cli/command/stack/kubernetes/services.go b/components/cli/cli/command/stack/kubernetes/services.go index 5a1db529dd..a2d13e5672 100644 --- a/components/cli/cli/command/stack/kubernetes/services.go +++ b/components/cli/cli/command/stack/kubernetes/services.go @@ -2,13 +2,13 @@ package kubernetes import ( "fmt" - "sort" "strings" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/kubernetes/labels" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" appsv1beta2 "k8s.io/api/apps/v1beta2" corev1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" @@ -49,15 +49,7 @@ func parseLabelFilters(rawFilters []string) map[string][]string { } func generateLabelSelector(f filters.Args, stackName string) string { - names := f.Get("name") - sort.Strings(names) - for _, n := range names { - if strings.HasPrefix(n, stackName+"_") { - // also accepts with unprefixed service name (for compat with existing swarm scripts where service names are prefixed by stack names) - names = append(names, strings.TrimPrefix(n, stackName+"_")) - } - } - selectors := append(generateSelector(parseLabelFilters(f.Get("label"))), labels.SelectorForStack(stackName, names...)) + selectors := append(generateSelector(parseLabelFilters(f.Get("label"))), labels.SelectorForStack(stackName)) return strings.Join(selectors, ",") } @@ -120,6 +112,7 @@ func RunServices(dockerCli *KubeCli, opts options.Services) error { if err != nil { return err } + services = filterServicesByName(services, filters.Get("name"), stackName) if opts.Quiet { info = map[string]formatter.ServiceListInfo{} @@ -140,3 +133,26 @@ func RunServices(dockerCli *KubeCli, opts options.Services) error { } return formatter.ServiceListWrite(servicesCtx, services, info) } + +func filterServicesByName(services []swarm.Service, names []string, stackName string) []swarm.Service { + if len(names) == 0 { + return services + } + prefix := stackName + "_" + // Accepts unprefixed service name (for compatibility with existing swarm scripts where service names are prefixed by stack names) + for i, n := range names { + if !strings.HasPrefix(n, prefix) { + names[i] = stackName + "_" + n + } + } + // Filter services + result := []swarm.Service{} + for _, s := range services { + for _, n := range names { + if strings.HasPrefix(s.Spec.Name, n) { + result = append(result, s) + } + } + } + return result +} diff --git a/components/cli/cli/command/stack/kubernetes/services_test.go b/components/cli/cli/command/stack/kubernetes/services_test.go index 41a3348e0b..89cb9b7e4e 100644 --- a/components/cli/cli/command/stack/kubernetes/services_test.go +++ b/components/cli/cli/command/stack/kubernetes/services_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" "github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert/cmp" ) @@ -23,27 +24,6 @@ func TestServiceFiltersLabelSelectorGen(t *testing.T) { "com.docker.stack.namespace=test", }, }, - { - name: "single-name filter", - stackName: "test", - filters: filters.NewArgs(filters.KeyValuePair{Key: "name", Value: "svc-test"}), - expectedSelectorParts: []string{ - "com.docker.stack.namespace=test", - "com.docker.service.name=svc-test", - }, - }, - { - name: "multi-name filter", - stackName: "test", - filters: filters.NewArgs( - filters.KeyValuePair{Key: "name", Value: "svc-test"}, - filters.KeyValuePair{Key: "name", Value: "svc-test2"}, - ), - expectedSelectorParts: []string{ - "com.docker.stack.namespace=test", - "com.docker.service.name in (svc-test,svc-test2)", - }, - }, { name: "label present filter", stackName: "test", @@ -92,17 +72,6 @@ func TestServiceFiltersLabelSelectorGen(t *testing.T) { "label2=test2", }, }, - { - name: "name filter with stackName prefix", - stackName: "test", - filters: filters.NewArgs( - filters.KeyValuePair{Key: "name", Value: "test_svc1"}, - ), - expectedSelectorParts: []string{ - "com.docker.stack.namespace=test", - "com.docker.service.name in (test_svc1,svc1)", - }, - }, } for _, c := range cases { @@ -114,3 +83,56 @@ func TestServiceFiltersLabelSelectorGen(t *testing.T) { }) } } +func TestServiceFiltersServiceByName(t *testing.T) { + cases := []struct { + name string + filters []string + services []swarm.Service + expectedServices []swarm.Service + }{ + { + name: "no filter", + filters: []string{}, + services: makeServices("s1", "s2"), + expectedServices: makeServices("s1", "s2"), + }, + { + name: "single-name filter", + filters: []string{"s1"}, + services: makeServices("s1", "s2"), + expectedServices: makeServices("s1"), + }, + { + name: "filter by prefix", + filters: []string{"prefix"}, + services: makeServices("prefix-s1", "prefix-s2", "s2"), + expectedServices: makeServices("prefix-s1", "prefix-s2"), + }, + { + name: "multi-name filter", + filters: []string{"s1", "s2"}, + services: makeServices("s1", "s2", "s3"), + expectedServices: makeServices("s1", "s2"), + }, + { + name: "stack name prefix is valid", + filters: []string{"stack_s1"}, + services: makeServices("s1", "s11", "s2"), + expectedServices: makeServices("s1", "s11"), + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := filterServicesByName(c.services, c.filters, "stack") + assert.DeepEqual(t, c.expectedServices, result) + }) + } +} + +func makeServices(names ...string) []swarm.Service { + result := make([]swarm.Service, len(names)) + for i, n := range names { + result[i] = swarm.Service{Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "stack_" + n}}} + } + return result +} diff --git a/components/cli/cli/compose/schema/bindata.go b/components/cli/cli/compose/schema/bindata.go index 9649d03070..e56e243201 100644 --- a/components/cli/cli/compose/schema/bindata.go +++ b/components/cli/cli/compose/schema/bindata.go @@ -467,44 +467,44 @@ DoTuq9lAU9Q4O1xV/59X/w8AAP//zRo7vm9CAAA= "/data/config_schema_v3.7.json": { local: "data/config_schema_v3.7.json", - size: 17540, + size: 17740, modtime: 1518458244, compressed: ` -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 `, }, diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.7.json b/components/cli/cli/compose/schema/data/config_schema_v3.7.json index 4c3d24dcd2..05566cf819 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.7.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.7.json @@ -319,6 +319,7 @@ }, "working_dir": {"type": "string"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, @@ -489,6 +490,7 @@ "attachable": {"type": "boolean"}, "labels": {"$ref": "#/definitions/list_or_dict"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, @@ -513,6 +515,7 @@ }, "labels": {"$ref": "#/definitions/list_or_dict"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, @@ -530,6 +533,7 @@ }, "labels": {"$ref": "#/definitions/list_or_dict"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, @@ -547,6 +551,7 @@ }, "labels": {"$ref": "#/definitions/list_or_dict"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, diff --git a/components/cli/cli/compose/schema/schema_test.go b/components/cli/cli/compose/schema/schema_test.go index c029f2f725..17ffb203f6 100644 --- a/components/cli/cli/compose/schema/schema_test.go +++ b/components/cli/cli/compose/schema/schema_test.go @@ -45,6 +45,39 @@ func TestValidateAllowsXTopLevelFields(t *testing.T) { assert.NilError(t, err) } +func TestValidateAllowsXFields(t *testing.T) { + config := dict{ + "version": "3.7", + "services": dict{ + "bar": dict{ + "x-extra-stuff": dict{}, + }, + }, + "volumes": dict{ + "bar": dict{ + "x-extra-stuff": dict{}, + }, + }, + "networks": dict{ + "bar": dict{ + "x-extra-stuff": dict{}, + }, + }, + "configs": dict{ + "bar": dict{ + "x-extra-stuff": dict{}, + }, + }, + "secrets": dict{ + "bar": dict{ + "x-extra-stuff": dict{}, + }, + }, + } + err := Validate(config, "3.7") + assert.NilError(t, err) +} + func TestValidateSecretConfigNames(t *testing.T) { config := dict{ "version": "3.5", diff --git a/components/cli/cmd/docker/docker.go b/components/cli/cmd/docker/docker.go index f20530dac5..4fe8bf738e 100644 --- a/components/cli/cmd/docker/docker.go +++ b/components/cli/cmd/docker/docker.go @@ -256,6 +256,9 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) { if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { subcmd.Hidden = true } + if v, ok := subcmd.Annotations["ostype"]; ok && v != osType { + subcmd.Hidden = true + } } } @@ -282,14 +285,14 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error { return } if !isOSTypeSupported(f, osType) { - errs = append(errs, fmt.Sprintf("\"--%s\" requires the Docker daemon to run on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType)) + errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType)) return } 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)) + errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name)) } _, isKubernetesAnnotated := f.Annotations["kubernetes"] _, isSwarmAnnotated := f.Annotations["swarm"] @@ -310,6 +313,7 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error { // Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack` func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error { clientVersion := details.Client().ClientVersion() + osType := details.ServerInfo().OSType hasExperimental := details.ServerInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental hasKubernetes := details.ClientInfo().HasKubernetes() @@ -319,11 +323,14 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error { if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) { return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion) } + if os, ok := curr.Annotations["ostype"]; ok && os != osType { + return fmt.Errorf("%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", cmd.CommandPath(), os, osType) + } 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()) + return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath()) } _, isKubernetesAnnotated := curr.Annotations["kubernetes"] _, isSwarmAnnotated := curr.Annotations["swarm"] diff --git a/components/cli/docs/yaml/yaml.go b/components/cli/docs/yaml/yaml.go index 5dc7164192..82c90973c0 100644 --- a/components/cli/docs/yaml/yaml.go +++ b/components/cli/docs/yaml/yaml.go @@ -25,6 +25,7 @@ type cmdOption struct { ExperimentalCLI bool Kubernetes bool Swarm bool + OSType string `yaml:"os_type,omitempty"` } type cmdDoc struct { @@ -48,6 +49,7 @@ type cmdDoc struct { ExperimentalCLI bool Kubernetes bool Swarm bool + OSType string `yaml:"os_type,omitempty"` } // GenYamlTree creates yaml structured ref files @@ -121,6 +123,9 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer) error { if _, ok := curr.Annotations["swarm"]; ok && !cliDoc.Swarm { cliDoc.Swarm = true } + if os, ok := curr.Annotations["ostype"]; ok && cliDoc.OSType == "" { + cliDoc.OSType = os + } } flags := cmd.NonInheritedFlags() @@ -207,6 +212,15 @@ func genFlagResult(flags *pflag.FlagSet) []cmdOption { opt.Swarm = true } + // Note that the annotation can have multiple ostypes set, however, multiple + // values are currently not used (and unlikely will). + // + // To simplify usage of the os_type property in the YAML, and for consistency + // with the same property for commands, we're only using the first ostype that's set. + if ostypes, ok := flag.Annotations["ostype"]; ok && len(opt.OSType) == 0 && len(ostypes) > 0 { + opt.OSType = ostypes[0] + } + result = append(result, opt) })