2016-09-08 13:11:39 -04:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2016-09-08 13:11:39 -04:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2022-05-12 07:18:48 -04:00
|
|
|
"github.com/docker/cli/cli/command/completion"
|
2017-05-15 08:45:19 -04:00
|
|
|
"github.com/docker/cli/opts"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/network"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-09-08 13:11:39 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
type createOptions struct {
|
|
|
|
name string
|
2017-05-08 18:01:39 -04:00
|
|
|
scope string
|
2016-09-08 13:11:39 -04:00
|
|
|
driver string
|
|
|
|
driverOpts opts.MapOpts
|
2016-10-12 19:06:34 -04:00
|
|
|
labels opts.ListOpts
|
2016-09-08 13:11:39 -04:00
|
|
|
internal bool
|
|
|
|
ipv6 bool
|
|
|
|
attachable bool
|
2017-03-09 14:52:25 -05:00
|
|
|
ingress bool
|
2017-05-08 18:01:39 -04:00
|
|
|
configOnly bool
|
|
|
|
configFrom string
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
ipamDriver string
|
|
|
|
ipamSubnet []string
|
|
|
|
ipamIPRange []string
|
|
|
|
ipamGateway []string
|
|
|
|
ipamAux opts.MapOpts
|
|
|
|
ipamOpt opts.MapOpts
|
|
|
|
}
|
|
|
|
|
2017-06-21 05:25:25 -04:00
|
|
|
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
2017-05-15 08:45:19 -04:00
|
|
|
options := createOptions{
|
2016-09-08 13:11:39 -04:00
|
|
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
Fix labels copying value from environment variables
This patch fixes a bug where labels use the same behavior as `--env`, resulting
in a value to be copied from environment variables with the same name as the
label if no value is set (i.e. a simple key, no `=` sign, no value).
An earlier pull request addressed similar cases for `docker run`;
2b17f4c8a8caad552025edb05a73db683fb8a5c6, but this did not address the
same situation for (e.g.) `docker service create`.
Digging in history for this bug, I found that use of the `ValidateEnv`
function for labels was added in the original implementation of the labels feature in
https://github.com/docker/docker/commit/abb5e9a0777469e64fe2c7ecfa66ea01083d2071#diff-ae476143d40e21ac0918630f7365ed3cR34
However, the design never intended it to expand environment variables,
and use of this function was either due to either a "copy/paste" of the
equivalent `--env` flags, or a misunderstanding (the name `ValidateEnv` does
not communicate that it also expands environment variables), and the existing
`ValidateLabel` was designed for _engine_ labels (which required a value to
be set).
Following the initial implementation, other parts of the code followed
the same (incorrect) approach, therefore leading the bug to be introduced
in services as well.
This patch:
- updates the `ValidateLabel` to match the expected validation
rules (this function is no longer used since 31dc5c0a9a8bdc11c7ad335aebb753ed527caa5a),
and the daemon has its own implementation)
- corrects various locations in the code where `ValidateEnv` was used instead of `ValidateLabel`.
Before this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker service inspect --format '{{json .Spec.Labels}}' test
{"SOME_ENV_VAR":"I_AM_SOME_ENV_VAR"}
```
After this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker container inspect --format '{{json .Config.Labels}}' test
{"SOME_ENV_VAR":""}
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-02-13 10:47:30 -05:00
|
|
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
2016-09-08 13:11:39 -04:00
|
|
|
ipamAux: *opts.NewMapOpts(nil, nil),
|
|
|
|
ipamOpt: *opts.NewMapOpts(nil, nil),
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "create [OPTIONS] NETWORK",
|
|
|
|
Short: "Create a network",
|
|
|
|
Args: cli.ExactArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-05-15 08:45:19 -04:00
|
|
|
options.name = args[0]
|
|
|
|
return runCreate(dockerCli, options)
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
2022-05-12 07:18:48 -04:00
|
|
|
ValidArgsFunction: completion.NoComplete,
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network")
|
|
|
|
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
|
|
|
flags.Var(&options.labels, "label", "Set metadata on a network")
|
|
|
|
flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network")
|
|
|
|
flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking")
|
|
|
|
flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment")
|
2017-01-16 11:57:26 -05:00
|
|
|
flags.SetAnnotation("attachable", "version", []string{"1.25"})
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network")
|
2017-03-09 14:52:25 -05:00
|
|
|
flags.SetAnnotation("ingress", "version", []string{"1.29"})
|
2017-05-08 18:01:39 -04:00
|
|
|
flags.StringVar(&options.scope, "scope", "", "Control the network's scope")
|
|
|
|
flags.SetAnnotation("scope", "version", []string{"1.30"})
|
|
|
|
flags.BoolVar(&options.configOnly, "config-only", false, "Create a configuration only network")
|
|
|
|
flags.SetAnnotation("config-only", "version", []string{"1.30"})
|
2019-10-29 07:01:30 -04:00
|
|
|
flags.StringVar(&options.configFrom, "config-from", "", "The network from which to copy the configuration")
|
2017-05-08 18:01:39 -04:00
|
|
|
flags.SetAnnotation("config-from", "version", []string{"1.30"})
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.StringVar(&options.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
|
|
|
|
flags.StringSliceVar(&options.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
|
|
|
|
flags.StringSliceVar(&options.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range")
|
|
|
|
flags.StringSliceVar(&options.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet")
|
2016-09-08 13:11:39 -04:00
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
flags.Var(&options.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver")
|
|
|
|
flags.Var(&options.ipamOpt, "ipam-opt", "Set IPAM driver specific options")
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2017-06-21 05:25:25 -04:00
|
|
|
func runCreate(dockerCli command.Cli, options createOptions) error {
|
2016-09-08 13:11:39 -04:00
|
|
|
client := dockerCli.Client()
|
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll())
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct network create request body
|
|
|
|
nc := types.NetworkCreate{
|
2017-05-15 08:45:19 -04:00
|
|
|
Driver: options.driver,
|
|
|
|
Options: options.driverOpts.GetAll(),
|
2016-09-08 13:11:39 -04:00
|
|
|
IPAM: &network.IPAM{
|
2017-05-15 08:45:19 -04:00
|
|
|
Driver: options.ipamDriver,
|
2016-09-08 13:11:39 -04:00
|
|
|
Config: ipamCfg,
|
2017-05-15 08:45:19 -04:00
|
|
|
Options: options.ipamOpt.GetAll(),
|
2016-09-08 13:11:39 -04:00
|
|
|
},
|
|
|
|
CheckDuplicate: true,
|
2017-05-15 08:45:19 -04:00
|
|
|
Internal: options.internal,
|
|
|
|
EnableIPv6: options.ipv6,
|
|
|
|
Attachable: options.attachable,
|
|
|
|
Ingress: options.ingress,
|
2017-05-08 18:01:39 -04:00
|
|
|
Scope: options.scope,
|
|
|
|
ConfigOnly: options.configOnly,
|
2017-06-05 18:23:21 -04:00
|
|
|
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2017-05-08 18:01:39 -04:00
|
|
|
if from := options.configFrom; from != "" {
|
|
|
|
nc.ConfigFrom = &network.ConfigReference{
|
|
|
|
Network: from,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
resp, err := client.NetworkCreate(context.Background(), options.name, nc)
|
2016-09-08 13:11:39 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consolidates the ipam configuration as a group from different related configurations
|
|
|
|
// user can configure network with multiple non-overlapping subnets and hence it is
|
|
|
|
// possible to correlate the various related parameters and consolidate them.
|
2017-02-16 10:56:53 -05:00
|
|
|
// consolidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
|
2016-09-08 13:11:39 -04:00
|
|
|
// structured ipam data.
|
2017-05-02 16:14:43 -04:00
|
|
|
// nolint: gocyclo
|
2016-09-08 13:11:39 -04:00
|
|
|
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
|
|
|
|
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("every ip-range or gateway must have a corresponding subnet")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
iData := map[string]*network.IPAMConfig{}
|
|
|
|
|
|
|
|
// Populate non-overlapping subnets into consolidation map
|
|
|
|
for _, s := range subnets {
|
|
|
|
for k := range iData {
|
|
|
|
ok1, err := subnetMatches(s, k)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ok2, err := subnetMatches(k, s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if ok1 || ok2 {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("multiple overlapping subnet configuration is not supported")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate and add valid ip ranges
|
|
|
|
for _, r := range ranges {
|
|
|
|
match := false
|
|
|
|
for _, s := range subnets {
|
|
|
|
ok, err := subnetMatches(s, r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if iData[s].IPRange != "" {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
d := iData[s]
|
|
|
|
d.IPRange = r
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
if !match {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("no matching subnet for range %s", r)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate and add valid gateways
|
|
|
|
for _, g := range gateways {
|
|
|
|
match := false
|
|
|
|
for _, s := range subnets {
|
|
|
|
ok, err := subnetMatches(s, g)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if iData[s].Gateway != "" {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
d := iData[s]
|
|
|
|
d.Gateway = g
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
if !match {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("no matching subnet for gateway %s", g)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate and add aux-addresses
|
|
|
|
for key, aa := range auxaddrs {
|
|
|
|
match := false
|
|
|
|
for _, s := range subnets {
|
|
|
|
ok, err := subnetMatches(s, aa)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
iData[s].AuxAddress[key] = aa
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
if !match {
|
2017-03-09 13:23:45 -05:00
|
|
|
return nil, errors.Errorf("no matching subnet for aux-address %s", aa)
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idl := []network.IPAMConfig{}
|
|
|
|
for _, v := range iData {
|
|
|
|
idl = append(idl, *v)
|
|
|
|
}
|
|
|
|
return idl, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func subnetMatches(subnet, data string) (bool, error) {
|
|
|
|
var (
|
|
|
|
ip net.IP
|
|
|
|
)
|
|
|
|
|
|
|
|
_, s, err := net.ParseCIDR(subnet)
|
|
|
|
if err != nil {
|
2017-06-21 05:25:25 -04:00
|
|
|
return false, errors.Wrap(err, "invalid subnet")
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(data, "/") {
|
|
|
|
ip, _, err = net.ParseCIDR(data)
|
|
|
|
if err != nil {
|
2017-06-21 05:25:25 -04:00
|
|
|
return false, err
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ip = net.ParseIP(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.Contains(ip), nil
|
|
|
|
}
|