This replaces the visitAll recursive function with a test that verifies that the option is set for all commands and subcommands, so that it doesn't have to be modified at runtime. We currently still have to loop over all functions for the setValidateArgs call, but that can be looked at separately. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
206 lines
7.4 KiB
Go
206 lines
7.4 KiB
Go
package volume
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/moby/moby/api/types/volume"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
type createOptions struct {
|
|
name string
|
|
driver string
|
|
driverOpts opts.MapOpts
|
|
labels opts.ListOpts
|
|
|
|
// options for cluster volumes only
|
|
cluster bool
|
|
group string
|
|
scope string
|
|
sharing string
|
|
availability string
|
|
secrets opts.MapOpts
|
|
requiredBytes opts.MemBytes
|
|
limitBytes opts.MemBytes
|
|
accessType string
|
|
requisiteTopology opts.ListOpts
|
|
preferredTopology opts.ListOpts
|
|
}
|
|
|
|
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
|
options := createOptions{
|
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
|
secrets: *opts.NewMapOpts(nil, nil),
|
|
requisiteTopology: opts.NewListOpts(nil),
|
|
preferredTopology: opts.NewListOpts(nil),
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "create [OPTIONS] [VOLUME]",
|
|
Short: "Create a volume",
|
|
Args: cli.RequiresMaxArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) == 1 {
|
|
if options.name != "" {
|
|
return errors.New("conflicting options: cannot specify a volume-name through both --name and as a positional arg")
|
|
}
|
|
options.name = args[0]
|
|
}
|
|
options.cluster = hasClusterVolumeOptionSet(cmd.Flags())
|
|
return runCreate(cmd.Context(), dockerCLI, options)
|
|
},
|
|
ValidArgsFunction: cobra.NoFileCompletions,
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
flags := cmd.Flags()
|
|
flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name")
|
|
flags.StringVar(&options.name, "name", "", "Specify volume name")
|
|
flags.Lookup("name").Hidden = true
|
|
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
|
flags.Var(&options.labels, "label", "Set metadata for a volume")
|
|
|
|
// flags for cluster volumes only
|
|
flags.StringVar(&options.group, "group", "", "Cluster Volume group (cluster volumes)")
|
|
flags.SetAnnotation("group", "version", []string{"1.42"})
|
|
flags.SetAnnotation("group", "swarm", []string{"manager"})
|
|
flags.StringVar(&options.scope, "scope", "single", `Cluster Volume access scope ("single", "multi")`)
|
|
flags.SetAnnotation("scope", "version", []string{"1.42"})
|
|
flags.SetAnnotation("scope", "swarm", []string{"manager"})
|
|
flags.StringVar(&options.sharing, "sharing", "none", `Cluster Volume access sharing ("none", "readonly", "onewriter", "all")`)
|
|
flags.SetAnnotation("sharing", "version", []string{"1.42"})
|
|
flags.SetAnnotation("sharing", "swarm", []string{"manager"})
|
|
flags.StringVar(&options.availability, "availability", "active", `Cluster Volume availability ("active", "pause", "drain")`)
|
|
flags.SetAnnotation("availability", "version", []string{"1.42"})
|
|
flags.SetAnnotation("availability", "swarm", []string{"manager"})
|
|
flags.StringVar(&options.accessType, "type", "block", `Cluster Volume access type ("mount", "block")`)
|
|
flags.SetAnnotation("type", "version", []string{"1.42"})
|
|
flags.SetAnnotation("type", "swarm", []string{"manager"})
|
|
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.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.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")
|
|
flags.SetAnnotation("topology-required", "version", []string{"1.42"})
|
|
flags.SetAnnotation("topology-required", "swarm", []string{"manager"})
|
|
flags.Var(&options.preferredTopology, "topology-preferred", "A topology that the Cluster Volume would be preferred in")
|
|
flags.SetAnnotation("topology-preferred", "version", []string{"1.42"})
|
|
flags.SetAnnotation("topology-preferred", "swarm", []string{"manager"})
|
|
|
|
return cmd
|
|
}
|
|
|
|
// hasClusterVolumeOptionSet returns true if any of the cluster-specific
|
|
// options are set.
|
|
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")
|
|
}
|
|
|
|
func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error {
|
|
volOpts := volume.CreateOptions{
|
|
Driver: options.driver,
|
|
DriverOpts: options.driverOpts.GetAll(),
|
|
Name: options.name,
|
|
Labels: opts.ConvertKVStringsToMap(options.labels.GetSlice()),
|
|
}
|
|
if options.cluster {
|
|
volOpts.ClusterVolumeSpec = &volume.ClusterVolumeSpec{
|
|
Group: options.group,
|
|
AccessMode: &volume.AccessMode{
|
|
Scope: volume.Scope(options.scope),
|
|
Sharing: volume.SharingMode(options.sharing),
|
|
},
|
|
Availability: volume.Availability(options.availability),
|
|
}
|
|
|
|
switch options.accessType {
|
|
case "mount":
|
|
volOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
|
|
case "block":
|
|
volOpts.ClusterVolumeSpec.AccessMode.BlockVolume = &volume.TypeBlock{}
|
|
}
|
|
|
|
vcr := &volume.CapacityRange{}
|
|
if r := options.requiredBytes.Value(); r >= 0 {
|
|
vcr.RequiredBytes = r
|
|
}
|
|
|
|
if l := options.limitBytes.Value(); l >= 0 {
|
|
vcr.LimitBytes = l
|
|
}
|
|
volOpts.ClusterVolumeSpec.CapacityRange = vcr
|
|
|
|
for key, secret := range options.secrets.GetAll() {
|
|
volOpts.ClusterVolumeSpec.Secrets = append(
|
|
volOpts.ClusterVolumeSpec.Secrets,
|
|
volume.Secret{
|
|
Key: key,
|
|
Secret: secret,
|
|
},
|
|
)
|
|
}
|
|
sort.SliceStable(volOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
|
|
return volOpts.ClusterVolumeSpec.Secrets[i].Key < volOpts.ClusterVolumeSpec.Secrets[j].Key
|
|
})
|
|
|
|
// TODO(dperny): ignore if no topology specified
|
|
topology := &volume.TopologyRequirement{}
|
|
for _, top := range options.requisiteTopology.GetSlice() {
|
|
// each topology takes the form segment=value,segment=value
|
|
// comma-separated list of equal separated maps
|
|
segments := map[string]string{}
|
|
for _, segment := range strings.Split(top, ",") {
|
|
// TODO(dperny): validate topology syntax
|
|
k, v, _ := strings.Cut(segment, "=")
|
|
segments[k] = v
|
|
}
|
|
topology.Requisite = append(
|
|
topology.Requisite,
|
|
volume.Topology{Segments: segments},
|
|
)
|
|
}
|
|
|
|
for _, top := range options.preferredTopology.GetSlice() {
|
|
// each topology takes the form segment=value,segment=value
|
|
// comma-separated list of equal separated maps
|
|
segments := map[string]string{}
|
|
for _, segment := range strings.Split(top, ",") {
|
|
// TODO(dperny): validate topology syntax
|
|
k, v, _ := strings.Cut(segment, "=")
|
|
segments[k] = v
|
|
}
|
|
|
|
topology.Preferred = append(
|
|
topology.Preferred,
|
|
volume.Topology{Segments: segments},
|
|
)
|
|
}
|
|
|
|
volOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
|
|
}
|
|
|
|
vol, err := dockerCli.Client().VolumeCreate(ctx, volOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _ = fmt.Fprintln(dockerCli.Out(), vol.Name)
|
|
return nil
|
|
}
|