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  }