DockerCLI/cli/command/network/create.go

261 lines
7.4 KiB
Go
Raw Normal View History

package network
import (
"context"
"fmt"
"io"
"net"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type createOptions struct {
name string
scope string
driver string
driverOpts opts.MapOpts
labels opts.ListOpts
internal bool
ipv6 *bool
attachable bool
ingress bool
configOnly bool
configFrom string
ipam ipamOptions
}
type ipamOptions struct {
driver string
subnets []string
ipRanges []string
gateways []string
auxAddresses opts.MapOpts
driverOpts opts.MapOpts
}
func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
var ipv6 bool
options := createOptions{
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),
ipam: ipamOptions{
auxAddresses: *opts.NewMapOpts(nil, nil),
driverOpts: *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 {
options.name = args[0]
if cmd.Flag("ipv6").Changed {
options.ipv6 = &ipv6
}
return runCreate(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), options)
},
ValidArgsFunction: completion.NoComplete,
}
flags := cmd.Flags()
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(&ipv6, "ipv6", false, "Enable or disable IPv6 networking")
flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment")
Add version annotation to various flags added in 1.13 Pull request https://github.com/docker/docker/pull/27745 added support for the client to talk to older versions of the daemon. Various flags were added to docker 1.13 that are not compatible with older daemons. This PR adds annotations to those flags, so that they are automatically hidden if the daemon is older than docker 1.13 (API 1.25). Not all new flags affect the API (some are client-side only). The following PR's added new flags to docker 1.13 that affect the API; - https://github.com/docker/docker/pull/23430 added `--cpu-rt-period`and `--cpu-rt-runtime` - https://github.com/docker/docker/pull/27800 / https://github.com/docker/docker/pull/25317 added `--group` / `--group-add` / `--group-rm` - https://github.com/docker/docker/pull/27702 added `--network` to `docker build` - https://github.com/docker/docker/pull/25962 added `--attachable` to `docker network create` - https://github.com/docker/docker/pull/27998 added `--compose-file` to `docker stack deploy` - https://github.com/docker/docker/pull/22566 added `--stop-timeout` to `docker run` and `docker create` - https://github.com/docker/docker/pull/26061 added `--init` to `docker run` and `docker create` - https://github.com/docker/docker/pull/26941 added `--init-path` to `docker run` and `docker create` - https://github.com/docker/docker/pull/27958 added `--cpus` on `docker run` / `docker create` - https://github.com/docker/docker/pull/27567 added `--dns`, `--dns-opt`, and `--dns-search` to `docker service create` - https://github.com/docker/docker/pull/27596 added `--force` to `docker service update` - https://github.com/docker/docker/pull/27857 added `--hostname` to `docker service create` - https://github.com/docker/docker/pull/28031 added `--hosts`, `--host-add` / `--host-rm` to `docker service create` and `docker service update` - https://github.com/docker/docker/pull/28076 added `--tty` on `docker service create` / `docker service update` - https://github.com/docker/docker/pull/26421 added `--update-max-failure-ratio`, `--update-monitor` and `--rollback` on `docker service update` - https://github.com/docker/docker/pull/27369 added `--health-cmd`, `--health-interval`, `--health-retries`, `--health-timeout` and `--no-healthcheck` options to `docker service create` and `docker service update` Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-01-16 11:57:26 -05:00
flags.SetAnnotation("attachable", "version", []string{"1.25"})
flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network")
flags.SetAnnotation("ingress", "version", []string{"1.29"})
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"})
flags.StringVar(&options.configFrom, "config-from", "", "The network from which to copy the configuration")
flags.SetAnnotation("config-from", "version", []string{"1.30"})
flags.StringVar(&options.ipam.driver, "ipam-driver", "default", "IP Address Management Driver")
flags.StringSliceVar(&options.ipam.subnets, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
flags.StringSliceVar(&options.ipam.ipRanges, "ip-range", []string{}, "Allocate container ip from a sub-range")
flags.StringSliceVar(&options.ipam.gateways, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet")
flags.Var(&options.ipam.auxAddresses, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver")
flags.Var(&options.ipam.driverOpts, "ipam-opt", "Set IPAM driver specific options")
return cmd
}
func runCreate(ctx context.Context, apiClient client.NetworkAPIClient, output io.Writer, options createOptions) error {
ipamCfg, err := createIPAMConfig(options.ipam)
if err != nil {
return err
}
var configFrom *network.ConfigReference
if options.configFrom != "" {
configFrom = &network.ConfigReference{
Network: options.configFrom,
}
}
resp, err := apiClient.NetworkCreate(ctx, options.name, network.CreateOptions{
Driver: options.driver,
Options: options.driverOpts.GetAll(),
IPAM: ipamCfg,
Internal: options.internal,
EnableIPv6: options.ipv6,
Attachable: options.attachable,
Ingress: options.ingress,
Scope: options.scope,
ConfigOnly: options.configOnly,
ConfigFrom: configFrom,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
})
if err != nil {
return err
}
_, _ = fmt.Fprintf(output, "%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.
// createIPAMConfig consolidates subnets, ip-ranges, gateways and auxiliary addresses into
// structured ipam data.
//
//nolint:gocyclo
func createIPAMConfig(options ipamOptions) (*network.IPAM, error) {
if len(options.subnets) < len(options.ipRanges) || len(options.subnets) < len(options.gateways) {
return nil, errors.Errorf("every ip-range or gateway must have a corresponding subnet")
}
iData := map[string]*network.IPAMConfig{}
// Populate non-overlapping subnets into consolidation map
for _, s := range options.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 {
return nil, errors.Errorf("multiple overlapping subnet configuration is not supported")
}
}
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
}
// Validate and add valid ip ranges
for _, r := range options.ipRanges {
match := false
for _, s := range options.subnets {
if _, _, err := net.ParseCIDR(r); err != nil {
return nil, err
}
ok, err := subnetMatches(s, r)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].IPRange != "" {
return nil, errors.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
}
d := iData[s]
d.IPRange = r
match = true
}
if !match {
return nil, errors.Errorf("no matching subnet for range %s", r)
}
}
// Validate and add valid gateways
for _, g := range options.gateways {
match := false
for _, s := range options.subnets {
ok, err := subnetMatches(s, g)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].Gateway != "" {
return nil, errors.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
}
d := iData[s]
d.Gateway = g
match = true
}
if !match {
return nil, errors.Errorf("no matching subnet for gateway %s", g)
}
}
// Validate and add aux-addresses
for key, aa := range options.auxAddresses.GetAll() {
match := false
for _, s := range options.subnets {
ok, err := subnetMatches(s, aa)
if err != nil {
return nil, err
}
if !ok {
continue
}
iData[s].AuxAddress[key] = aa
match = true
}
if !match {
return nil, errors.Errorf("no matching subnet for aux-address %s", aa)
}
}
idl := make([]network.IPAMConfig, 0, len(iData))
for _, v := range iData {
idl = append(idl, *v)
}
return &network.IPAM{
Driver: options.driver,
Config: idl,
Options: options.driverOpts.GetAll(),
}, nil
}
func subnetMatches(subnet, data string) (bool, error) {
var ip net.IP
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, errors.Wrap(err, "invalid subnet")
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, err
}
} else {
ip = net.ParseIP(data)
}
return s.Contains(ip), nil
}