2016-09-08 13:11:39 -04:00
|
|
|
package volume
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
2022-05-31 07:52:00 -04:00
|
|
|
"sort"
|
2021-02-23 09:23:53 -05:00
|
|
|
"strings"
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2022-05-12 07:18:48 -04:00
|
|
|
"github.com/docker/cli/cli/command/completion"
|
2017-05-15 08:45:19 -04:00
|
|
|
"github.com/docker/cli/opts"
|
2022-04-29 13:26:50 -04:00
|
|
|
"github.com/docker/docker/api/types/volume"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
2021-02-23 09:23:53 -05:00
|
|
|
"github.com/spf13/pflag"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type createOptions struct {
|
|
|
|
name string
|
|
|
|
driver string
|
|
|
|
driverOpts opts.MapOpts
|
2016-10-12 19:06:34 -04:00
|
|
|
labels opts.ListOpts
|
2021-02-23 09:23:53 -05:00
|
|
|
|
|
|
|
// 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
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-02-27 12:39:35 -05:00
|
|
|
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
2017-05-15 08:45:19 -04:00
|
|
|
options := createOptions{
|
2021-02-23 09:23:53 -05:00
|
|
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
|
|
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
|
|
|
secrets: *opts.NewMapOpts(nil, nil),
|
|
|
|
requisiteTopology: opts.NewListOpts(nil),
|
|
|
|
preferredTopology: opts.NewListOpts(nil),
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2017-05-15 08:45:19 -04:00
|
|
|
if options.name != "" {
|
linting: fix incorrectly formatted errors (revive)
cli/compose/interpolation/interpolation.go:102:4: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.",
^
cli/command/stack/loader/loader.go:30:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return nil, errors.Errorf("Compose file contains unsupported options:\n\n%s\n",
^
cli/command/formatter/formatter.go:76:30: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return tmpl, errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/formatter/formatter.go:97:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Template parsing error: %v\n", err)
^
cli/command/image/build.go:257:25: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("error checking context: '%s'.", err)
^
cli/command/volume/create.go:35:27: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
^
cli/command/container/create.go:160:24: error-strings: error strings should not be capitalized or end with punctuation or a newline (revive)
return errors.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-27 15:13:03 -04:00
|
|
|
return errors.Errorf("conflicting options: either specify --name or provide positional arg, not both")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2017-05-15 08:45:19 -04:00
|
|
|
options.name = args[0]
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
2021-02-23 09:23:53 -05:00
|
|
|
options.cluster = hasClusterVolumeOptionSet(cmd.Flags())
|
2017-05-15 08:45:19 -04:00
|
|
|
return runCreate(dockerCli, options)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
2022-05-12 07:18:48 -04:00
|
|
|
ValidArgsFunction: completion.NoComplete,
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
flags := cmd.Flags()
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name")
|
|
|
|
flags.StringVar(&options.name, "name", "", "Specify volume name")
|
2016-09-08 13:11:39 -04:00
|
|
|
flags.Lookup("name").Hidden = true
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
|
|
|
flags.Var(&options.labels, "label", "Set metadata for a volume")
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2021-02-23 09:23:53 -05:00
|
|
|
// flags for cluster volumes only
|
|
|
|
flags.StringVar(&options.group, "group", "", "Cluster Volume group (cluster volumes)")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("group", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("group", "swarm", []string{"manager"})
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVar(&options.scope, "scope", "single", `Cluster Volume access scope ("single", "multi")`)
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("scope", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("scope", "swarm", []string{"manager"})
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVar(&options.sharing, "sharing", "none", `Cluster Volume access sharing ("none", "readonly", "onewriter", "all")`)
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("sharing", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("sharing", "swarm", []string{"manager"})
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVar(&options.availability, "availability", "active", `Cluster Volume availability ("active", "pause", "drain")`)
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("availability", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("availability", "swarm", []string{"manager"})
|
2023-01-03 05:12:24 -05:00
|
|
|
flags.StringVar(&options.accessType, "type", "block", `Cluster Volume access type ("mount", "block")`)
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("type", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("type", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
flags.Var(&options.secrets, "secret", "Cluster Volume secrets")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("secret", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("secret", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
flags.Var(&options.limitBytes, "limit-bytes", "Minimum size of the Cluster Volume in bytes")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("limit-bytes", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("limit-bytes", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
flags.Var(&options.requiredBytes, "required-bytes", "Maximum size of the Cluster Volume in bytes")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("required-bytes", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("required-bytes", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
flags.Var(&options.requisiteTopology, "topology-required", "A topology that the Cluster Volume must be accessible from")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("topology-required", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("topology-required", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
flags.Var(&options.preferredTopology, "topology-preferred", "A topology that the Cluster Volume would be preferred in")
|
2022-05-13 13:48:24 -04:00
|
|
|
flags.SetAnnotation("topology-preferred", "version", []string{"1.42"})
|
|
|
|
flags.SetAnnotation("topology-preferred", "swarm", []string{"manager"})
|
2021-02-23 09:23:53 -05:00
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2021-02-23 09:23:53 -05:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
func runCreate(dockerCli command.Cli, options createOptions) error {
|
2021-02-23 09:23:53 -05:00
|
|
|
volOpts := volume.CreateOptions{
|
2017-05-15 08:45:19 -04:00
|
|
|
Driver: options.driver,
|
|
|
|
DriverOpts: options.driverOpts.GetAll(),
|
|
|
|
Name: options.name,
|
2017-06-05 18:23:21 -04:00
|
|
|
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
2021-02-23 09:23:53 -05:00
|
|
|
}
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
|
|
|
|
if options.accessType == "mount" {
|
|
|
|
volOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
|
|
|
|
} else if options.accessType == "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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2022-05-31 07:52:00 -04:00
|
|
|
sort.SliceStable(volOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
|
|
|
|
return volOpts.ClusterVolumeSpec.Secrets[i].Key < volOpts.ClusterVolumeSpec.Secrets[j].Key
|
|
|
|
})
|
2021-02-23 09:23:53 -05:00
|
|
|
|
|
|
|
// TODO(dperny): ignore if no topology specified
|
|
|
|
topology := &volume.TopologyRequirement{}
|
|
|
|
for _, top := range options.requisiteTopology.GetAll() {
|
|
|
|
// 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
|
2022-12-27 11:44:01 -05:00
|
|
|
k, v, _ := strings.Cut(segment, "=")
|
|
|
|
segments[k] = v
|
2021-02-23 09:23:53 -05:00
|
|
|
}
|
|
|
|
topology.Requisite = append(
|
|
|
|
topology.Requisite,
|
|
|
|
volume.Topology{Segments: segments},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, top := range options.preferredTopology.GetAll() {
|
|
|
|
// 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
|
2022-12-27 11:44:01 -05:00
|
|
|
k, v, _ := strings.Cut(segment, "=")
|
|
|
|
segments[k] = v
|
2021-02-23 09:23:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
topology.Preferred = append(
|
|
|
|
topology.Preferred,
|
|
|
|
volume.Topology{Segments: segments},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
volOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
|
|
|
|
}
|
|
|
|
|
|
|
|
vol, err := dockerCli.Client().VolumeCreate(context.Background(), volOpts)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-29 13:26:50 -04:00
|
|
|
_, _ = fmt.Fprintln(dockerCli.Out(), vol.Name)
|
2016-09-08 13:11:39 -04:00
|
|
|
return nil
|
|
|
|
}
|