github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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/opts" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/network" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 ) 17 18 type createOptions struct { 19 name string 20 scope string 21 driver string 22 driverOpts opts.MapOpts 23 labels opts.ListOpts 24 internal bool 25 ipv6 bool 26 attachable bool 27 ingress bool 28 configOnly bool 29 configFrom string 30 31 ipamDriver string 32 ipamSubnet []string 33 ipamIPRange []string 34 ipamGateway []string 35 ipamAux opts.MapOpts 36 ipamOpt opts.MapOpts 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 ipamAux: *opts.NewMapOpts(nil, nil), 44 ipamOpt: *opts.NewMapOpts(nil, nil), 45 } 46 47 cmd := &cobra.Command{ 48 Use: "create [OPTIONS] NETWORK", 49 Short: "Create a network", 50 Args: cli.ExactArgs(1), 51 RunE: func(cmd *cobra.Command, args []string) error { 52 options.name = args[0] 53 return runCreate(dockerCli, options) 54 }, 55 } 56 57 flags := cmd.Flags() 58 flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network") 59 flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options") 60 flags.Var(&options.labels, "label", "Set metadata on a network") 61 flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network") 62 flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking") 63 flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment") 64 flags.SetAnnotation("attachable", "version", []string{"1.25"}) 65 flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network") 66 flags.SetAnnotation("ingress", "version", []string{"1.29"}) 67 flags.StringVar(&options.scope, "scope", "", "Control the network's scope") 68 flags.SetAnnotation("scope", "version", []string{"1.30"}) 69 flags.BoolVar(&options.configOnly, "config-only", false, "Create a configuration only network") 70 flags.SetAnnotation("config-only", "version", []string{"1.30"}) 71 flags.StringVar(&options.configFrom, "config-from", "", "The network from which to copy the configuration") 72 flags.SetAnnotation("config-from", "version", []string{"1.30"}) 73 74 flags.StringVar(&options.ipamDriver, "ipam-driver", "default", "IP Address Management Driver") 75 flags.StringSliceVar(&options.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment") 76 flags.StringSliceVar(&options.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range") 77 flags.StringSliceVar(&options.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet") 78 79 flags.Var(&options.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver") 80 flags.Var(&options.ipamOpt, "ipam-opt", "Set IPAM driver specific options") 81 82 return cmd 83 } 84 85 func runCreate(dockerCli command.Cli, options createOptions) error { 86 client := dockerCli.Client() 87 88 ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll()) 89 if err != nil { 90 return err 91 } 92 93 // Construct network create request body 94 nc := types.NetworkCreate{ 95 Driver: options.driver, 96 Options: options.driverOpts.GetAll(), 97 IPAM: &network.IPAM{ 98 Driver: options.ipamDriver, 99 Config: ipamCfg, 100 Options: options.ipamOpt.GetAll(), 101 }, 102 CheckDuplicate: true, 103 Internal: options.internal, 104 EnableIPv6: options.ipv6, 105 Attachable: options.attachable, 106 Ingress: options.ingress, 107 Scope: options.scope, 108 ConfigOnly: options.configOnly, 109 Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), 110 } 111 112 if from := options.configFrom; from != "" { 113 nc.ConfigFrom = &network.ConfigReference{ 114 Network: from, 115 } 116 } 117 118 resp, err := client.NetworkCreate(context.Background(), options.name, nc) 119 if err != nil { 120 return err 121 } 122 fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID) 123 return nil 124 } 125 126 // Consolidates the ipam configuration as a group from different related configurations 127 // user can configure network with multiple non-overlapping subnets and hence it is 128 // possible to correlate the various related parameters and consolidate them. 129 // consolidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into 130 // structured ipam data. 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 ( 230 ip net.IP 231 ) 232 233 _, s, err := net.ParseCIDR(subnet) 234 if err != nil { 235 return false, errors.Wrap(err, "invalid subnet") 236 } 237 238 if strings.Contains(data, "/") { 239 ip, _, err = net.ParseCIDR(data) 240 if err != nil { 241 return false, err 242 } 243 } else { 244 ip = net.ParseIP(data) 245 } 246 247 return s.Contains(ip), nil 248 }