github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/network/create.go (about) 1 package network 2 3 import ( 4 "context" 5 "fmt" 6 "net" 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" 14 "github.com/docker/docker/api/types/network" 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 ) 18 19 type createOptions struct { 20 name string 21 scope string 22 driver string 23 driverOpts opts.MapOpts 24 labels opts.ListOpts 25 internal bool 26 ipv6 bool 27 attachable bool 28 ingress bool 29 configOnly bool 30 configFrom string 31 32 ipamDriver string 33 ipamSubnet []string 34 ipamIPRange []string 35 ipamGateway []string 36 ipamAux opts.MapOpts 37 ipamOpt opts.MapOpts 38 } 39 40 func newCreateCommand(dockerCli command.Cli) *cobra.Command { 41 options := createOptions{ 42 driverOpts: *opts.NewMapOpts(nil, nil), 43 labels: opts.NewListOpts(opts.ValidateLabel), 44 ipamAux: *opts.NewMapOpts(nil, nil), 45 ipamOpt: *opts.NewMapOpts(nil, nil), 46 } 47 48 cmd := &cobra.Command{ 49 Use: "create [OPTIONS] NETWORK", 50 Short: "Create a network", 51 Args: cli.ExactArgs(1), 52 RunE: func(cmd *cobra.Command, args []string) error { 53 options.name = args[0] 54 return runCreate(cmd.Context(), dockerCli, options) 55 }, 56 ValidArgsFunction: completion.NoComplete, 57 } 58 59 flags := cmd.Flags() 60 flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network") 61 flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options") 62 flags.Var(&options.labels, "label", "Set metadata on a network") 63 flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network") 64 flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking") 65 flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment") 66 flags.SetAnnotation("attachable", "version", []string{"1.25"}) 67 flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network") 68 flags.SetAnnotation("ingress", "version", []string{"1.29"}) 69 flags.StringVar(&options.scope, "scope", "", "Control the network's scope") 70 flags.SetAnnotation("scope", "version", []string{"1.30"}) 71 flags.BoolVar(&options.configOnly, "config-only", false, "Create a configuration only network") 72 flags.SetAnnotation("config-only", "version", []string{"1.30"}) 73 flags.StringVar(&options.configFrom, "config-from", "", "The network from which to copy the configuration") 74 flags.SetAnnotation("config-from", "version", []string{"1.30"}) 75 76 flags.StringVar(&options.ipamDriver, "ipam-driver", "default", "IP Address Management Driver") 77 flags.StringSliceVar(&options.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment") 78 flags.StringSliceVar(&options.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range") 79 flags.StringSliceVar(&options.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet") 80 81 flags.Var(&options.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver") 82 flags.Var(&options.ipamOpt, "ipam-opt", "Set IPAM driver specific options") 83 84 return cmd 85 } 86 87 func runCreate(ctx context.Context, dockerCli command.Cli, options createOptions) error { 88 client := dockerCli.Client() 89 90 ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll()) 91 if err != nil { 92 return err 93 } 94 95 var configFrom *network.ConfigReference 96 if options.configFrom != "" { 97 configFrom = &network.ConfigReference{ 98 Network: options.configFrom, 99 } 100 } 101 resp, err := client.NetworkCreate(ctx, options.name, types.NetworkCreate{ 102 Driver: options.driver, 103 Options: options.driverOpts.GetAll(), 104 IPAM: &network.IPAM{ 105 Driver: options.ipamDriver, 106 Config: ipamCfg, 107 Options: options.ipamOpt.GetAll(), 108 }, 109 Internal: options.internal, 110 EnableIPv6: options.ipv6, 111 Attachable: options.attachable, 112 Ingress: options.ingress, 113 Scope: options.scope, 114 ConfigOnly: options.configOnly, 115 ConfigFrom: configFrom, 116 Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), 117 }) 118 if err != nil { 119 return err 120 } 121 fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID) 122 return nil 123 } 124 125 // Consolidates the ipam configuration as a group from different related configurations 126 // user can configure network with multiple non-overlapping subnets and hence it is 127 // possible to correlate the various related parameters and consolidate them. 128 // consolidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into 129 // structured ipam data. 130 // 131 //nolint:gocyclo 132 func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) { 133 if len(subnets) < len(ranges) || len(subnets) < len(gateways) { 134 return nil, errors.Errorf("every ip-range or gateway must have a corresponding subnet") 135 } 136 iData := map[string]*network.IPAMConfig{} 137 138 // Populate non-overlapping subnets into consolidation map 139 for _, s := range subnets { 140 for k := range iData { 141 ok1, err := subnetMatches(s, k) 142 if err != nil { 143 return nil, err 144 } 145 ok2, err := subnetMatches(k, s) 146 if err != nil { 147 return nil, err 148 } 149 if ok1 || ok2 { 150 return nil, errors.Errorf("multiple overlapping subnet configuration is not supported") 151 } 152 } 153 iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}} 154 } 155 156 // Validate and add valid ip ranges 157 for _, r := range ranges { 158 match := false 159 for _, s := range subnets { 160 ok, err := subnetMatches(s, r) 161 if err != nil { 162 return nil, err 163 } 164 if !ok { 165 continue 166 } 167 if iData[s].IPRange != "" { 168 return nil, errors.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s) 169 } 170 d := iData[s] 171 d.IPRange = r 172 match = true 173 } 174 if !match { 175 return nil, errors.Errorf("no matching subnet for range %s", r) 176 } 177 } 178 179 // Validate and add valid gateways 180 for _, g := range gateways { 181 match := false 182 for _, s := range subnets { 183 ok, err := subnetMatches(s, g) 184 if err != nil { 185 return nil, err 186 } 187 if !ok { 188 continue 189 } 190 if iData[s].Gateway != "" { 191 return nil, errors.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s) 192 } 193 d := iData[s] 194 d.Gateway = g 195 match = true 196 } 197 if !match { 198 return nil, errors.Errorf("no matching subnet for gateway %s", g) 199 } 200 } 201 202 // Validate and add aux-addresses 203 for key, aa := range auxaddrs { 204 match := false 205 for _, s := range subnets { 206 ok, err := subnetMatches(s, aa) 207 if err != nil { 208 return nil, err 209 } 210 if !ok { 211 continue 212 } 213 iData[s].AuxAddress[key] = aa 214 match = true 215 } 216 if !match { 217 return nil, errors.Errorf("no matching subnet for aux-address %s", aa) 218 } 219 } 220 221 idl := []network.IPAMConfig{} 222 for _, v := range iData { 223 idl = append(idl, *v) 224 } 225 return idl, nil 226 } 227 228 func subnetMatches(subnet, data string) (bool, error) { 229 var ip net.IP 230 231 _, s, err := net.ParseCIDR(subnet) 232 if err != nil { 233 return false, errors.Wrap(err, "invalid subnet") 234 } 235 236 if strings.Contains(data, "/") { 237 ip, _, err = net.ParseCIDR(data) 238 if err != nil { 239 return false, err 240 } 241 } else { 242 ip = net.ParseIP(data) 243 } 244 245 return s.Contains(ip), nil 246 }