DockerCLI/cli/command/service/update.go

1519 lines
48 KiB
Go
Raw Normal View History

package service
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
units "github.com/docker/go-units"
"github.com/docker/swarmkit/api/defaults"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
options := newServiceOptions()
cmd := &cobra.Command{
Use: "update [OPTIONS] SERVICE",
Short: "Update a service",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runUpdate(dockerCli, cmd.Flags(), options, args[0])
},
}
flags := cmd.Flags()
flags.String("image", "", "Service image tag")
flags.Var(&ShlexOpt{}, "args", "Service command args")
flags.Bool(flagRollback, false, "Rollback to previous specification")
flags.SetAnnotation(flagRollback, "version", []string{"1.25"})
flags.Bool("force", false, "Force update even if no changes require it")
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("force", "version", []string{"1.25"})
addServiceFlags(flags, options, nil)
flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
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(flagGroupRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
// flags.Var(newListOptsVar().WithValidator(validatePublishRemove), flagPublishRemove, "Remove a published port by its target port")
flags.Var(&opts.PortOpt{}, flagPublishRemove, "Remove a published port by its target port")
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
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(flagDNSRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
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(flagDNSOptionRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain")
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(flagDNSSearchRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
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(flagHostRemove, "version", []string{"1.25"})
flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
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(flagSecretRemove, "version", []string{"1.25"})
flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service")
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(flagSecretAdd, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service")
flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
flags.Var(&options.networks, flagNetworkAdd, "Add a network")
flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
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(flagGroupAdd, "version", []string{"1.25"})
flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server")
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(flagDNSAdd, "version", []string{"1.25"})
flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
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(flagDNSOptionAdd, "version", []string{"1.25"})
flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
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(flagDNSSearchAdd, "version", []string{"1.25"})
flags.Var(&options.hosts, flagHostAdd, "Add a custom host-to-IP mapping (host:ip)")
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(flagHostAdd, "version", []string{"1.25"})
flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
flags.Var(&options.sysctls, flagSysCtlAdd, "Add or update a Sysctl option")
flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
flags.Var(&options.ulimits, flagUlimitAdd, "Add or update a ulimit option")
flags.SetAnnotation(flagUlimitAdd, "version", []string{"1.41"})
flags.Var(newListOptsVar(), flagUlimitRemove, "Remove a ulimit option")
flags.SetAnnotation(flagUlimitRemove, "version", []string{"1.41"})
// Add needs parsing, Remove only needs the key
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
return cmd
}
func newListOptsVar() *opts.ListOpts {
return opts.NewListOptsRef(&[]string{}, nil)
}
func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts {
return opts.NewListOptsRef(&[]string{}, validator)
}
// nolint: gocyclo
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
apiClient := dockerCli.Client()
ctx := context.Background()
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if err != nil {
return err
}
rollback, err := flags.GetBool(flagRollback)
if err != nil {
return err
}
// There are two ways to do user-requested rollback. The old way is
// client-side, but with a sufficiently recent daemon we prefer
// server-side, because it will honor the rollback parameters.
var (
clientSideRollback bool
serverSideRollback bool
)
spec := &service.Spec
if rollback {
// Rollback can't be combined with other flags.
otherFlagsPassed := false
flags.VisitAll(func(f *pflag.Flag) {
if f.Name == flagRollback || f.Name == flagDetach || f.Name == flagQuiet {
return
}
if flags.Changed(f.Name) {
otherFlagsPassed = true
}
})
if otherFlagsPassed {
return errors.New("other flags may not be combined with --rollback")
}
if versions.LessThan(apiClient.ClientVersion(), "1.28") {
clientSideRollback = true
spec = service.PreviousSpec
if spec == nil {
return errors.Errorf("service does not have a previous specification to roll back to")
}
} else {
serverSideRollback = true
}
}
updateOpts := types.ServiceUpdateOptions{}
if serverSideRollback {
updateOpts.Rollback = "previous"
}
err = updateService(ctx, apiClient, flags, spec)
if err != nil {
return err
}
if flags.Changed("image") {
if err := resolveServiceImageDigestContentTrust(dockerCli, spec); err != nil {
return err
}
if !options.noResolveImage && versions.GreaterThanOrEqualTo(apiClient.ClientVersion(), "1.30") {
updateOpts.QueryRegistry = true
}
}
updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
if err != nil {
return err
}
spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets
updatedConfigs, err := getUpdatedConfigs(apiClient, flags, spec.TaskTemplate.ContainerSpec)
if err != nil {
return err
}
spec.TaskTemplate.ContainerSpec.Configs = updatedConfigs
// set the credential spec value after get the updated configs, because we
// might need the updated configs to set the correct value of the
// CredentialSpec.
updateCredSpecConfig(flags, spec.TaskTemplate.ContainerSpec)
// only send auth if flag was set
sendAuth, err := flags.GetBool(flagRegistryAuth)
if err != nil {
return err
}
if sendAuth {
// Retrieve encoded auth token from the image reference
// This would be the old image if it didn't change in this update
image := spec.TaskTemplate.ContainerSpec.Image
encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
if err != nil {
return err
}
updateOpts.EncodedRegistryAuth = encodedAuth
} else if clientSideRollback {
updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
} else {
updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
}
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
if err != nil {
return err
}
for _, warning := range response.Warnings {
fmt.Fprintln(dockerCli.Err(), warning)
}
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
return nil
}
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
}
// nolint: gocyclo
func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
updateBoolPtr := func(flag string, field **bool) {
if flags.Changed(flag) {
b, _ := flags.GetBool(flag)
*field = &b
}
}
updateString := func(flag string, field *string) {
if flags.Changed(flag) {
*field, _ = flags.GetString(flag)
}
}
updateInt64Value := func(flag string, field *int64) {
if flags.Changed(flag) {
*field = flags.Lookup(flag).Value.(int64Value).Value()
}
}
updateFloatValue := func(flag string, field *float32) {
if flags.Changed(flag) {
*field = flags.Lookup(flag).Value.(*floatValue).Value()
}
}
updateDuration := func(flag string, field *time.Duration) {
if flags.Changed(flag) {
*field, _ = flags.GetDuration(flag)
}
}
updateDurationOpt := func(flag string, field **time.Duration) {
if flags.Changed(flag) {
val := *flags.Lookup(flag).Value.(*opts.DurationOpt).Value()
*field = &val
}
}
updateInt64 := func(flag string, field *int64) {
if flags.Changed(flag) {
*field, _ = flags.GetInt64(flag)
}
}
updateUint64 := func(flag string, field *uint64) {
if flags.Changed(flag) {
*field, _ = flags.GetUint64(flag)
}
}
updateUint64Opt := func(flag string, field **uint64) {
if flags.Changed(flag) {
val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
*field = &val
}
}
updateIsolation := func(flag string, field *container.Isolation) error {
if flags.Changed(flag) {
val, _ := flags.GetString(flag)
*field = container.Isolation(val)
}
return nil
}
cspec := spec.TaskTemplate.ContainerSpec
task := &spec.TaskTemplate
taskResources := func() *swarm.ResourceRequirements {
if task.Resources == nil {
task.Resources = &swarm.ResourceRequirements{}
}
if task.Resources.Limits == nil {
task.Resources.Limits = &swarm.Limit{}
}
if task.Resources.Reservations == nil {
task.Resources.Reservations = &swarm.Resources{}
}
return task.Resources
}
updateLabels(flags, &spec.Labels)
updateContainerLabels(flags, &cspec.Labels)
updateString("image", &cspec.Image)
updateStringToSlice(flags, "args", &cspec.Args)
updateStringToSlice(flags, flagEntrypoint, &cspec.Command)
updateEnvironment(flags, &cspec.Env)
updateString(flagWorkdir, &cspec.Dir)
updateString(flagUser, &cspec.User)
updateString(flagHostname, &cspec.Hostname)
updateBoolPtr(flagInit, &cspec.Init)
if err := updateIsolation(flagIsolation, &cspec.Isolation); err != nil {
return err
}
if err := updateMounts(flags, &cspec.Mounts); err != nil {
return err
}
updateSysCtls(flags, &task.ContainerSpec.Sysctls)
task.ContainerSpec.Ulimits = updateUlimits(flags, task.ContainerSpec.Ulimits)
if anyChanged(flags, flagLimitCPU, flagLimitMemory, flagLimitPids) {
Fix cpu/memory limits and reservations being reset on service update Before this change: ---------------------------------------------------- Create a service with reservations and limits for memory and cpu: docker service create --name test \ --limit-memory=100M --limit-cpu=1 \ --reserve-memory=100M --reserve-cpu=1 \ nginx:alpine Verify the configuration docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 } } Update just CPU limit and reservation: docker service update --limit-cpu=2 --reserve-cpu=2 test Notice that the memory limit and reservation is not preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000 }, "Reservations": { "NanoCPUs": 2000000000 } } Update just Memory limit and reservation: docker service update --limit-memory=200M --reserve-memory=200M test Notice that the CPU limit and reservation is not preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "MemoryBytes": 209715200 }, "Reservations": { "MemoryBytes": 209715200 } } After this change: ---------------------------------------------------- Create a service with reservations and limits for memory and cpu: docker service create --name test \ --limit-memory=100M --limit-cpu=1 \ --reserve-memory=100M --reserve-cpu=1 \ nginx:alpine Verify the configuration docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 } } Update just CPU limit and reservation: docker service update --limit-cpu=2 --reserve-cpu=2 test Confirm that the CPU limits/reservations are updated, but memory limit and reservation are preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 2000000000, "MemoryBytes": 104857600 } } Update just Memory limit and reservation: docker service update --limit-memory=200M --reserve-memory=200M test Confirm that the Mempry limits/reservations are updated, but CPU limit and reservation are preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000, "MemoryBytes": 209715200 }, "Reservations": { "NanoCPUs": 2000000000, "MemoryBytes": 209715200 } } Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-05-23 19:11:49 -04:00
taskResources().Limits = spec.TaskTemplate.Resources.Limits
updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
updateInt64(flagLimitPids, &task.Resources.Limits.Pids)
}
if anyChanged(flags, flagReserveCPU, flagReserveMemory) {
Fix cpu/memory limits and reservations being reset on service update Before this change: ---------------------------------------------------- Create a service with reservations and limits for memory and cpu: docker service create --name test \ --limit-memory=100M --limit-cpu=1 \ --reserve-memory=100M --reserve-cpu=1 \ nginx:alpine Verify the configuration docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 } } Update just CPU limit and reservation: docker service update --limit-cpu=2 --reserve-cpu=2 test Notice that the memory limit and reservation is not preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000 }, "Reservations": { "NanoCPUs": 2000000000 } } Update just Memory limit and reservation: docker service update --limit-memory=200M --reserve-memory=200M test Notice that the CPU limit and reservation is not preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "MemoryBytes": 209715200 }, "Reservations": { "MemoryBytes": 209715200 } } After this change: ---------------------------------------------------- Create a service with reservations and limits for memory and cpu: docker service create --name test \ --limit-memory=100M --limit-cpu=1 \ --reserve-memory=100M --reserve-cpu=1 \ nginx:alpine Verify the configuration docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 1000000000, "MemoryBytes": 104857600 } } Update just CPU limit and reservation: docker service update --limit-cpu=2 --reserve-cpu=2 test Confirm that the CPU limits/reservations are updated, but memory limit and reservation are preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000, "MemoryBytes": 104857600 }, "Reservations": { "NanoCPUs": 2000000000, "MemoryBytes": 104857600 } } Update just Memory limit and reservation: docker service update --limit-memory=200M --reserve-memory=200M test Confirm that the Mempry limits/reservations are updated, but CPU limit and reservation are preserved: docker service inspect --format '{{json .Spec.TaskTemplate.Resources}}' test { "Limits": { "NanoCPUs": 2000000000, "MemoryBytes": 209715200 }, "Reservations": { "NanoCPUs": 2000000000, "MemoryBytes": 209715200 } } Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-05-23 19:11:49 -04:00
taskResources().Reservations = spec.TaskTemplate.Resources.Reservations
updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
}
if err := addGenericResources(flags, task); err != nil {
return err
}
if err := removeGenericResources(flags, task); err != nil {
return err
}
updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
if task.RestartPolicy == nil {
task.RestartPolicy = defaultRestartPolicy()
}
if flags.Changed(flagRestartCondition) {
value, _ := flags.GetString(flagRestartCondition)
task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
}
updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
}
if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
if task.Placement == nil {
task.Placement = &swarm.Placement{}
}
updatePlacementConstraints(flags, task.Placement)
}
if anyChanged(flags, flagPlacementPrefAdd, flagPlacementPrefRemove) {
if task.Placement == nil {
task.Placement = &swarm.Placement{}
}
updatePlacementPreferences(flags, task.Placement)
}
if anyChanged(flags, flagNetworkAdd, flagNetworkRemove) {
if err := updateNetworks(ctx, apiClient, flags, spec); err != nil {
return err
}
}
if err := updateReplicas(flags, &spec.Mode); err != nil {
return err
}
if anyChanged(flags, flagMaxReplicas) {
updateUint64(flagMaxReplicas, &task.Placement.MaxReplicas)
}
if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) {
if spec.UpdateConfig == nil {
spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update)
}
updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor)
updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
updateFloatValue(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio)
updateString(flagUpdateOrder, &spec.UpdateConfig.Order)
}
if anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio, flagRollbackOrder) {
if spec.RollbackConfig == nil {
spec.RollbackConfig = updateConfigFromDefaults(defaults.Service.Rollback)
}
updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism)
updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay)
updateDuration(flagRollbackMonitor, &spec.RollbackConfig.Monitor)
updateString(flagRollbackFailureAction, &spec.RollbackConfig.FailureAction)
updateFloatValue(flagRollbackMaxFailureRatio, &spec.RollbackConfig.MaxFailureRatio)
updateString(flagRollbackOrder, &spec.RollbackConfig.Order)
}
if flags.Changed(flagEndpointMode) {
value, _ := flags.GetString(flagEndpointMode)
if spec.EndpointSpec == nil {
spec.EndpointSpec = &swarm.EndpointSpec{}
}
spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
}
if anyChanged(flags, flagGroupAdd, flagGroupRemove) {
if err := updateGroups(flags, &cspec.Groups); err != nil {
return err
}
}
if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
if spec.EndpointSpec == nil {
spec.EndpointSpec = &swarm.EndpointSpec{}
}
if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
return err
}
}
if anyChanged(flags, flagDNSAdd, flagDNSRemove, flagDNSOptionAdd, flagDNSOptionRemove, flagDNSSearchAdd, flagDNSSearchRemove) {
if cspec.DNSConfig == nil {
cspec.DNSConfig = &swarm.DNSConfig{}
}
if err := updateDNSConfig(flags, &cspec.DNSConfig); err != nil {
return err
}
}
if anyChanged(flags, flagHostAdd, flagHostRemove) {
if err := updateHosts(flags, &cspec.Hosts); err != nil {
return err
}
}
if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
return err
}
force, err := flags.GetBool("force")
if err != nil {
return err
}
if force {
spec.TaskTemplate.ForceUpdate++
}
if err := updateHealthcheck(flags, cspec); err != nil {
return err
}
if flags.Changed(flagTTY) {
tty, err := flags.GetBool(flagTTY)
if err != nil {
return err
}
cspec.TTY = tty
}
if flags.Changed(flagReadOnly) {
readOnly, err := flags.GetBool(flagReadOnly)
if err != nil {
return err
}
cspec.ReadOnly = readOnly
}
updateString(flagStopSignal, &cspec.StopSignal)
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
if anyChanged(flags, flagCapAdd, flagCapDrop) {
updateCapabilities(flags, cspec)
}
return nil
}
func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) {
if !flags.Changed(flag) {
return
}
*field = flags.Lookup(flag).Value.(*ShlexOpt).Value()
}
func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
for _, flag := range fields {
if flags.Changed(flag) {
return true
}
}
return false
}
func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
if !flags.Changed(flagGenericResourcesAdd) {
return nil
}
if spec.Resources == nil {
spec.Resources = &swarm.ResourceRequirements{}
}
if spec.Resources.Reservations == nil {
spec.Resources.Reservations = &swarm.Resources{}
}
values := flags.Lookup(flagGenericResourcesAdd).Value.(*opts.ListOpts).GetAll()
generic, err := ParseGenericResources(values)
if err != nil {
return err
}
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
if err != nil {
return err
}
for _, toAddRes := range generic {
m[toAddRes.DiscreteResourceSpec.Kind] = toAddRes
}
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
return nil
}
func removeGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
// Can only be Discrete Resources
if !flags.Changed(flagGenericResourcesRemove) {
return nil
}
if spec.Resources == nil {
spec.Resources = &swarm.ResourceRequirements{}
}
if spec.Resources.Reservations == nil {
spec.Resources.Reservations = &swarm.Resources{}
}
values := flags.Lookup(flagGenericResourcesRemove).Value.(*opts.ListOpts).GetAll()
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
if err != nil {
return err
}
for _, toRemoveRes := range values {
if _, ok := m[toRemoveRes]; !ok {
return fmt.Errorf("could not find generic-resource `%s` to remove it", toRemoveRes)
}
delete(m, toRemoveRes)
}
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
return nil
}
func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement) {
if flags.Changed(flagConstraintAdd) {
values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
placement.Constraints = append(placement.Constraints, values...)
}
toRemove := buildToRemoveSet(flags, flagConstraintRemove)
newConstraints := []string{}
for _, constraint := range placement.Constraints {
if _, exists := toRemove[constraint]; !exists {
newConstraints = append(newConstraints, constraint)
}
}
// Sort so that result is predictable.
sort.Strings(newConstraints)
placement.Constraints = newConstraints
}
func updatePlacementPreferences(flags *pflag.FlagSet, placement *swarm.Placement) {
var newPrefs []swarm.PlacementPreference
if flags.Changed(flagPlacementPrefRemove) {
for _, existing := range placement.Preferences {
removed := false
for _, removal := range flags.Lookup(flagPlacementPrefRemove).Value.(*placementPrefOpts).prefs {
if removal.Spread != nil && existing.Spread != nil && removal.Spread.SpreadDescriptor == existing.Spread.SpreadDescriptor {
removed = true
break
}
}
if !removed {
newPrefs = append(newPrefs, existing)
}
}
} else {
newPrefs = placement.Preferences
}
if flags.Changed(flagPlacementPrefAdd) {
newPrefs = append(newPrefs,
flags.Lookup(flagPlacementPrefAdd).Value.(*placementPrefOpts).prefs...)
}
placement.Preferences = newPrefs
}
func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
Fix order of processing of some xx-add/xx-rm service update flags Combining `-add` and `-rm` flags on `docker service update` should be usable to explicitly replace existing options. The current order of processing did not allow this, causing the `-rm` flag to remove properties that were specified in `-add`. This behavior was inconsistent with (for example) `--host-add` and `--host-rm`. This patch updates the behavior to first remove properties, then add new properties. Note that there's still some improvements to make, to make the removal more granulas (e.g. to make `--label-rm label=some-value` only remove the label if value matches `some-value`); these changes are left for a follow-up. Before this change: ----------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", } ``` With this patch applied: -------------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz", "FOO=updated-foo" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-04 13:09:59 -04:00
if *field != nil && flags.Changed(flagContainerLabelRemove) {
toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
for _, label := range toRemove {
delete(*field, label)
}
}
if flags.Changed(flagContainerLabelAdd) {
if *field == nil {
*field = map[string]string{}
}
values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
}
Fix order of processing of some xx-add/xx-rm service update flags Combining `-add` and `-rm` flags on `docker service update` should be usable to explicitly replace existing options. The current order of processing did not allow this, causing the `-rm` flag to remove properties that were specified in `-add`. This behavior was inconsistent with (for example) `--host-add` and `--host-rm`. This patch updates the behavior to first remove properties, then add new properties. Note that there's still some improvements to make, to make the removal more granulas (e.g. to make `--label-rm label=some-value` only remove the label if value matches `some-value`); these changes are left for a follow-up. Before this change: ----------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", } ``` With this patch applied: -------------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz", "FOO=updated-foo" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-04 13:09:59 -04:00
}
Fix order of processing of some xx-add/xx-rm service update flags Combining `-add` and `-rm` flags on `docker service update` should be usable to explicitly replace existing options. The current order of processing did not allow this, causing the `-rm` flag to remove properties that were specified in `-add`. This behavior was inconsistent with (for example) `--host-add` and `--host-rm`. This patch updates the behavior to first remove properties, then add new properties. Note that there's still some improvements to make, to make the removal more granulas (e.g. to make `--label-rm label=some-value` only remove the label if value matches `some-value`); these changes are left for a follow-up. Before this change: ----------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", } ``` With this patch applied: -------------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz", "FOO=updated-foo" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-04 13:09:59 -04:00
func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
if *field != nil && flags.Changed(flagLabelRemove) {
toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
for _, label := range toRemove {
delete(*field, label)
}
}
if flags.Changed(flagLabelAdd) {
if *field == nil {
*field = map[string]string{}
}
values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
}
}
func updateSysCtls(flags *pflag.FlagSet, field *map[string]string) {
if *field != nil && flags.Changed(flagSysCtlRemove) {
values := flags.Lookup(flagSysCtlRemove).Value.(*opts.ListOpts).GetAll()
for key := range opts.ConvertKVStringsToMap(values) {
delete(*field, key)
}
}
if flags.Changed(flagSysCtlAdd) {
if *field == nil {
*field = map[string]string{}
}
values := flags.Lookup(flagSysCtlAdd).Value.(*opts.ListOpts).GetAll()
for key, value := range opts.ConvertKVStringsToMap(values) {
(*field)[key] = value
}
}
}
func updateUlimits(flags *pflag.FlagSet, ulimits []*units.Ulimit) []*units.Ulimit {
newUlimits := make(map[string]*units.Ulimit)
for _, ulimit := range ulimits {
newUlimits[ulimit.Name] = ulimit
}
if flags.Changed(flagUlimitRemove) {
values := flags.Lookup(flagUlimitRemove).Value.(*opts.ListOpts).GetAll()
for key := range opts.ConvertKVStringsToMap(values) {
delete(newUlimits, key)
}
}
if flags.Changed(flagUlimitAdd) {
for _, ulimit := range flags.Lookup(flagUlimitAdd).Value.(*opts.UlimitOpt).GetList() {
newUlimits[ulimit.Name] = ulimit
}
}
var limits []*units.Ulimit
for _, ulimit := range newUlimits {
limits = append(limits, ulimit)
}
sort.SliceStable(limits, func(i, j int) bool {
return limits[i].Name < limits[j].Name
})
return limits
}
func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
Fix order of processing of some xx-add/xx-rm service update flags Combining `-add` and `-rm` flags on `docker service update` should be usable to explicitly replace existing options. The current order of processing did not allow this, causing the `-rm` flag to remove properties that were specified in `-add`. This behavior was inconsistent with (for example) `--host-add` and `--host-rm`. This patch updates the behavior to first remove properties, then add new properties. Note that there's still some improvements to make, to make the removal more granulas (e.g. to make `--label-rm label=some-value` only remove the label if value matches `some-value`); these changes are left for a follow-up. Before this change: ----------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, with the intent to replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", } ``` With this patch applied: -------------------------------- Create a service with two env-vars ```bash docker service create --env FOO=bar --env BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "FOO=bar", "BAR=baz" ] ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --env-rm FOO --env-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Env }}' test | jq . [ "BAR=baz", "FOO=updated-foo" ] ``` Create a service with two labels ```bash docker service create --label FOO=bar --label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --label-rm FOO --label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Create a service with two container labels ```bash docker service create --container-label FOO=bar --container-label BAR=baz --name=test nginx:alpine docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "bar" } ``` Update the service, and replace the value of `FOO` for a new value ```bash docker service update --container-label-rm FOO --container-label-add FOO=updated-foo test docker service inspect --format '{{json .Spec.TaskTemplate.ContainerSpec.Labels }}' test | jq . { "BAR": "baz", "FOO": "updated-foo" } ``` Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-04 13:09:59 -04:00
toRemove := buildToRemoveSet(flags, flagEnvRemove)
*field = removeItems(*field, toRemove, envKey)
if flags.Changed(flagEnvAdd) {
envSet := map[string]string{}
for _, v := range *field {
envSet[envKey(v)] = v
}
value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
for _, v := range value.GetAll() {
envSet[envKey(v)] = v
}
*field = []string{}
for _, v := range envSet {
*field = append(*field, v)
}
}
}
Update order of '--secret-rm' and '--secret-add' When using both `--secret-rm` and `--secret-add` on `docker service update`, `--secret-rm` was always performed last. This made it impossible to update a secret that was already in use on a service (for example, to change it's permissions, or mount-location inside the container). This patch changes the order in which `rm` and `add` are performed, allowing updating a secret in a single `docker service update`. Before this change, the `rm` was always performed "last", so the secret was always removed: $ echo "foo" | docker secret create foo -f - foo $ docker service create --name myservice --secret foo nginx:alpine 62xjcr9sr0c2hvepdzqrn3ssn $ docker service update --secret-rm foo --secret-add source=foo,target=foo2 myservice myservice $ docker service inspect --format '{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}' myservice | jq . null After this change, the `rm` is performed _first_, allowing users to update a secret without updating the service _twice_; $ echo "foo" | docker secret create foo -f - 1bllmvw3a1yaq3eixqw3f7bjl $ docker service create --name myservice --secret foo nginx:alpine lr6s3uoggli1x0hab78glpcxo $ docker service update --secret-rm foo --secret-add source=foo,target=foo2 myservice myservice $ docker service inspect --format '{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}' myservice | jq . [ { "File": { "Name": "foo2", "UID": "0", "GID": "0", "Mode": 292 }, "SecretID": "tn9qiblgnuuut11eufquw5dev", "SecretName": "foo" } ] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2016-12-30 12:15:53 -05:00
func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) {
newSecrets := []*swarm.SecretReference{}
toRemove := buildToRemoveSet(flags, flagSecretRemove)
for _, secret := range secrets {
if _, exists := toRemove[secret.SecretName]; !exists {
newSecrets = append(newSecrets, secret)
}
}
if flags.Changed(flagSecretAdd) {
values := flags.Lookup(flagSecretAdd).Value.(*opts.SecretOpt).Value()
addSecrets, err := ParseSecrets(apiClient, values)
if err != nil {
return nil, err
}
Update order of '--secret-rm' and '--secret-add' When using both `--secret-rm` and `--secret-add` on `docker service update`, `--secret-rm` was always performed last. This made it impossible to update a secret that was already in use on a service (for example, to change it's permissions, or mount-location inside the container). This patch changes the order in which `rm` and `add` are performed, allowing updating a secret in a single `docker service update`. Before this change, the `rm` was always performed "last", so the secret was always removed: $ echo "foo" | docker secret create foo -f - foo $ docker service create --name myservice --secret foo nginx:alpine 62xjcr9sr0c2hvepdzqrn3ssn $ docker service update --secret-rm foo --secret-add source=foo,target=foo2 myservice myservice $ docker service inspect --format '{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}' myservice | jq . null After this change, the `rm` is performed _first_, allowing users to update a secret without updating the service _twice_; $ echo "foo" | docker secret create foo -f - 1bllmvw3a1yaq3eixqw3f7bjl $ docker service create --name myservice --secret foo nginx:alpine lr6s3uoggli1x0hab78glpcxo $ docker service update --secret-rm foo --secret-add source=foo,target=foo2 myservice myservice $ docker service inspect --format '{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}' myservice | jq . [ { "File": { "Name": "foo2", "UID": "0", "GID": "0", "Mode": 292 }, "SecretID": "tn9qiblgnuuut11eufquw5dev", "SecretName": "foo" } ] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2016-12-30 12:15:53 -05:00
newSecrets = append(newSecrets, addSecrets...)
}
return newSecrets, nil
}
func getUpdatedConfigs(apiClient client.ConfigAPIClient, flags *pflag.FlagSet, spec *swarm.ContainerSpec) ([]*swarm.ConfigReference, error) {
var (
// credSpecConfigName stores the name of the config specified by the
// credential-spec flag. if a Runtime target Config with this name is
// already in the containerSpec, then this value will be set to
// emptystring in the removeConfigs stage. otherwise, a ConfigReference
// will be created to pass to ParseConfigs to get the ConfigID.
credSpecConfigName string
// credSpecConfigID stores the ID of the credential spec config if that
// config is being carried over from the old set of references
credSpecConfigID string
)
if flags.Changed(flagCredentialSpec) {
credSpec := flags.Lookup(flagCredentialSpec).Value.(*credentialSpecOpt).Value()
credSpecConfigName = credSpec.Config
} else {
// if the credential spec flag has not changed, then check if there
// already is a credentialSpec. if there is one, and it's for a Config,
// then it's from the old object, and its value is the config ID. we
// need this so we don't remove the config if the credential spec is
// not being updated.
if spec.Privileges != nil && spec.Privileges.CredentialSpec != nil {
if config := spec.Privileges.CredentialSpec.Config; config != "" {
credSpecConfigID = config
}
}
}
newConfigs := removeConfigs(flags, spec, credSpecConfigName, credSpecConfigID)
// resolveConfigs is a slice of any new configs that need to have the ID
// resolved
resolveConfigs := []*swarm.ConfigReference{}
if flags.Changed(flagConfigAdd) {
resolveConfigs = append(resolveConfigs, flags.Lookup(flagConfigAdd).Value.(*opts.ConfigOpt).Value()...)
}
// if credSpecConfigNameis non-empty at this point, it means its a new
// config, and we need to resolve its ID accordingly.
if credSpecConfigName != "" {
resolveConfigs = append(resolveConfigs, &swarm.ConfigReference{
ConfigName: credSpecConfigName,
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
})
}
if len(resolveConfigs) > 0 {
addConfigs, err := ParseConfigs(apiClient, resolveConfigs)
if err != nil {
return nil, err
}
newConfigs = append(newConfigs, addConfigs...)
}
return newConfigs, nil
}
// removeConfigs figures out which configs in the existing spec should be kept
// after the update.
func removeConfigs(flags *pflag.FlagSet, spec *swarm.ContainerSpec, credSpecName, credSpecID string) []*swarm.ConfigReference {
keepConfigs := []*swarm.ConfigReference{}
toRemove := buildToRemoveSet(flags, flagConfigRemove)
// all configs in spec.Configs should have both a Name and ID, because
// they come from an already-accepted spec.
for _, config := range spec.Configs {
// if the config is a Runtime target, make sure it's still in use right
// now, the only use for Runtime target is credential specs. if, in
// the future, more uses are added, then this check will need to be
// made more intelligent.
if config.Runtime != nil {
// if we're carrying over a credential spec explicitly (because the
// user passed --credential-spec with the same config name) then we
// should match on credSpecName. if we're carrying over a
// credential spec implicitly (because the user did not pass any
// --credential-spec flag) then we should match on credSpecID. in
// either case, we're keeping the config that already exists.
if config.ConfigName == credSpecName || config.ConfigID == credSpecID {
keepConfigs = append(keepConfigs, config)
}
// continue the loop, to skip the part where we check if the config
// is in toRemove.
continue
}
if _, exists := toRemove[config.ConfigName]; !exists {
keepConfigs = append(keepConfigs, config)
}
}
return keepConfigs
}
func envKey(value string) string {
kv := strings.SplitN(value, "=", 2)
return kv[0]
}
func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
var empty struct{}
toRemove := make(map[string]struct{})
if !flags.Changed(flag) {
return toRemove
}
toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
for _, key := range toRemoveSlice {
toRemove[key] = empty
}
return toRemove
}
func removeItems(
seq []string,
toRemove map[string]struct{},
keyFunc func(string) string,
) []string {
newSeq := []string{}
for _, item := range seq {
if _, exists := toRemove[keyFunc(item)]; !exists {
newSeq = append(newSeq, item)
}
}
return newSeq
}
func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
mountsByTarget := map[string]mounttypes.Mount{}
if flags.Changed(flagMountAdd) {
values := flags.Lookup(flagMountAdd).Value.(*opts.MountOpt).Value()
for _, mount := range values {
if _, ok := mountsByTarget[mount.Target]; ok {
return errors.Errorf("duplicate mount target")
}
mountsByTarget[mount.Target] = mount
}
}
// Add old list of mount points minus updated one.
for _, mount := range *mounts {
if _, ok := mountsByTarget[mount.Target]; !ok {
mountsByTarget[mount.Target] = mount
}
}
newMounts := []mounttypes.Mount{}
toRemove := buildToRemoveSet(flags, flagMountRemove)
for _, mount := range mountsByTarget {
if _, exists := toRemove[mount.Target]; !exists {
newMounts = append(newMounts, mount)
}
}
sort.Slice(newMounts, func(i, j int) bool {
a, b := newMounts[i], newMounts[j]
if a.Source == b.Source {
return a.Target < b.Target
}
return a.Source < b.Source
})
*mounts = newMounts
return nil
}
func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
if flags.Changed(flagGroupAdd) {
values := flags.Lookup(flagGroupAdd).Value.(*opts.ListOpts).GetAll()
*groups = append(*groups, values...)
}
toRemove := buildToRemoveSet(flags, flagGroupRemove)
newGroups := []string{}
for _, group := range *groups {
if _, exists := toRemove[group]; !exists {
newGroups = append(newGroups, group)
}
}
// Sort so that result is predictable.
sort.Strings(newGroups)
*groups = newGroups
return nil
}
func removeDuplicates(entries []string) []string {
hit := map[string]bool{}
newEntries := []string{}
for _, v := range entries {
if !hit[v] {
newEntries = append(newEntries, v)
hit[v] = true
}
}
return newEntries
}
func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
newConfig := &swarm.DNSConfig{}
nameservers := (*config).Nameservers
if flags.Changed(flagDNSAdd) {
values := flags.Lookup(flagDNSAdd).Value.(*opts.ListOpts).GetAll()
nameservers = append(nameservers, values...)
}
nameservers = removeDuplicates(nameservers)
toRemove := buildToRemoveSet(flags, flagDNSRemove)
for _, nameserver := range nameservers {
if _, exists := toRemove[nameserver]; !exists {
newConfig.Nameservers = append(newConfig.Nameservers, nameserver)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Nameservers)
search := (*config).Search
if flags.Changed(flagDNSSearchAdd) {
values := flags.Lookup(flagDNSSearchAdd).Value.(*opts.ListOpts).GetAll()
search = append(search, values...)
}
search = removeDuplicates(search)
toRemove = buildToRemoveSet(flags, flagDNSSearchRemove)
for _, entry := range search {
if _, exists := toRemove[entry]; !exists {
newConfig.Search = append(newConfig.Search, entry)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Search)
options := (*config).Options
if flags.Changed(flagDNSOptionAdd) {
values := flags.Lookup(flagDNSOptionAdd).Value.(*opts.ListOpts).GetAll()
options = append(options, values...)
}
options = removeDuplicates(options)
toRemove = buildToRemoveSet(flags, flagDNSOptionRemove)
for _, option := range options {
if _, exists := toRemove[option]; !exists {
newConfig.Options = append(newConfig.Options, option)
}
}
// Sort so that result is predictable.
sort.Strings(newConfig.Options)
*config = newConfig
return nil
}
func portConfigToString(portConfig *swarm.PortConfig) string {
protocol := portConfig.Protocol
mode := portConfig.PublishMode
return fmt.Sprintf("%v:%v/%s/%s", portConfig.PublishedPort, portConfig.TargetPort, protocol, mode)
}
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
// The key of the map is `port/protocol`, e.g., `80/tcp`
portSet := map[string]swarm.PortConfig{}
// Build the current list of portConfig
for _, entry := range *portConfig {
entry := entry
if _, ok := portSet[portConfigToString(&entry)]; !ok {
portSet[portConfigToString(&entry)] = entry
}
}
newPorts := []swarm.PortConfig{}
// Clean current ports
toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.PortOpt).Value()
portLoop:
for _, port := range portSet {
for _, pConfig := range toRemove {
if equalProtocol(port.Protocol, pConfig.Protocol) &&
port.TargetPort == pConfig.TargetPort &&
equalPublishMode(port.PublishMode, pConfig.PublishMode) {
continue portLoop
}
}
newPorts = append(newPorts, port)
}
// Check to see if there are any conflict in flags.
if flags.Changed(flagPublishAdd) {
ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
for _, port := range ports {
port := port
if _, ok := portSet[portConfigToString(&port)]; ok {
continue
}
// portSet[portConfigToString(&port)] = port
newPorts = append(newPorts, port)
}
}
// Sort the PortConfig to avoid unnecessary updates
sort.Slice(newPorts, func(i, j int) bool {
// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
// In updatePorts we already filter out with map so there is duplicate entries
return portConfigToString(&newPorts[i]) < portConfigToString(&newPorts[j])
})
*portConfig = newPorts
return nil
}
func equalProtocol(prot1, prot2 swarm.PortConfigProtocol) bool {
return prot1 == prot2 ||
(prot1 == swarm.PortConfigProtocol("") && prot2 == swarm.PortConfigProtocolTCP) ||
(prot2 == swarm.PortConfigProtocol("") && prot1 == swarm.PortConfigProtocolTCP)
}
func equalPublishMode(mode1, mode2 swarm.PortConfigPublishMode) bool {
return mode1 == mode2 ||
(mode1 == swarm.PortConfigPublishMode("") && mode2 == swarm.PortConfigPublishModeIngress) ||
(mode2 == swarm.PortConfigPublishMode("") && mode1 == swarm.PortConfigPublishModeIngress)
}
func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
if !flags.Changed(flagReplicas) {
return nil
}
if serviceMode == nil || serviceMode.Replicated == nil {
return errors.Errorf("replicas can only be used with replicated mode")
}
serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
return nil
}
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
type hostMapping struct {
IPAddr string
Host string
}
Preserve sort-order of extra hosts, and allow duplicate entries Extra hosts (`extra_hosts` in compose-file, or `--hosts` in services) adds custom host/ip mappings to the container's `/etc/hosts`. The current implementation used a `map[string]string{}` as intermediate storage, and sorted the results alphabetically when converting to a service-spec. As a result, duplicate hosts were removed, and order of host/ip mappings was not preserved (in case the compose-file used a list instead of a map). According to the **host.conf(5)** man page (http://man7.org/linux/man-pages/man5/host.conf.5.html) multi Valid values are on and off. If set to on, the resolver library will return all valid addresses for a host that appears in the /etc/hosts file, instead of only the first. This is off by default, as it may cause a substantial performance loss at sites with large hosts files. Multiple entries for a host are allowed, and even required for some situations, for example, to add mappings for IPv4 and IPv6 addreses for a host, as illustrated by the example hosts file in the **hosts(5)** man page (http://man7.org/linux/man-pages/man5/hosts.5.html): # The following lines are desirable for IPv4 capable hosts 127.0.0.1 localhost # 127.0.1.1 is often used for the FQDN of the machine 127.0.1.1 thishost.mydomain.org thishost 192.168.1.10 foo.mydomain.org foo 192.168.1.13 bar.mydomain.org bar 146.82.138.7 master.debian.org master 209.237.226.90 www.opensource.org # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters This patch changes the intermediate storage format to use a `[]string`, and only sorts entries if the input format in the compose file is a mapping. If the input format is a list, the original sort-order is preserved. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-29 20:33:23 -04:00
// updateHosts performs a diff between existing host entries, entries to be
// removed, and entries to be added. Host entries preserve the order in which they
// were added, as the specification mentions that in case multiple entries for a
// host exist, the first entry should be used (by default).
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
//
// Note that, even though unsupported by the the CLI, the service specs format
// allow entries with both a _canonical_ hostname, and one or more aliases
// in an entry (IP-address canonical_hostname [alias ...])
//
// Entries can be removed by either a specific `<host-name>:<ip-address>` mapping,
// or by `<host>` alone:
//
// - If both IP-address and host-name is provided, the hostname is removed only
// from entries that match the given IP-address.
// - If only a host-name is provided, the hostname is removed from any entry it
// is part of (either as canonical host-name, or as alias).
// - If, after removing the host-name from an entry, no host-names remain in
// the entry, the entry itself is removed.
//
// For example, the list of host-entries before processing could look like this:
//
// hosts = &[]string{
// "127.0.0.2 host3 host1 host2 host4",
// "127.0.0.1 host1 host4",
// "127.0.0.3 host1",
// "127.0.0.1 host1",
// }
//
// Removing `host1` removes every occurrence:
//
// hosts = &[]string{
// "127.0.0.2 host3 host2 host4",
// "127.0.0.1 host4",
// }
//
// Removing `host1:127.0.0.1` on the other hand, only remove the host if the
// IP-address matches:
//
// hosts = &[]string{
// "127.0.0.2 host3 host1 host2 host4",
// "127.0.0.1 host4",
// "127.0.0.3 host1",
// }
func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
var toRemove []hostMapping
if flags.Changed(flagHostRemove) {
extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll()
for _, entry := range extraHostsToRemove {
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
v := strings.SplitN(entry, ":", 2)
if len(v) > 1 {
toRemove = append(toRemove, hostMapping{IPAddr: v[1], Host: v[0]})
} else {
toRemove = append(toRemove, hostMapping{Host: v[0]})
}
}
}
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
var newHosts []string
for _, entry := range *hosts {
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
// Since this is in SwarmKit format, we need to find the key, which is canonical_hostname of:
// IP_address canonical_hostname [aliases...]
parts := strings.Fields(entry)
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
if len(parts) == 0 {
continue
}
ip := parts[0]
hostNames := parts[1:]
for _, rm := range toRemove {
if rm.IPAddr != "" && rm.IPAddr != ip {
continue
}
for i, h := range hostNames {
if h == rm.Host {
hostNames = append(hostNames[:i], hostNames[i+1:]...)
}
}
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
}
if len(hostNames) > 0 {
newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
}
}
Fix service update --host-rm not being granular enough Removing a host by `<host>:<ip>` should only remove occurences of the host with a matching IP-address, instead of removing all entries for that host. In addition, combining `--host-rm` and `--host-add` for the same host should result in the new host being added. This patch fixes the way the diff is calculated to allow combining removing/adding, and to support entries having both a canonical, and aliases. Aliases cannot be added by the CLI, but are supported in the Service spec, thus should be taken into account: Entries can be removed by either a specific `<host-name>:<ip-address>` mapping, or by `<host>` alone: - If both IP-address and host-name is provided, only remove the hostname from entries that match the given IP-address. - If only a host-name is provided, remove the hostname from any entry it is part of (either as _canonical_ host-name, or as _alias_). - If, after removing the host-name from an entry, no host-names remain in the entry, the entry itself should be removed. For example, the list of host-entries before processing could look like this: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1", } Removing `host1` removes every occurrence: hosts = &[]string{ "127.0.0.2 host3 host2 host4", "127.0.0.1 host4", } Whereas removing `host1:127.0.0.1` only remove the host if the IP-address matches: hosts = &[]string{ "127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1", } Before this patch: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.4 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [] After this patch is applied: $ docker service create --name my-service --host foo:127.0.0.1 --host foo:127.0.0.2 --host foo:127.0.0.3 nginx:alpine $ docker service update --host-rm foo:127.0.0.1 --host-add foo:127.0.0.5 my-service $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Hosts}}' my-service [127.0.0.2 foo 127.0.0.3 foo 127.0.0.4 foo] Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-10-28 13:34:58 -04:00
// Append new hosts (in SwarmKit format)
if flags.Changed(flagHostAdd) {
values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll())
newHosts = append(newHosts, values...)
}
*hosts = removeDuplicates(newHosts)
return nil
}
// updateLogDriver updates the log driver only if the log driver flag is set.
// All options will be replaced with those provided on the command line.
func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
if !flags.Changed(flagLogDriver) {
return nil
}
name, err := flags.GetString(flagLogDriver)
if err != nil {
return err
}
if name == "" {
return nil
}
taskTemplate.LogDriver = &swarm.Driver{
Name: name,
Options: opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
}
return nil
}
func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
return nil
}
if containerSpec.Healthcheck == nil {
containerSpec.Healthcheck = &container.HealthConfig{}
}
noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
if err != nil {
return err
}
if noHealthcheck {
if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) {
containerSpec.Healthcheck = &container.HealthConfig{
Test: []string{"NONE"},
}
return nil
}
return errors.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
}
if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
containerSpec.Healthcheck.Test = nil
}
if flags.Changed(flagHealthInterval) {
val := *flags.Lookup(flagHealthInterval).Value.(*opts.PositiveDurationOpt).Value()
containerSpec.Healthcheck.Interval = val
}
if flags.Changed(flagHealthTimeout) {
val := *flags.Lookup(flagHealthTimeout).Value.(*opts.PositiveDurationOpt).Value()
containerSpec.Healthcheck.Timeout = val
}
if flags.Changed(flagHealthStartPeriod) {
val := *flags.Lookup(flagHealthStartPeriod).Value.(*opts.PositiveDurationOpt).Value()
containerSpec.Healthcheck.StartPeriod = val
}
if flags.Changed(flagHealthRetries) {
containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
}
if flags.Changed(flagHealthCmd) {
cmd, _ := flags.GetString(flagHealthCmd)
if cmd != "" {
containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
} else {
containerSpec.Healthcheck.Test = nil
}
}
return nil
}
func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
// spec.TaskTemplate.Networks takes precedence over the deprecated
// spec.Networks field. If spec.Network is in use, we'll migrate those
// values to spec.TaskTemplate.Networks.
specNetworks := spec.TaskTemplate.Networks
if len(specNetworks) == 0 {
specNetworks = spec.Networks
}
spec.Networks = nil
toRemove := buildToRemoveSet(flags, flagNetworkRemove)
idsToRemove := make(map[string]struct{})
for networkIDOrName := range toRemove {
network, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
if err != nil {
return err
}
idsToRemove[network.ID] = struct{}{}
}
existingNetworks := make(map[string]struct{})
var newNetworks []swarm.NetworkAttachmentConfig
for _, network := range specNetworks {
if _, exists := idsToRemove[network.Target]; exists {
continue
}
newNetworks = append(newNetworks, network)
existingNetworks[network.Target] = struct{}{}
}
if flags.Changed(flagNetworkAdd) {
values := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)
networks := convertNetworks(*values)
for _, network := range networks {
nwID, err := resolveNetworkID(ctx, apiClient, network.Target)
if err != nil {
return err
}
if _, exists := existingNetworks[nwID]; exists {
return errors.Errorf("service is already attached to network %s", network.Target)
}
network.Target = nwID
newNetworks = append(newNetworks, network)
existingNetworks[network.Target] = struct{}{}
}
}
sort.Slice(newNetworks, func(i, j int) bool {
return newNetworks[i].Target < newNetworks[j].Target
})
spec.TaskTemplate.Networks = newNetworks
return nil
}
// updateCredSpecConfig updates the value of the credential spec Config field
// to the config ID if the credential spec has changed. it mutates the passed
// spec. it does not handle the case where the credential spec specifies a
// config that does not exist -- that case is handled as part of
// getUpdatedConfigs
func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
if flags.Changed(flagCredentialSpec) {
credSpecOpt := flags.Lookup(flagCredentialSpec)
// if the flag has changed, and the value is empty string, then we
// should remove any credential spec that might be present
if credSpecOpt.Value.String() == "" {
if containerSpec.Privileges != nil {
containerSpec.Privileges.CredentialSpec = nil
}
return
}
// otherwise, set the credential spec to be the parsed value
credSpec := credSpecOpt.Value.(*credentialSpecOpt).Value()
// if this is a Config credential spec, we we still need to replace the
// value of credSpec.Config with the config ID instead of Name.
if credSpec.Config != "" {
for _, config := range containerSpec.Configs {
// if the config name matches, then set the config ID. we do
// not need to worry about if this is a Runtime target or not.
// even if it is not a Runtime target, getUpdatedConfigs
// ensures that a Runtime target for this config exists, and
// the Name is unique so the ID is correct no matter the
// target.
if config.ConfigName == credSpec.Config {
credSpec.Config = config.ConfigID
break
}
}
}
if containerSpec.Privileges == nil {
containerSpec.Privileges = &swarm.Privileges{}
}
containerSpec.Privileges.CredentialSpec = credSpec
}
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
// updateCapabilities calculates the list of capabilities to "drop" and to "add"
// after applying the capabilities passed through `--cap-add` and `--cap-drop`
// to the existing list of added/dropped capabilities in the service spec.
//
// Adding capabilities takes precedence over "dropping" the same capability, so
// if both `--cap-add` and `--cap-drop` are specifying the same capability, the
// `--cap-drop` is ignored.
//
// Capabilities to "drop" are removed from the existing list of "added"
// capabilities, and vice-versa (capabilities to "add" are removed from the existing
// list of capabilities to "drop").
//
// Capabilities are normalized, sorted, and duplicates are removed to prevent
// service tasks from being updated if no changes are made. If a list has the "ALL"
// capability set, then any other capability is removed from that list.
//
// Adding/removing capabilities when updating a service is handled as a tri-state;
//
// - if the capability was previously "dropped", then remove it from "CapabilityDrop",
// but NOT added to "CapabilityAdd". However, if the capability was not yet in
// the service's "CapabilityDrop", then it's simply added to the service's "CapabilityAdd"
// - likewise, if the capability was previously "added", then it's removed from
// "CapabilityAdd", but NOT added to "CapabilityDrop". If the capability was
// not yet in the service's "CapabilityAdd", then simply add it to the service's
// "CapabilityDrop".
//
// In other words, given a service with the following:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | CAP_SOME_CAP | |
//
// When updating the service, and applying `--cap-add CAP_SOME_CAP`, the previously
// dropped capability is removed:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | | |
//
// After updating the service a second time, applying `--cap-add CAP_SOME_CAP`,
// capability is now added:
//
// | CapDrop | CapAdd |
// | -------------- | ------------- |
// | | CAP_SOME_CAP |
//
func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
var (
toAdd, toDrop map[string]bool
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
capDrop = opts.CapabilitiesMap(containerSpec.CapabilityDrop)
capAdd = opts.CapabilitiesMap(containerSpec.CapabilityAdd)
)
if flags.Changed(flagCapAdd) {
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
toAdd = opts.CapabilitiesMap(flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll())
if toAdd[opts.ResetCapabilities] {
capAdd = make(map[string]bool)
delete(toAdd, opts.ResetCapabilities)
}
}
if flags.Changed(flagCapDrop) {
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
toDrop = opts.CapabilitiesMap(flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll())
if toDrop[opts.ResetCapabilities] {
capDrop = make(map[string]bool)
delete(toDrop, opts.ResetCapabilities)
}
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
// First remove the capabilities to "drop" from the service's exiting
// list of capabilities to "add". If a capability is both added and dropped
// on update, then "adding" takes precedence.
//
// Dropping a capability when updating a service is considered a tri-state;
//
// - if the capability was previously "added", then remove it from
// "CapabilityAdd", and do NOT add it to "CapabilityDrop"
// - if the capability was not yet in the service's "CapabilityAdd",
// then simply add it to the service's "CapabilityDrop"
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
for c := range toDrop {
if !toAdd[c] {
if capAdd[c] {
delete(capAdd, c)
} else {
capDrop[c] = true
}
}
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
// And remove the capabilities we're "adding" from the service's existing
// list of capabilities to "drop".
//
// "Adding" capabilities takes precedence over "dropping" them, so if a
// capability is set both as "add" and "drop", remove the capability from
// the service's list of dropped capabilities (if present).
//
// Adding a capability when updating a service is considered a tri-state;
//
// - if the capability was previously "dropped", then remove it from
// "CapabilityDrop", and do NOT add it to "CapabilityAdd"
// - if the capability was not yet in the service's "CapabilityDrop",
// then simply add it to the service's "CapabilityAdd"
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
for c := range toAdd {
if capDrop[c] {
delete(capDrop, c)
} else {
capAdd[c] = true
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
}
// Now that the service's existing lists are updated, apply the new
// capabilities to add/drop to both lists. Sort the lists to prevent
// unneeded updates to service-tasks.
containerSpec.CapabilityDrop = capsList(capDrop)
containerSpec.CapabilityAdd = capsList(capAdd)
}
func capsList(caps map[string]bool) []string {
if caps[opts.AllCapabilities] {
return []string{opts.AllCapabilities}
}
var out []string
for c := range caps {
out = append(out, c)
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value When creating and updating services, we need to avoid unneeded service churn. The interaction of separate lists to "add" and "drop" capabilities, a special ("ALL") capability, as well as a "relaxed" format for accepted capabilities (case-insensitive, `CAP_` prefix optional) make this rather involved. This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as well as _updating_, with the following rules/assumptions applied: - both existing (service spec) and new (values passed through flags or in the compose-file) are normalized and de-duplicated before use. - the special "ALL" capability is equivalent to "all capabilities" and taken into account when normalizing capabilities. Combining "ALL" capabilities and other capabilities is therefore equivalent to just specifying "ALL". - adding capabilities takes precedence over dropping, which means that if a capability is both set to be "dropped" and to be "added", it is removed from the list to "drop". - the final lists should be sorted and normalized to reduce service churn - no validation of capabilities is handled by the client. Validation is delegated to the daemon/server. When deploying a service using a docker-compose file, the docker-compose file is *mostly* handled as being "declarative". However, many of the issues outlined above also apply to compose-files, so similar handling is applied to compose files as well to prevent service churn. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
sort.Strings(out)
return out
}