Skip to content

Commit e480e6c

Browse files
committed
volume create: simplify cluster volume options
Extract construction of the cluster volume spec from runCreate, and use struct literals for the individual cluster volume options. Also simplify topology parsing by reusing ConvertKVStringsToMap, use slices.SortFunc for deterministic secret ordering, and use max to clamp negative capacity values to zero. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 977d999 commit e480e6c

1 file changed

Lines changed: 58 additions & 82 deletions

File tree

‎cli/command/volume/create.go‎

Lines changed: 58 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
package volume
55

66
import (
7+
"cmp"
78
"context"
89
"errors"
910
"fmt"
10-
"sort"
11+
"slices"
1112
"strings"
1213

1314
"github.com/docker/cli/cli"
@@ -117,93 +118,68 @@ func hasClusterVolumeOptionSet(flags *pflag.FlagSet) bool {
117118
}
118119

119120
func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error {
120-
volOpts := client.VolumeCreateOptions{
121-
Driver: options.driver,
122-
DriverOpts: options.driverOpts.GetAll(),
123-
Name: options.name,
124-
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
121+
res, err := dockerCli.Client().VolumeCreate(ctx, client.VolumeCreateOptions{
122+
Driver: options.driver,
123+
DriverOpts: options.driverOpts.GetAll(),
124+
Name: options.name,
125+
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
126+
ClusterVolumeSpec: clusterVolumeSpec(options),
127+
})
128+
if err != nil {
129+
return err
125130
}
126-
if options.cluster {
127-
volOpts.ClusterVolumeSpec = &volume.ClusterVolumeSpec{
128-
Group: options.group,
129-
AccessMode: &volume.AccessMode{
130-
Scope: volume.Scope(options.scope),
131-
Sharing: volume.SharingMode(options.sharing),
132-
},
133-
Availability: volume.Availability(options.availability),
134-
}
135-
136-
switch options.accessType {
137-
case "mount":
138-
volOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
139-
case "block":
140-
volOpts.ClusterVolumeSpec.AccessMode.BlockVolume = &volume.TypeBlock{}
141-
}
142-
143-
vcr := &volume.CapacityRange{}
144-
if r := options.requiredBytes.Value(); r >= 0 {
145-
vcr.RequiredBytes = r
146-
}
147-
148-
if l := options.limitBytes.Value(); l >= 0 {
149-
vcr.LimitBytes = l
150-
}
151-
volOpts.ClusterVolumeSpec.CapacityRange = vcr
152-
153-
for key, secret := range options.secrets.GetAll() {
154-
volOpts.ClusterVolumeSpec.Secrets = append(
155-
volOpts.ClusterVolumeSpec.Secrets,
156-
volume.Secret{
157-
Key: key,
158-
Secret: secret,
159-
},
160-
)
161-
}
162-
sort.SliceStable(volOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
163-
return volOpts.ClusterVolumeSpec.Secrets[i].Key < volOpts.ClusterVolumeSpec.Secrets[j].Key
164-
})
165131

166-
// TODO(dperny): ignore if no topology specified
167-
topology := &volume.TopologyRequirement{}
168-
for _, top := range options.requisiteTopology.GetSlice() {
169-
// each topology takes the form segment=value,segment=value
170-
// comma-separated list of equal separated maps
171-
segments := map[string]string{}
172-
for segment := range strings.SplitSeq(top, ",") {
173-
// TODO(dperny): validate topology syntax
174-
k, v, _ := strings.Cut(segment, "=")
175-
segments[k] = v
176-
}
177-
topology.Requisite = append(
178-
topology.Requisite,
179-
volume.Topology{Segments: segments},
180-
)
181-
}
182-
183-
for _, top := range options.preferredTopology.GetSlice() {
184-
// each topology takes the form segment=value,segment=value
185-
// comma-separated list of equal separated maps
186-
segments := map[string]string{}
187-
for segment := range strings.SplitSeq(top, ",") {
188-
// TODO(dperny): validate topology syntax
189-
k, v, _ := strings.Cut(segment, "=")
190-
segments[k] = v
191-
}
132+
_, _ = fmt.Fprintln(dockerCli.Out(), res.Volume.Name)
133+
return nil
134+
}
192135

193-
topology.Preferred = append(
194-
topology.Preferred,
195-
volume.Topology{Segments: segments},
196-
)
197-
}
136+
func clusterVolumeSpec(options createOptions) *volume.ClusterVolumeSpec {
137+
if !options.cluster {
138+
return nil
139+
}
198140

199-
volOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
141+
var secrets []volume.Secret
142+
for key, secret := range options.secrets.GetAll() {
143+
secrets = append(secrets, volume.Secret{Key: key, Secret: secret})
200144
}
145+
slices.SortFunc(secrets, func(a, b volume.Secret) int {
146+
return cmp.Compare(a.Key, b.Key)
147+
})
201148

202-
res, err := dockerCli.Client().VolumeCreate(ctx, volOpts)
203-
if err != nil {
204-
return err
149+
accessMode := &volume.AccessMode{
150+
Scope: volume.Scope(options.scope),
151+
Sharing: volume.SharingMode(options.sharing),
152+
}
153+
switch options.accessType {
154+
case "mount":
155+
accessMode.MountVolume = &volume.TypeMount{}
156+
case "block":
157+
accessMode.BlockVolume = &volume.TypeBlock{}
205158
}
206159

207-
_, _ = fmt.Fprintln(dockerCli.Out(), res.Volume.Name)
208-
return nil
160+
return &volume.ClusterVolumeSpec{
161+
Group: options.group,
162+
AccessMode: accessMode,
163+
AccessibilityRequirements: &volume.TopologyRequirement{
164+
Requisite: parseTopologies(options.requisiteTopology.GetSlice()),
165+
Preferred: parseTopologies(options.preferredTopology.GetSlice()),
166+
},
167+
CapacityRange: &volume.CapacityRange{
168+
RequiredBytes: max(options.requiredBytes.Value(), 0),
169+
LimitBytes: max(options.limitBytes.Value(), 0),
170+
},
171+
Secrets: secrets,
172+
Availability: volume.Availability(options.availability),
173+
}
174+
}
175+
176+
func parseTopologies(values []string) []volume.Topology {
177+
topologies := make([]volume.Topology, 0, len(values))
178+
for _, top := range values {
179+
// TODO(dperny): validate topology syntax
180+
topologies = append(topologies, volume.Topology{
181+
Segments: opts.ConvertKVStringsToMap(strings.Split(top, ",")),
182+
})
183+
}
184+
return topologies
209185
}

0 commit comments

Comments
 (0)