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 }