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  }