diff --git a/cli/command/volume/create.go b/cli/command/volume/create.go index 80b09ed2097b..7f305ae2c667 100644 --- a/cli/command/volume/create.go +++ b/cli/command/volume/create.go @@ -92,10 +92,10 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { flags.Var(&options.secrets, "secret", "Cluster Volume secrets") flags.SetAnnotation("secret", "version", []string{"1.42"}) flags.SetAnnotation("secret", "swarm", []string{"manager"}) - flags.Var(&options.limitBytes, "limit-bytes", "Minimum size of the Cluster Volume in bytes") + flags.Var(&options.limitBytes, "limit-bytes", "Maximum size of the Cluster Volume in bytes") flags.SetAnnotation("limit-bytes", "version", []string{"1.42"}) flags.SetAnnotation("limit-bytes", "swarm", []string{"manager"}) - flags.Var(&options.requiredBytes, "required-bytes", "Maximum size of the Cluster Volume in bytes") + flags.Var(&options.requiredBytes, "required-bytes", "Minimum size of the Cluster Volume in bytes") flags.SetAnnotation("required-bytes", "version", []string{"1.42"}) flags.SetAnnotation("required-bytes", "swarm", []string{"manager"}) flags.Var(&options.requisiteTopology, "topology-required", "A topology that the Cluster Volume must be accessible from") @@ -113,8 +113,9 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { func hasClusterVolumeOptionSet(flags *pflag.FlagSet) bool { return flags.Changed("group") || flags.Changed("scope") || flags.Changed("sharing") || flags.Changed("availability") || - flags.Changed("type") || flags.Changed("secrets") || - flags.Changed("limit-bytes") || flags.Changed("required-bytes") + flags.Changed("type") || flags.Changed("secret") || + flags.Changed("limit-bytes") || flags.Changed("required-bytes") || + flags.Changed("topology-required") || flags.Changed("topology-preferred") } func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error { diff --git a/cli/command/volume/create_test.go b/cli/command/volume/create_test.go index 3f9e43cdc016..19b00a89857a 100644 --- a/cli/command/volume/create_test.go +++ b/cli/command/volume/create_test.go @@ -262,3 +262,147 @@ func TestVolumeCreateClusterOpts(t *testing.T) { assert.NilError(t, cmd.Execute()) } + +// TestVolumeCreateClusterOptionDetection verifies that each cluster-specific +// flag on its own is enough to send a ClusterVolumeSpec, and that its value +// is propagated. +func TestVolumeCreateClusterOptionDetection(t *testing.T) { + testCases := []struct { + flag string + value string + expected *volume.ClusterVolumeSpec + }{ + { + flag: "secret", + value: "key1=secret1", + expected: &volume.ClusterVolumeSpec{ + Secrets: []volume.Secret{{Key: "key1", Secret: "secret1"}}, + }, + }, + { + flag: "topology-required", + value: "region=R1,zone=Z1", + expected: &volume.ClusterVolumeSpec{ + AccessibilityRequirements: &volume.TopologyRequirement{ + Requisite: []volume.Topology{ + {Segments: map[string]string{"region": "R1", "zone": "Z1"}}, + }, + Preferred: []volume.Topology{}, + }, + }, + }, + { + flag: "topology-preferred", + value: "region=R1,zone=Z2", + expected: &volume.ClusterVolumeSpec{ + AccessibilityRequirements: &volume.TopologyRequirement{ + Requisite: []volume.Topology{}, + Preferred: []volume.Topology{ + {Segments: map[string]string{"region": "R1", "zone": "Z2"}}, + }, + }, + }, + }, + { + flag: "limit-bytes", + value: "567890", + expected: &volume.ClusterVolumeSpec{ + CapacityRange: &volume.CapacityRange{LimitBytes: 567890}, + }, + }, + { + flag: "required-bytes", + value: "1234", + expected: &volume.ClusterVolumeSpec{ + CapacityRange: &volume.CapacityRange{RequiredBytes: 1234}, + }, + }, + { + flag: "scope", + value: "multi", + expected: &volume.ClusterVolumeSpec{ + AccessMode: &volume.AccessMode{ + Scope: volume.ScopeMultiNode, + Sharing: volume.SharingNone, + BlockVolume: &volume.TypeBlock{}, + }, + }, + }, + { + flag: "sharing", + value: "all", + expected: &volume.ClusterVolumeSpec{ + AccessMode: &volume.AccessMode{ + Scope: volume.ScopeSingleNode, + Sharing: volume.SharingAll, + BlockVolume: &volume.TypeBlock{}, + }, + }, + }, + { + flag: "type", + value: "mount", + expected: &volume.ClusterVolumeSpec{ + AccessMode: &volume.AccessMode{ + Scope: volume.ScopeSingleNode, + Sharing: volume.SharingNone, + MountVolume: &volume.TypeMount{}, + }, + }, + }, + { + flag: "group", + value: "gronp", + expected: &volume.ClusterVolumeSpec{ + Group: "gronp", + }, + }, + { + flag: "availability", + value: "drain", + expected: &volume.ClusterVolumeSpec{ + Availability: volume.AvailabilityDrain, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.flag, func(t *testing.T) { + var actual *volume.ClusterVolumeSpec + cli := test.NewFakeCli(&fakeClient{ + volumeCreateFunc: func(options client.VolumeCreateOptions) (client.VolumeCreateResult, error) { + actual = options.ClusterVolumeSpec + return client.VolumeCreateResult{}, nil + }, + }) + + cmd := newCreateCommand(cli) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{"my-csi-volume"}) + assert.Check(t, cmd.Flags().Set("driver", "csi")) + assert.Check(t, cmd.Flags().Set(tc.flag, tc.value)) + assert.NilError(t, cmd.Execute()) + + assert.Assert(t, actual != nil, "expected a ClusterVolumeSpec when only --%s is set", tc.flag) + if tc.expected.Secrets != nil { + assert.Check(t, is.DeepEqual(actual.Secrets, tc.expected.Secrets)) + } + if tc.expected.AccessMode != nil { + assert.Check(t, is.DeepEqual(actual.AccessMode, tc.expected.AccessMode)) + } + if tc.expected.AccessibilityRequirements != nil { + assert.Check(t, is.DeepEqual(actual.AccessibilityRequirements, tc.expected.AccessibilityRequirements)) + } + if tc.expected.CapacityRange != nil { + assert.Check(t, is.DeepEqual(actual.CapacityRange, tc.expected.CapacityRange)) + } + if tc.expected.Group != "" { + assert.Check(t, is.Equal(actual.Group, tc.expected.Group)) + } + if tc.expected.Availability != "" { + assert.Check(t, is.Equal(actual.Availability, tc.expected.Availability)) + } + }) + } +} diff --git a/docs/reference/commandline/volume_create.md b/docs/reference/commandline/volume_create.md index fea1cbec845b..13570f12fe24 100644 --- a/docs/reference/commandline/volume_create.md +++ b/docs/reference/commandline/volume_create.md @@ -11,9 +11,9 @@ Create a volume | `-d`, `--driver` | `string` | `local` | Specify volume driver name | | `--group` | `string` | | Cluster Volume group (cluster volumes) | | `--label` | `list` | | Set metadata for a volume | -| `--limit-bytes` | `bytes` | `0` | Minimum size of the Cluster Volume in bytes | +| `--limit-bytes` | `bytes` | `0` | Maximum size of the Cluster Volume in bytes | | [`-o`](#opt), [`--opt`](#opt) | `map` | `map[]` | Set driver specific options | -| `--required-bytes` | `bytes` | `0` | Maximum size of the Cluster Volume in bytes | +| `--required-bytes` | `bytes` | `0` | Minimum size of the Cluster Volume in bytes | | `--scope` | `string` | `single` | Cluster Volume access scope (`single`, `multi`) | | `--secret` | `map` | `map[]` | Cluster Volume secrets | | `--sharing` | `string` | `none` | Cluster Volume access sharing (`none`, `readonly`, `onewriter`, `all`) |