| package service |
| |
| import ( |
| "fmt" |
| "sort" |
| "strings" |
| "time" |
| |
| "golang.org/x/net/context" |
| |
| "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/cli" |
| "github.com/docker/docker/cli/command" |
| "github.com/docker/docker/client" |
| "github.com/docker/docker/opts" |
| runconfigopts "github.com/docker/docker/runconfig/opts" |
| "github.com/docker/go-connections/nat" |
| shlex "github.com/flynn-archive/go-shlex" |
| "github.com/spf13/cobra" |
| "github.com/spf13/pflag" |
| ) |
| |
| func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { |
| serviceOpts := 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(), args[0]) |
| }, |
| } |
| |
| flags := cmd.Flags() |
| flags.String("image", "", "Service image tag") |
| flags.String("args", "", "Service command args") |
| flags.Bool("rollback", false, "Rollback to previous specification") |
| flags.Bool("force", false, "Force update even if no changes require it") |
| addServiceFlags(cmd, serviceOpts) |
| |
| flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable") |
| flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container") |
| 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") |
| flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option") |
| flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain") |
| flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)") |
| flags.Var(&serviceOpts.labels, flagLabelAdd, "Add or update a service label") |
| flags.Var(&serviceOpts.containerLabels, flagContainerLabelAdd, "Add or update a container label") |
| flags.Var(&serviceOpts.env, flagEnvAdd, "Add or update an environment variable") |
| flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret") |
| flags.Var(&serviceOpts.secrets, flagSecretAdd, "Add or update a secret on a service") |
| flags.Var(&serviceOpts.mounts, flagMountAdd, "Add or update a mount on a service") |
| flags.Var(&serviceOpts.constraints, flagConstraintAdd, "Add or update a placement constraint") |
| flags.Var(&serviceOpts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port") |
| flags.Var(&serviceOpts.groups, flagGroupAdd, "Add an additional supplementary user group to the container") |
| flags.Var(&serviceOpts.dns, flagDNSAdd, "Add or update a custom DNS server") |
| flags.Var(&serviceOpts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option") |
| flags.Var(&serviceOpts.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain") |
| flags.Var(&serviceOpts.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)") |
| |
| return cmd |
| } |
| |
| func newListOptsVar() *opts.ListOpts { |
| return opts.NewListOptsRef(&[]string{}, nil) |
| } |
| |
| func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, serviceID string) error { |
| apiClient := dockerCli.Client() |
| ctx := context.Background() |
| updateOpts := types.ServiceUpdateOptions{} |
| |
| service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) |
| if err != nil { |
| return err |
| } |
| |
| rollback, err := flags.GetBool("rollback") |
| if err != nil { |
| return err |
| } |
| |
| spec := &service.Spec |
| if rollback { |
| spec = service.PreviousSpec |
| if spec == nil { |
| return fmt.Errorf("service does not have a previous specification to roll back to") |
| } |
| } |
| |
| err = updateService(flags, spec) |
| if err != nil { |
| return err |
| } |
| |
| if flags.Changed("image") { |
| if err := resolveServiceImageDigest(dockerCli, spec); err != nil { |
| return err |
| } |
| } |
| |
| updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets) |
| if err != nil { |
| return err |
| } |
| |
| spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets |
| |
| // 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 rollback { |
| 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) |
| return nil |
| } |
| |
| func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error { |
| 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.(*DurationOpt).Value() |
| *field = &val |
| } |
| } |
| |
| 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 |
| } |
| } |
| |
| cspec := &spec.TaskTemplate.ContainerSpec |
| task := &spec.TaskTemplate |
| |
| taskResources := func() *swarm.ResourceRequirements { |
| if task.Resources == nil { |
| task.Resources = &swarm.ResourceRequirements{} |
| } |
| return task.Resources |
| } |
| |
| updateLabels(flags, &spec.Labels) |
| updateContainerLabels(flags, &cspec.Labels) |
| updateString("image", &cspec.Image) |
| updateStringToSlice(flags, "args", &cspec.Args) |
| updateEnvironment(flags, &cspec.Env) |
| updateString(flagWorkdir, &cspec.Dir) |
| updateString(flagUser, &cspec.User) |
| updateString(flagHostname, &cspec.Hostname) |
| if err := updateMounts(flags, &cspec.Mounts); err != nil { |
| return err |
| } |
| |
| if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) { |
| taskResources().Limits = &swarm.Resources{} |
| updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs) |
| updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes) |
| } |
| if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) { |
| taskResources().Reservations = &swarm.Resources{} |
| updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs) |
| updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes) |
| } |
| |
| updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod) |
| |
| if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) { |
| if task.RestartPolicy == nil { |
| task.RestartPolicy = &swarm.RestartPolicy{} |
| } |
| |
| 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{} |
| } |
| updatePlacement(flags, task.Placement) |
| } |
| |
| if err := updateReplicas(flags, &spec.Mode); err != nil { |
| return err |
| } |
| |
| if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) { |
| if spec.UpdateConfig == nil { |
| spec.UpdateConfig = &swarm.UpdateConfig{} |
| } |
| 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) |
| } |
| |
| 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 |
| } |
| |
| return nil |
| } |
| |
| func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error { |
| if !flags.Changed(flag) { |
| return nil |
| } |
| |
| value, _ := flags.GetString(flag) |
| valueSlice, err := shlex.Split(value) |
| *field = valueSlice |
| return err |
| } |
| |
| func anyChanged(flags *pflag.FlagSet, fields ...string) bool { |
| for _, flag := range fields { |
| if flags.Changed(flag) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func updatePlacement(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 updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) { |
| if flags.Changed(flagContainerLabelAdd) { |
| if *field == nil { |
| *field = map[string]string{} |
| } |
| |
| values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll() |
| for key, value := range runconfigopts.ConvertKVStringsToMap(values) { |
| (*field)[key] = value |
| } |
| } |
| |
| if *field != nil && flags.Changed(flagContainerLabelRemove) { |
| toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll() |
| for _, label := range toRemove { |
| delete(*field, label) |
| } |
| } |
| } |
| |
| func updateLabels(flags *pflag.FlagSet, field *map[string]string) { |
| if flags.Changed(flagLabelAdd) { |
| if *field == nil { |
| *field = map[string]string{} |
| } |
| |
| values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() |
| for key, value := range runconfigopts.ConvertKVStringsToMap(values) { |
| (*field)[key] = value |
| } |
| } |
| |
| if *field != nil && flags.Changed(flagLabelRemove) { |
| toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll() |
| for _, label := range toRemove { |
| delete(*field, label) |
| } |
| } |
| } |
| |
| func updateEnvironment(flags *pflag.FlagSet, field *[]string) { |
| envSet := map[string]string{} |
| for _, v := range *field { |
| envSet[envKey(v)] = v |
| } |
| if flags.Changed(flagEnvAdd) { |
| 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) |
| } |
| |
| toRemove := buildToRemoveSet(flags, flagEnvRemove) |
| *field = removeItems(*field, toRemove, envKey) |
| } |
| |
| func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) { |
| if flags.Changed(flagSecretAdd) { |
| values := flags.Lookup(flagSecretAdd).Value.(*opts.SecretOpt).Value() |
| |
| addSecrets, err := ParseSecrets(apiClient, values) |
| if err != nil { |
| return nil, err |
| } |
| secrets = append(secrets, addSecrets...) |
| } |
| toRemove := buildToRemoveSet(flags, flagSecretRemove) |
| newSecrets := []*swarm.SecretReference{} |
| for _, secret := range secrets { |
| if _, exists := toRemove[secret.SecretName]; !exists { |
| newSecrets = append(newSecrets, secret) |
| } |
| } |
| |
| return newSecrets, nil |
| } |
| |
| func envKey(value string) string { |
| kv := strings.SplitN(value, "=", 2) |
| return kv[0] |
| } |
| |
| func itemKey(value string) string { |
| return value |
| } |
| |
| 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 |
| } |
| |
| type byMountSource []mounttypes.Mount |
| |
| func (m byMountSource) Len() int { return len(m) } |
| func (m byMountSource) Swap(i, j int) { m[i], m[j] = m[j], m[i] } |
| func (m byMountSource) Less(i, j int) bool { |
| a, b := m[i], m[j] |
| |
| if a.Source == b.Source { |
| return a.Target < b.Target |
| } |
| |
| return a.Source < b.Source |
| } |
| |
| 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 fmt.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.Sort(byMountSource(newMounts)) |
| *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 |
| } |
| |
| type byPortConfig []swarm.PortConfig |
| |
| func (r byPortConfig) Len() int { return len(r) } |
| func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
| func (r byPortConfig) Less(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(&r[i]) < portConfigToString(&r[j]) |
| } |
| |
| 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) |
| } |
| |
| // FIXME(vdemeester) port to opts.PortOpt |
| // This validation is only used for `--publish-rm`. |
| // The `--publish-rm` takes: |
| // <TargetPort>[/<Protocol>] (e.g., 80, 80/tcp, 53/udp) |
| func validatePublishRemove(val string) (string, error) { |
| proto, port := nat.SplitProtoPort(val) |
| if proto != "tcp" && proto != "udp" { |
| return "", fmt.Errorf("invalid protocol '%s' for %s", proto, val) |
| } |
| if strings.Contains(port, ":") { |
| return "", fmt.Errorf("invalid port format: '%s', should be <TargetPort>[/<Protocol>] (e.g., 80, 80/tcp, 53/udp)", port) |
| } |
| if _, err := nat.ParsePort(port); err != nil { |
| return "", err |
| } |
| return val, nil |
| } |
| |
| 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 { |
| 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 { |
| if v, ok := portSet[portConfigToString(&port)]; ok { |
| if v != port { |
| fmt.Println("v", v) |
| return fmt.Errorf("conflicting port mapping between %v:%v/%s and %v:%v/%s", port.PublishedPort, port.TargetPort, port.Protocol, v.PublishedPort, v.TargetPort, v.Protocol) |
| } |
| continue |
| } |
| //portSet[portConfigToString(&port)] = port |
| newPorts = append(newPorts, port) |
| } |
| } |
| |
| // Sort the PortConfig to avoid unnecessary updates |
| sort.Sort(byPortConfig(newPorts)) |
| *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 equalPort(targetPort nat.Port, port swarm.PortConfig) bool { |
| return (string(port.Protocol) == targetPort.Proto() && |
| port.TargetPort == uint32(targetPort.Int())) |
| } |
| |
| func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error { |
| if !flags.Changed(flagReplicas) { |
| return nil |
| } |
| |
| if serviceMode == nil || serviceMode.Replicated == nil { |
| return fmt.Errorf("replicas can only be used with replicated mode") |
| } |
| serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value() |
| return nil |
| } |
| |
| func updateHosts(flags *pflag.FlagSet, hosts *[]string) error { |
| // Combine existing Hosts (in swarmkit format) with the host to add (convert to swarmkit format) |
| if flags.Changed(flagHostAdd) { |
| values := convertExtraHostsToSwarmHosts(flags.Lookup(flagHostAdd).Value.(*opts.ListOpts).GetAll()) |
| *hosts = append(*hosts, values...) |
| } |
| // Remove duplicate |
| *hosts = removeDuplicates(*hosts) |
| |
| keysToRemove := make(map[string]struct{}) |
| if flags.Changed(flagHostRemove) { |
| var empty struct{} |
| extraHostsToRemove := flags.Lookup(flagHostRemove).Value.(*opts.ListOpts).GetAll() |
| for _, entry := range extraHostsToRemove { |
| key := strings.SplitN(entry, ":", 2)[0] |
| keysToRemove[key] = empty |
| } |
| } |
| |
| newHosts := []string{} |
| for _, entry := range *hosts { |
| // 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) |
| if len(parts) > 1 { |
| key := parts[1] |
| if _, exists := keysToRemove[key]; !exists { |
| newHosts = append(newHosts, entry) |
| } |
| } else { |
| newHosts = append(newHosts, entry) |
| } |
| } |
| |
| // Sort so that result is predictable. |
| sort.Strings(newHosts) |
| |
| *hosts = 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: runconfigopts.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) { |
| 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) { |
| containerSpec.Healthcheck = &container.HealthConfig{ |
| Test: []string{"NONE"}, |
| } |
| return nil |
| } |
| return fmt.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.(*PositiveDurationOpt).Value() |
| containerSpec.Healthcheck.Interval = val |
| } |
| if flags.Changed(flagHealthTimeout) { |
| val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value() |
| containerSpec.Healthcheck.Timeout = 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 |
| } |