github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/volume/create.go (about)

     1  package volume
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/docker/cli/cli"
    10  	"github.com/docker/cli/cli/command"
    11  	"github.com/docker/cli/cli/command/completion"
    12  	"github.com/docker/cli/opts"
    13  	"github.com/docker/docker/api/types/volume"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  	"github.com/spf13/pflag"
    17  )
    18  
    19  type createOptions struct {
    20  	name       string
    21  	driver     string
    22  	driverOpts opts.MapOpts
    23  	labels     opts.ListOpts
    24  
    25  	// options for cluster volumes only
    26  	cluster           bool
    27  	group             string
    28  	scope             string
    29  	sharing           string
    30  	availability      string
    31  	secrets           opts.MapOpts
    32  	requiredBytes     opts.MemBytes
    33  	limitBytes        opts.MemBytes
    34  	accessType        string
    35  	requisiteTopology opts.ListOpts
    36  	preferredTopology opts.ListOpts
    37  }
    38  
    39  func newCreateCommand(dockerCli command.Cli) *cobra.Command {
    40  	options := createOptions{
    41  		driverOpts:        *opts.NewMapOpts(nil, nil),
    42  		labels:            opts.NewListOpts(opts.ValidateLabel),
    43  		secrets:           *opts.NewMapOpts(nil, nil),
    44  		requisiteTopology: opts.NewListOpts(nil),
    45  		preferredTopology: opts.NewListOpts(nil),
    46  	}
    47  
    48  	cmd := &cobra.Command{
    49  		Use:   "create [OPTIONS] [VOLUME]",
    50  		Short: "Create a volume",
    51  		Args:  cli.RequiresMaxArgs(1),
    52  		RunE: func(cmd *cobra.Command, args []string) error {
    53  			if len(args) == 1 {
    54  				if options.name != "" {
    55  					return errors.Errorf("conflicting options: either specify --name or provide positional arg, not both")
    56  				}
    57  				options.name = args[0]
    58  			}
    59  			options.cluster = hasClusterVolumeOptionSet(cmd.Flags())
    60  			return runCreate(cmd.Context(), dockerCli, options)
    61  		},
    62  		ValidArgsFunction: completion.NoComplete,
    63  	}
    64  	flags := cmd.Flags()
    65  	flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name")
    66  	flags.StringVar(&options.name, "name", "", "Specify volume name")
    67  	flags.Lookup("name").Hidden = true
    68  	flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
    69  	flags.Var(&options.labels, "label", "Set metadata for a volume")
    70  
    71  	// flags for cluster volumes only
    72  	flags.StringVar(&options.group, "group", "", "Cluster Volume group (cluster volumes)")
    73  	flags.SetAnnotation("group", "version", []string{"1.42"})
    74  	flags.SetAnnotation("group", "swarm", []string{"manager"})
    75  	flags.StringVar(&options.scope, "scope", "single", `Cluster Volume access scope ("single", "multi")`)
    76  	flags.SetAnnotation("scope", "version", []string{"1.42"})
    77  	flags.SetAnnotation("scope", "swarm", []string{"manager"})
    78  	flags.StringVar(&options.sharing, "sharing", "none", `Cluster Volume access sharing ("none", "readonly", "onewriter", "all")`)
    79  	flags.SetAnnotation("sharing", "version", []string{"1.42"})
    80  	flags.SetAnnotation("sharing", "swarm", []string{"manager"})
    81  	flags.StringVar(&options.availability, "availability", "active", `Cluster Volume availability ("active", "pause", "drain")`)
    82  	flags.SetAnnotation("availability", "version", []string{"1.42"})
    83  	flags.SetAnnotation("availability", "swarm", []string{"manager"})
    84  	flags.StringVar(&options.accessType, "type", "block", `Cluster Volume access type ("mount", "block")`)
    85  	flags.SetAnnotation("type", "version", []string{"1.42"})
    86  	flags.SetAnnotation("type", "swarm", []string{"manager"})
    87  	flags.Var(&options.secrets, "secret", "Cluster Volume secrets")
    88  	flags.SetAnnotation("secret", "version", []string{"1.42"})
    89  	flags.SetAnnotation("secret", "swarm", []string{"manager"})
    90  	flags.Var(&options.limitBytes, "limit-bytes", "Minimum size of the Cluster Volume in bytes")
    91  	flags.SetAnnotation("limit-bytes", "version", []string{"1.42"})
    92  	flags.SetAnnotation("limit-bytes", "swarm", []string{"manager"})
    93  	flags.Var(&options.requiredBytes, "required-bytes", "Maximum size of the Cluster Volume in bytes")
    94  	flags.SetAnnotation("required-bytes", "version", []string{"1.42"})
    95  	flags.SetAnnotation("required-bytes", "swarm", []string{"manager"})
    96  	flags.Var(&options.requisiteTopology, "topology-required", "A topology that the Cluster Volume must be accessible from")
    97  	flags.SetAnnotation("topology-required", "version", []string{"1.42"})
    98  	flags.SetAnnotation("topology-required", "swarm", []string{"manager"})
    99  	flags.Var(&options.preferredTopology, "topology-preferred", "A topology that the Cluster Volume would be preferred in")
   100  	flags.SetAnnotation("topology-preferred", "version", []string{"1.42"})
   101  	flags.SetAnnotation("topology-preferred", "swarm", []string{"manager"})
   102  
   103  	return cmd
   104  }
   105  
   106  // hasClusterVolumeOptionSet returns true if any of the cluster-specific
   107  // options are set.
   108  func hasClusterVolumeOptionSet(flags *pflag.FlagSet) bool {
   109  	return flags.Changed("group") || flags.Changed("scope") ||
   110  		flags.Changed("sharing") || flags.Changed("availability") ||
   111  		flags.Changed("type") || flags.Changed("secrets") ||
   112  		flags.Changed("limit-bytes") || flags.Changed("required-bytes")
   113  }
   114  
   115  func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error {
   116  	volOpts := volume.CreateOptions{
   117  		Driver:     options.driver,
   118  		DriverOpts: options.driverOpts.GetAll(),
   119  		Name:       options.name,
   120  		Labels:     opts.ConvertKVStringsToMap(options.labels.GetAll()),
   121  	}
   122  	if options.cluster {
   123  		volOpts.ClusterVolumeSpec = &volume.ClusterVolumeSpec{
   124  			Group: options.group,
   125  			AccessMode: &volume.AccessMode{
   126  				Scope:   volume.Scope(options.scope),
   127  				Sharing: volume.SharingMode(options.sharing),
   128  			},
   129  			Availability: volume.Availability(options.availability),
   130  		}
   131  
   132  		if options.accessType == "mount" {
   133  			volOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
   134  		} else if options.accessType == "block" {
   135  			volOpts.ClusterVolumeSpec.AccessMode.BlockVolume = &volume.TypeBlock{}
   136  		}
   137  
   138  		vcr := &volume.CapacityRange{}
   139  		if r := options.requiredBytes.Value(); r >= 0 {
   140  			vcr.RequiredBytes = r
   141  		}
   142  
   143  		if l := options.limitBytes.Value(); l >= 0 {
   144  			vcr.LimitBytes = l
   145  		}
   146  		volOpts.ClusterVolumeSpec.CapacityRange = vcr
   147  
   148  		for key, secret := range options.secrets.GetAll() {
   149  			volOpts.ClusterVolumeSpec.Secrets = append(
   150  				volOpts.ClusterVolumeSpec.Secrets,
   151  				volume.Secret{
   152  					Key:    key,
   153  					Secret: secret,
   154  				},
   155  			)
   156  		}
   157  		sort.SliceStable(volOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
   158  			return volOpts.ClusterVolumeSpec.Secrets[i].Key < volOpts.ClusterVolumeSpec.Secrets[j].Key
   159  		})
   160  
   161  		// TODO(dperny): ignore if no topology specified
   162  		topology := &volume.TopologyRequirement{}
   163  		for _, top := range options.requisiteTopology.GetAll() {
   164  			// each topology takes the form segment=value,segment=value
   165  			// comma-separated list of equal separated maps
   166  			segments := map[string]string{}
   167  			for _, segment := range strings.Split(top, ",") {
   168  				// TODO(dperny): validate topology syntax
   169  				k, v, _ := strings.Cut(segment, "=")
   170  				segments[k] = v
   171  			}
   172  			topology.Requisite = append(
   173  				topology.Requisite,
   174  				volume.Topology{Segments: segments},
   175  			)
   176  		}
   177  
   178  		for _, top := range options.preferredTopology.GetAll() {
   179  			// each topology takes the form segment=value,segment=value
   180  			// comma-separated list of equal separated maps
   181  			segments := map[string]string{}
   182  			for _, segment := range strings.Split(top, ",") {
   183  				// TODO(dperny): validate topology syntax
   184  				k, v, _ := strings.Cut(segment, "=")
   185  				segments[k] = v
   186  			}
   187  
   188  			topology.Preferred = append(
   189  				topology.Preferred,
   190  				volume.Topology{Segments: segments},
   191  			)
   192  		}
   193  
   194  		volOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
   195  	}
   196  
   197  	vol, err := dockerCli.Client().VolumeCreate(ctx, volOpts)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	_, _ = fmt.Fprintln(dockerCli.Out(), vol.Name)
   203  	return nil
   204  }