diff --git a/api/server/router/swarm/backend.go b/api/server/router/swarm/backend.go index 28b9a98018..3a5da97d2c 100644 --- a/api/server/router/swarm/backend.go +++ b/api/server/router/swarm/backend.go @@ -17,7 +17,7 @@ type Backend interface { GetUnlockKey() (string, error) UnlockSwarm(req types.UnlockRequest) error GetServices(basictypes.ServiceListOptions) ([]types.Service, error) - GetService(string) (types.Service, error) + GetService(idOrName string, insertDefaults bool) (types.Service, error) CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error) UpdateService(string, uint64, types.ServiceSpec, basictypes.ServiceUpdateOptions) (*basictypes.ServiceUpdateResponse, error) RemoveService(string) error @@ -30,7 +30,7 @@ type Backend interface { GetTask(string) (types.Task, error) GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error) CreateSecret(s types.SecretSpec) (string, error) - RemoveSecret(id string) error + RemoveSecret(idOrName string) error GetSecret(id string) (types.Secret, error) - UpdateSecret(id string, version uint64, spec types.SecretSpec) error + UpdateSecret(idOrName string, version uint64, spec types.SecretSpec) error } diff --git a/api/server/router/swarm/cluster_routes.go b/api/server/router/swarm/cluster_routes.go index dfae13f1dd..4c60b6b6ee 100644 --- a/api/server/router/swarm/cluster_routes.go +++ b/api/server/router/swarm/cluster_routes.go @@ -151,7 +151,17 @@ func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r } func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - service, err := sr.backend.GetService(vars["id"]) + var insertDefaults bool + if value := r.URL.Query().Get("insertDefaults"); value != "" { + var err error + insertDefaults, err = strconv.ParseBool(value) + if err != nil { + err := fmt.Errorf("invalid value for insertDefaults: %s", value) + return errors.NewBadRequestError(err) + } + } + + service, err := sr.backend.GetService(vars["id"], insertDefaults) if err != nil { logrus.Errorf("Error getting service %s: %v", vars["id"], err) return err diff --git a/api/server/router/swarm/helpers.go b/api/server/router/swarm/helpers.go index af745b84c3..ea692ea368 100644 --- a/api/server/router/swarm/helpers.go +++ b/api/server/router/swarm/helpers.go @@ -39,7 +39,7 @@ func (sr *swarmRouter) swarmLogs(ctx context.Context, w http.ResponseWriter, r * // checking for whether logs are TTY involves iterating over every service // and task. idk if there is a better way for _, service := range selector.Services { - s, err := sr.backend.GetService(service) + s, err := sr.backend.GetService(service, false) if err != nil { // maybe should return some context with this error? return err diff --git a/api/swagger.yaml b/api/swagger.yaml index f6e6fc628f..36927b5a04 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -7584,6 +7584,11 @@ paths: description: "ID or name of service." required: true type: "string" + - name: "insertDefaults" + in: "query" + description: "Fill empty fields with default values." + type: "boolean" + default: false tags: ["Service"] delete: summary: "Delete a service" diff --git a/api/types/client.go b/api/types/client.go index 2bf6ad01a9..d7bc55011c 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -316,12 +316,18 @@ type ServiceUpdateOptions struct { Rollback string } -// ServiceListOptions holds parameters to list services with. +// ServiceListOptions holds parameters to list services with. type ServiceListOptions struct { Filters filters.Args } -// TaskListOptions holds parameters to list tasks with. +// ServiceInspectOptions holds parameters related to the "service inspect" +// operation. +type ServiceInspectOptions struct { + InsertDefaults bool +} + +// TaskListOptions holds parameters to list tasks with. type TaskListOptions struct { Filters filters.Args } diff --git a/cli/command/idresolver/client_test.go b/cli/command/idresolver/client_test.go index 8c02d7ebcf..f84683b907 100644 --- a/cli/command/idresolver/client_test.go +++ b/cli/command/idresolver/client_test.go @@ -1,6 +1,7 @@ package idresolver import ( + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "golang.org/x/net/context" @@ -19,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (s return swarm.Node{}, []byte{}, nil } -func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { +func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { if cli.serviceInspectFunc != nil { return cli.serviceInspectFunc(serviceID) } diff --git a/cli/command/idresolver/idresolver.go b/cli/command/idresolver/idresolver.go index 25c51a27eb..6088b64b59 100644 --- a/cli/command/idresolver/idresolver.go +++ b/cli/command/idresolver/idresolver.go @@ -3,6 +3,7 @@ package idresolver import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "github.com/pkg/errors" @@ -39,7 +40,7 @@ func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, } return id, nil case swarm.Service: - service, _, err := r.client.ServiceInspectWithRaw(ctx, id) + service, _, err := r.client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{}) if err != nil { return id, nil } diff --git a/cli/command/service/create.go b/cli/command/service/create.go index 0e77f73d32..bb2a1fe3b7 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -30,7 +30,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)") flags.StringVar(&opts.name, flagName, "", "Service name") - addServiceFlags(flags, opts) + addServiceFlags(flags, opts, buildServiceDefaultFlagMapping()) flags.VarP(&opts.labels, flagLabel, "l", "Service labels") flags.Var(&opts.containerLabels, flagContainerLabel, "Container labels") @@ -65,7 +65,7 @@ func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service ctx := context.Background() - service, err := opts.ToService(ctx, apiClient) + service, err := opts.ToService(ctx, apiClient, flags) if err != nil { return err } diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 8a8b51cd0e..fae24eeaf1 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -5,6 +5,7 @@ import ( "golang.org/x/net/context" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/formatter" @@ -51,7 +52,8 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { } getRef := func(ref string) (interface{}, []byte, error) { - service, _, err := client.ServiceInspectWithRaw(ctx, ref) + // Service inspect shows defaults values in empty fields. + service, _, err := client.ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true}) if err == nil || !apiclient.IsErrServiceNotFound(err) { return service, nil, err } diff --git a/cli/command/service/logs.go b/cli/command/service/logs.go index 30ed504cfa..2440c1680e 100644 --- a/cli/command/service/logs.go +++ b/cli/command/service/logs.go @@ -86,7 +86,7 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error { tty bool ) - service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target) + service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target, types.ServiceInspectOptions{}) if err != nil { // if it's any error other than service not found, it's Real if !client.IsErrServiceNotFound(err) { diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index 066436838c..4211c5bf8c 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -12,7 +12,10 @@ import ( "github.com/docker/docker/client" "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/api/defaults" shlex "github.com/flynn-archive/go-shlex" + gogotypes "github.com/gogo/protobuf/types" "github.com/pkg/errors" "github.com/spf13/pflag" "golang.org/x/net/context" @@ -177,6 +180,9 @@ func (s *ShlexOpt) Type() string { } func (s *ShlexOpt) String() string { + if len(*s) == 0 { + return "" + } return fmt.Sprint(*s) } @@ -194,17 +200,77 @@ type updateOptions struct { order string } -func (opts updateOptions) config() *swarm.UpdateConfig { +func updateConfigFromDefaults(defaultUpdateConfig *api.UpdateConfig) *swarm.UpdateConfig { + defaultFailureAction := strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaultUpdateConfig.FailureAction)]) + defaultMonitor, _ := gogotypes.DurationFromProto(defaultUpdateConfig.Monitor) return &swarm.UpdateConfig{ - Parallelism: opts.parallelism, - Delay: opts.delay, - Monitor: opts.monitor, - FailureAction: opts.onFailure, - MaxFailureRatio: opts.maxFailureRatio.Value(), - Order: opts.order, + Parallelism: defaultUpdateConfig.Parallelism, + Delay: defaultUpdateConfig.Delay, + Monitor: defaultMonitor, + FailureAction: defaultFailureAction, + MaxFailureRatio: defaultUpdateConfig.MaxFailureRatio, + Order: defaultOrder(defaultUpdateConfig.Order), } } +func (opts updateOptions) updateConfig(flags *pflag.FlagSet) *swarm.UpdateConfig { + if !anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) { + return nil + } + + updateConfig := updateConfigFromDefaults(defaults.Service.Update) + + if flags.Changed(flagUpdateParallelism) { + updateConfig.Parallelism = opts.parallelism + } + if flags.Changed(flagUpdateDelay) { + updateConfig.Delay = opts.delay + } + if flags.Changed(flagUpdateMonitor) { + updateConfig.Monitor = opts.monitor + } + if flags.Changed(flagUpdateFailureAction) { + updateConfig.FailureAction = opts.onFailure + } + if flags.Changed(flagUpdateMaxFailureRatio) { + updateConfig.MaxFailureRatio = opts.maxFailureRatio.Value() + } + if flags.Changed(flagUpdateOrder) { + updateConfig.Order = opts.order + } + + return updateConfig +} + +func (opts updateOptions) rollbackConfig(flags *pflag.FlagSet) *swarm.UpdateConfig { + if !anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio) { + return nil + } + + updateConfig := updateConfigFromDefaults(defaults.Service.Rollback) + + if flags.Changed(flagRollbackParallelism) { + updateConfig.Parallelism = opts.parallelism + } + if flags.Changed(flagRollbackDelay) { + updateConfig.Delay = opts.delay + } + if flags.Changed(flagRollbackMonitor) { + updateConfig.Monitor = opts.monitor + } + if flags.Changed(flagRollbackFailureAction) { + updateConfig.FailureAction = opts.onFailure + } + if flags.Changed(flagRollbackMaxFailureRatio) { + updateConfig.MaxFailureRatio = opts.maxFailureRatio.Value() + } + if flags.Changed(flagRollbackOrder) { + updateConfig.Order = opts.order + } + + return updateConfig +} + type resourceOptions struct { limitCPU opts.NanoCPUs limitMemBytes opts.MemBytes @@ -232,13 +298,70 @@ type restartPolicyOptions struct { window DurationOpt } -func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy { - return &swarm.RestartPolicy{ - Condition: swarm.RestartPolicyCondition(r.condition), - Delay: r.delay.Value(), - MaxAttempts: r.maxAttempts.Value(), - Window: r.window.Value(), +func defaultRestartPolicy() *swarm.RestartPolicy { + defaultMaxAttempts := defaults.Service.Task.Restart.MaxAttempts + rp := &swarm.RestartPolicy{ + MaxAttempts: &defaultMaxAttempts, } + + if defaults.Service.Task.Restart.Delay != nil { + defaultRestartDelay, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Delay) + rp.Delay = &defaultRestartDelay + } + if defaults.Service.Task.Restart.Window != nil { + defaultRestartWindow, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Window) + rp.Window = &defaultRestartWindow + } + rp.Condition = defaultRestartCondition() + + return rp +} + +func defaultRestartCondition() swarm.RestartPolicyCondition { + switch defaults.Service.Task.Restart.Condition { + case api.RestartOnNone: + return "none" + case api.RestartOnFailure: + return "on-failure" + case api.RestartOnAny: + return "any" + default: + return "" + } +} + +func defaultOrder(order api.UpdateConfig_UpdateOrder) string { + switch order { + case api.UpdateConfig_STOP_FIRST: + return "stop-first" + case api.UpdateConfig_START_FIRST: + return "start-first" + default: + return "" + } +} + +func (r *restartPolicyOptions) ToRestartPolicy(flags *pflag.FlagSet) *swarm.RestartPolicy { + if !anyChanged(flags, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow, flagRestartCondition) { + return nil + } + + restartPolicy := defaultRestartPolicy() + + if flags.Changed(flagRestartDelay) { + restartPolicy.Delay = r.delay.Value() + } + if flags.Changed(flagRestartCondition) { + restartPolicy.Condition = swarm.RestartPolicyCondition(r.condition) + } + if flags.Changed(flagRestartMaxAttempts) { + restartPolicy.MaxAttempts = r.maxAttempts.Value() + } + if flags.Changed(flagRestartWindow) { + restartPolicy.Window = r.window.Value() + } + + return restartPolicy } type credentialSpecOpt struct { @@ -463,7 +586,14 @@ func (opts *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) { return serviceMode, nil } -func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIClient) (swarm.ServiceSpec, error) { +func (opts *serviceOptions) ToStopGracePeriod(flags *pflag.FlagSet) *time.Duration { + if flags.Changed(flagStopGracePeriod) { + return opts.stopGrace.Value() + } + return nil +} + +func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIClient, flags *pflag.FlagSet) (swarm.ServiceSpec, error) { var service swarm.ServiceSpec envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll()) @@ -526,13 +656,13 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIC Options: opts.dnsOption.GetAll(), }, Hosts: convertExtraHostsToSwarmHosts(opts.hosts.GetAll()), - StopGracePeriod: opts.stopGrace.Value(), + StopGracePeriod: opts.ToStopGracePeriod(flags), Secrets: nil, Healthcheck: healthConfig, }, Networks: networks, Resources: opts.resources.ToResourceRequirements(), - RestartPolicy: opts.restartPolicy.ToRestartPolicy(), + RestartPolicy: opts.restartPolicy.ToRestartPolicy(flags), Placement: &swarm.Placement{ Constraints: opts.constraints.GetAll(), Preferences: opts.placementPrefs.prefs, @@ -540,8 +670,8 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIC LogDriver: opts.logDriver.toLogDriver(), }, Mode: serviceMode, - UpdateConfig: opts.update.config(), - RollbackConfig: opts.rollback.config(), + UpdateConfig: opts.update.updateConfig(flags), + RollbackConfig: opts.update.rollbackConfig(flags), EndpointSpec: opts.endpoint.ToEndpointSpec(), } @@ -554,9 +684,67 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIC return service, nil } +type flagDefaults map[string]interface{} + +func (fd flagDefaults) getUint64(flagName string) uint64 { + if val, ok := fd[flagName].(uint64); ok { + return val + } + return 0 +} + +func (fd flagDefaults) getString(flagName string) string { + if val, ok := fd[flagName].(string); ok { + return val + } + return "" +} + +func buildServiceDefaultFlagMapping() flagDefaults { + defaultFlagValues := make(map[string]interface{}) + + defaultFlagValues[flagStopGracePeriod], _ = gogotypes.DurationFromProto(defaults.Service.Task.GetContainer().StopGracePeriod) + defaultFlagValues[flagRestartCondition] = `"` + defaultRestartCondition() + `"` + defaultFlagValues[flagRestartDelay], _ = gogotypes.DurationFromProto(defaults.Service.Task.Restart.Delay) + + if defaults.Service.Task.Restart.MaxAttempts != 0 { + defaultFlagValues[flagRestartMaxAttempts] = defaults.Service.Task.Restart.MaxAttempts + } + + defaultRestartWindow, _ := gogotypes.DurationFromProto(defaults.Service.Task.Restart.Window) + if defaultRestartWindow != 0 { + defaultFlagValues[flagRestartWindow] = defaultRestartWindow + } + + defaultFlagValues[flagUpdateParallelism] = defaults.Service.Update.Parallelism + defaultFlagValues[flagUpdateDelay] = defaults.Service.Update.Delay + defaultFlagValues[flagUpdateMonitor], _ = gogotypes.DurationFromProto(defaults.Service.Update.Monitor) + defaultFlagValues[flagUpdateFailureAction] = `"` + strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaults.Service.Update.FailureAction)]) + `"` + defaultFlagValues[flagUpdateMaxFailureRatio] = defaults.Service.Update.MaxFailureRatio + defaultFlagValues[flagUpdateOrder] = `"` + defaultOrder(defaults.Service.Update.Order) + `"` + + defaultFlagValues[flagRollbackParallelism] = defaults.Service.Rollback.Parallelism + defaultFlagValues[flagRollbackDelay] = defaults.Service.Rollback.Delay + defaultFlagValues[flagRollbackMonitor], _ = gogotypes.DurationFromProto(defaults.Service.Rollback.Monitor) + defaultFlagValues[flagRollbackFailureAction] = `"` + strings.ToLower(api.UpdateConfig_FailureAction_name[int32(defaults.Service.Rollback.FailureAction)]) + `"` + defaultFlagValues[flagRollbackMaxFailureRatio] = defaults.Service.Rollback.MaxFailureRatio + defaultFlagValues[flagRollbackOrder] = `"` + defaultOrder(defaults.Service.Rollback.Order) + `"` + + defaultFlagValues[flagEndpointMode] = "vip" + + return defaultFlagValues +} + // addServiceFlags adds all flags that are common to both `create` and `update`. // Any flags that are not common are added separately in the individual command -func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) { +func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValues flagDefaults) { + flagDesc := func(flagName string, desc string) string { + if defaultValue, ok := defaultFlagValues[flagName]; ok { + return fmt.Sprintf("%s (default %v)", desc, defaultValue) + } + return desc + } + flags.BoolVarP(&opts.detach, "detach", "d", true, "Exit immediately instead of waiting for the service to converge") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output") @@ -572,39 +760,40 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) { flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs") flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory") - flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)") + flags.Var(&opts.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)")) flags.Var(&opts.replicas, flagReplicas, "Number of tasks") - flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", `Restart when condition is met ("none"|"on-failure"|"any")`) - flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)") - flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up") - flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)") + flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", flagDesc(flagRestartCondition, `Restart when condition is met ("none"|"on-failure"|"any")`)) + flags.Var(&opts.restartPolicy.delay, flagRestartDelay, flagDesc(flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)")) + flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, flagDesc(flagRestartMaxAttempts, "Maximum number of restarts before giving up")) - flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)") - flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates (ns|us|ms|s|m|h) (default 0s)") - flags.DurationVar(&opts.update.monitor, flagUpdateMonitor, time.Duration(0), "Duration after each task update to monitor for failure (ns|us|ms|s|m|h)") + flags.Var(&opts.restartPolicy.window, flagRestartWindow, flagDesc(flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)")) + + flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, defaultFlagValues.getUint64(flagUpdateParallelism), "Maximum number of tasks updated simultaneously (0 to update all at once)") + flags.DurationVar(&opts.update.delay, flagUpdateDelay, 0, flagDesc(flagUpdateDelay, "Delay between updates (ns|us|ms|s|m|h)")) + flags.DurationVar(&opts.update.monitor, flagUpdateMonitor, 0, flagDesc(flagUpdateMonitor, "Duration after each task update to monitor for failure (ns|us|ms|s|m|h)")) flags.SetAnnotation(flagUpdateMonitor, "version", []string{"1.25"}) - flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", `Action on update failure ("pause"|"continue"|"rollback")`) - flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update") + flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "", flagDesc(flagUpdateFailureAction, `Action on update failure ("pause"|"continue"|"rollback")`)) + flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, flagDesc(flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update")) flags.SetAnnotation(flagUpdateMaxFailureRatio, "version", []string{"1.25"}) - flags.StringVar(&opts.update.order, flagUpdateOrder, "stop-first", `Update order ("start-first"|"stop-first")`) + flags.StringVar(&opts.update.order, flagUpdateOrder, "", flagDesc(flagUpdateOrder, `Update order ("start-first"|"stop-first")`)) flags.SetAnnotation(flagUpdateOrder, "version", []string{"1.29"}) - flags.Uint64Var(&opts.rollback.parallelism, flagRollbackParallelism, 1, "Maximum number of tasks rolled back simultaneously (0 to roll back all at once)") + flags.Uint64Var(&opts.rollback.parallelism, flagRollbackParallelism, defaultFlagValues.getUint64(flagRollbackParallelism), "Maximum number of tasks rolled back simultaneously (0 to roll back all at once)") flags.SetAnnotation(flagRollbackParallelism, "version", []string{"1.28"}) - flags.DurationVar(&opts.rollback.delay, flagRollbackDelay, time.Duration(0), "Delay between task rollbacks (ns|us|ms|s|m|h) (default 0s)") + flags.DurationVar(&opts.rollback.delay, flagRollbackDelay, 0, flagDesc(flagRollbackDelay, "Delay between task rollbacks (ns|us|ms|s|m|h)")) flags.SetAnnotation(flagRollbackDelay, "version", []string{"1.28"}) - flags.DurationVar(&opts.rollback.monitor, flagRollbackMonitor, time.Duration(0), "Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h) (default 0s)") + flags.DurationVar(&opts.rollback.monitor, flagRollbackMonitor, 0, flagDesc(flagRollbackMonitor, "Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h)")) flags.SetAnnotation(flagRollbackMonitor, "version", []string{"1.28"}) - flags.StringVar(&opts.rollback.onFailure, flagRollbackFailureAction, "pause", `Action on rollback failure ("pause"|"continue")`) + flags.StringVar(&opts.rollback.onFailure, flagRollbackFailureAction, "", flagDesc(flagRollbackFailureAction, `Action on rollback failure ("pause"|"continue")`)) flags.SetAnnotation(flagRollbackFailureAction, "version", []string{"1.28"}) - flags.Var(&opts.rollback.maxFailureRatio, flagRollbackMaxFailureRatio, "Failure rate to tolerate during a rollback") + flags.Var(&opts.rollback.maxFailureRatio, flagRollbackMaxFailureRatio, flagDesc(flagRollbackMaxFailureRatio, "Failure rate to tolerate during a rollback")) flags.SetAnnotation(flagRollbackMaxFailureRatio, "version", []string{"1.28"}) - flags.StringVar(&opts.rollback.order, flagRollbackOrder, "stop-first", `Rollback order ("start-first"|"stop-first")`) + flags.StringVar(&opts.rollback.order, flagRollbackOrder, "", flagDesc(flagRollbackOrder, `Rollback order ("start-first"|"stop-first")`)) flags.SetAnnotation(flagRollbackOrder, "version", []string{"1.29"}) - flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "vip", "Endpoint mode (vip or dnsrr)") + flags.StringVar(&opts.endpoint.mode, flagEndpointMode, defaultFlagValues.getString(flagEndpointMode), "Endpoint mode (vip or dnsrr)") flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents") diff --git a/cli/command/service/progress/progress.go b/cli/command/service/progress/progress.go index ccc7e60cfc..bfeaa314a4 100644 --- a/cli/command/service/progress/progress.go +++ b/cli/command/service/progress/progress.go @@ -85,7 +85,7 @@ func ServiceProgress(ctx context.Context, client client.APIClient, serviceID str ) for { - service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) + service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) if err != nil { return err } diff --git a/cli/command/service/scale.go b/cli/command/service/scale.go index ed76c862fe..98163c87c9 100644 --- a/cli/command/service/scale.go +++ b/cli/command/service/scale.go @@ -71,7 +71,7 @@ func runServiceScale(dockerCli *command.DockerCli, serviceID string, scale uint6 client := dockerCli.Client() ctx := context.Background() - service, _, err := client.ServiceInspectWithRaw(ctx, serviceID) + service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) if err != nil { return err } diff --git a/cli/command/service/update.go b/cli/command/service/update.go index b59f163829..233da68eee 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/go-connections/nat" + "github.com/docker/swarmkit/api/defaults" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -42,7 +43,7 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { flags.SetAnnotation("rollback", "version", []string{"1.25"}) flags.Bool("force", false, "Force update even if no changes require it") flags.SetAnnotation("force", "version", []string{"1.25"}) - addServiceFlags(flags, serviceOpts) + addServiceFlags(flags, serviceOpts, nil) flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable") flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container") @@ -101,7 +102,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service apiClient := dockerCli.Client() ctx := context.Background() - service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) + service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) if err != nil { return err } @@ -294,9 +295,8 @@ func updateService(ctx context.Context, apiClient client.APIClient, flags *pflag if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) { if task.RestartPolicy == nil { - task.RestartPolicy = &swarm.RestartPolicy{} + task.RestartPolicy = defaultRestartPolicy() } - if flags.Changed(flagRestartCondition) { value, _ := flags.GetString(flagRestartCondition) task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value) @@ -332,7 +332,7 @@ func updateService(ctx context.Context, apiClient client.APIClient, flags *pflag if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) { if spec.UpdateConfig == nil { - spec.UpdateConfig = &swarm.UpdateConfig{} + spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update) } updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism) updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay) @@ -344,7 +344,7 @@ func updateService(ctx context.Context, apiClient client.APIClient, flags *pflag if anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio, flagRollbackOrder) { if spec.RollbackConfig == nil { - spec.RollbackConfig = &swarm.UpdateConfig{} + spec.RollbackConfig = updateConfigFromDefaults(defaults.Service.Rollback) } updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism) updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index 361902a9e7..ad23d35a09 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/inspect" @@ -79,7 +80,8 @@ func inspectNode(ctx context.Context, dockerCli *command.DockerCli) inspect.GetR func inspectService(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc { return func(ref string) (interface{}, []byte, error) { - return dockerCli.Client().ServiceInspectWithRaw(ctx, ref) + // Service inspect shows defaults values in empty fields. + return dockerCli.Client().ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true}) } } diff --git a/client/interface.go b/client/interface.go index 6f8c094b31..8dbe4300dc 100644 --- a/client/interface.go +++ b/client/interface.go @@ -123,7 +123,7 @@ type PluginAPIClient interface { // ServiceAPIClient defines API client methods for the services type ServiceAPIClient interface { ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) - ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) + ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) ServiceRemove(ctx context.Context, serviceID string) error ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) diff --git a/client/service_inspect.go b/client/service_inspect.go index ca71cbde1a..d7e051e3a4 100644 --- a/client/service_inspect.go +++ b/client/service_inspect.go @@ -3,16 +3,21 @@ package client import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" + "net/url" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "golang.org/x/net/context" ) // ServiceInspectWithRaw returns the service information and the raw data. -func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { - serverResp, err := cli.get(ctx, "/services/"+serviceID, nil, nil) +func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) { + query := url.Values{} + query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults)) + serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil) if err != nil { if serverResp.statusCode == http.StatusNotFound { return swarm.Service{}, nil, serviceNotFoundError{serviceID} diff --git a/client/service_inspect_test.go b/client/service_inspect_test.go index 0346847317..d53f583e90 100644 --- a/client/service_inspect_test.go +++ b/client/service_inspect_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "golang.org/x/net/context" ) @@ -18,7 +19,7 @@ func TestServiceInspectError(t *testing.T) { client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), } - _, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing") + _, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing", types.ServiceInspectOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } @@ -29,7 +30,7 @@ func TestServiceInspectServiceNotFound(t *testing.T) { client: newMockClient(errorMock(http.StatusNotFound, "Server error")), } - _, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown") + _, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown", types.ServiceInspectOptions{}) if err == nil || !IsErrServiceNotFound(err) { t.Fatalf("expected a serviceNotFoundError error, got %v", err) } @@ -55,7 +56,7 @@ func TestServiceInspect(t *testing.T) { }), } - serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id") + serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id", types.ServiceInspectOptions{}) if err != nil { t.Fatal(err) } diff --git a/daemon/cluster/helpers.go b/daemon/cluster/helpers.go index 6523a80e1c..98c7cc5472 100644 --- a/daemon/cluster/helpers.go +++ b/daemon/cluster/helpers.go @@ -58,9 +58,9 @@ func getNode(ctx context.Context, c swarmapi.ControlClient, input string) (*swar return rl.Nodes[0], nil } -func getService(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Service, error) { +func getService(ctx context.Context, c swarmapi.ControlClient, input string, insertDefaults bool) (*swarmapi.Service, error) { // GetService to match via full ID. - if rg, err := c.GetService(ctx, &swarmapi.GetServiceRequest{ServiceID: input}); err == nil { + if rg, err := c.GetService(ctx, &swarmapi.GetServiceRequest{ServiceID: input, InsertDefaults: insertDefaults}); err == nil { return rg.Service, nil } @@ -91,7 +91,15 @@ func getService(ctx context.Context, c swarmapi.ControlClient, input string) (*s return nil, fmt.Errorf("service %s is ambiguous (%d matches found)", input, l) } - return rl.Services[0], nil + if !insertDefaults { + return rl.Services[0], nil + } + + rg, err := c.GetService(ctx, &swarmapi.GetServiceRequest{ServiceID: rl.Services[0].ID, InsertDefaults: true}) + if err == nil { + return rg.Service, nil + } + return nil, err } func getTask(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Task, error) { diff --git a/daemon/cluster/services.go b/daemon/cluster/services.go index 8fd730eee7..8d5d4a5edd 100644 --- a/daemon/cluster/services.go +++ b/daemon/cluster/services.go @@ -87,10 +87,10 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv } // GetService returns a service based on an ID or name. -func (c *Cluster) GetService(input string) (types.Service, error) { +func (c *Cluster) GetService(input string, insertDefaults bool) (types.Service, error) { var service *swarmapi.Service if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error { - s, err := getService(ctx, state.controlClient, input) + s, err := getService(ctx, state.controlClient, input, insertDefaults) if err != nil { return err } @@ -187,7 +187,7 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ return apierrors.NewBadRequestError(err) } - currentService, err := getService(ctx, state.controlClient, serviceIDOrName) + currentService, err := getService(ctx, state.controlClient, serviceIDOrName, false) if err != nil { return err } @@ -289,7 +289,7 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ // RemoveService removes a service from a managed swarm cluster. func (c *Cluster) RemoveService(input string) error { return c.lockedManagerAction(func(ctx context.Context, state nodeState) error { - service, err := getService(ctx, state.controlClient, input) + service, err := getService(ctx, state.controlClient, input, false) if err != nil { return err } @@ -442,7 +442,7 @@ func convertSelector(ctx context.Context, cc swarmapi.ControlClient, selector *b // don't rely on swarmkit to resolve IDs, do it ourselves swarmSelector := &swarmapi.LogSelector{} for _, s := range selector.Services { - service, err := getService(ctx, cc, s) + service, err := getService(ctx, cc, s, false) if err != nil { return nil, err } diff --git a/daemon/cluster/tasks.go b/daemon/cluster/tasks.go index 001a345a68..6a6c59ffe5 100644 --- a/daemon/cluster/tasks.go +++ b/daemon/cluster/tasks.go @@ -23,7 +23,7 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro if filter.Include("service") { serviceFilters := filter.Get("service") for _, serviceFilter := range serviceFilters { - service, err := c.GetService(serviceFilter) + service, err := c.GetService(serviceFilter, false) if err != nil { return err } diff --git a/docs/reference/commandline/service_create.md b/docs/reference/commandline/service_create.md index 9490f1bb9f..082dffb827 100644 --- a/docs/reference/commandline/service_create.md +++ b/docs/reference/commandline/service_create.md @@ -21,61 +21,60 @@ Usage: docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] Create a new service Options: - --constraint list Placement constraints (default []) - --container-label list Container labels (default []) - -d, --detach Exit immediately instead of waiting for the service to converge - (default true) - --dns list Set custom DNS servers (default []) - --dns-option list Set DNS options (default []) - --dns-search list Set custom DNS search domains (default []) - --endpoint-mode string Endpoint mode ("vip"|"dnsrr") (default "vip") - -e, --env list Set environment variables (default []) - --env-file list Read in a file of environment variables (default []) - --group list Set one or more supplementary user groups for the container (default []) + --constraint list Placement constraints + --container-label list Container labels + -d, --detach Exit immediately instead of waiting for the service to converge (default true) + --dns list Set custom DNS servers + --dns-option list Set DNS options + --dns-search list Set custom DNS search domains + --endpoint-mode string Endpoint mode (vip or dnsrr) (default "vip") + --entrypoint command Overwrite the default ENTRYPOINT of the image + -e, --env list Set environment variables + --env-file list Read in a file of environment variables + --group list Set one or more supplementary user groups for the container --health-cmd string Command to run to check health --health-interval duration Time between running the check (ns|us|ms|s|m|h) --health-retries int Consecutive failures needed to report unhealthy + --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) --health-timeout duration Maximum time to allow one check to run (ns|us|ms|s|m|h) - --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) (default 0s) --help Print usage - --host list Set one or more custom host-to-IP mappings (host:ip) (default []) + --host list Set one or more custom host-to-IP mappings (host:ip) --hostname string Container hostname - -l, --label list Service labels (default []) - --limit-cpu decimal Limit CPUs (default 0.000) + -l, --label list Service labels + --limit-cpu decimal Limit CPUs --limit-memory bytes Limit Memory --log-driver string Logging driver for service - --log-opt list Logging driver options (default []) + --log-opt list Logging driver options --mode string Service mode (replicated or global) (default "replicated") --mount mount Attach a filesystem mount to the service --name string Service name - --network list Network attachments (default []) + --network list Network attachments --no-healthcheck Disable any container-specified HEALTHCHECK --placement-pref pref Add a placement preference -p, --publish port Publish a port as a node port + -q, --quiet Suppress progress output --read-only Mount the container's root filesystem as read only --replicas uint Number of tasks - --reserve-cpu decimal Reserve CPUs (default 0.000) + --reserve-cpu decimal Reserve CPUs --reserve-memory bytes Reserve Memory - --restart-condition string Restart when condition is met ("none"|"on-failure"|"any") - --restart-delay duration Delay between restart attempts (ns|us|ms|s|m|h) + --restart-condition string Restart when condition is met ("none"|"on-failure"|"any") (default "any") + --restart-delay duration Delay between restart attempts (ns|us|ms|s|m|h) (default 5s) --restart-max-attempts uint Maximum number of restarts before giving up --restart-window duration Window used to evaluate the restart policy (ns|us|ms|s|m|h) --rollback-delay duration Delay between task rollbacks (ns|us|ms|s|m|h) (default 0s) --rollback-failure-action string Action on rollback failure ("pause"|"continue") (default "pause") - --rollback-max-failure-ratio float Failure rate to tolerate during a rollback - --rollback-monitor duration Duration after each task rollback to monitor for failure - (ns|us|ms|s|m|h) (default 0s) + --rollback-max-failure-ratio float Failure rate to tolerate during a rollback (default 0) + --rollback-monitor duration Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h) (default 5s) --rollback-order string Rollback order ("start-first"|"stop-first") (default "stop-first") - --rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll - back all at once) (default 1) + --rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll back all at once) (default 1) --secret secret Specify secrets to expose to the service - --stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h) + --stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h) (default 10s) --stop-signal string Signal to stop the container -t, --tty Allocate a pseudo-TTY --update-delay duration Delay between updates (ns|us|ms|s|m|h) (default 0s) --update-failure-action string Action on update failure ("pause"|"continue"|"rollback") (default "pause") - --update-max-failure-ratio float Failure rate to tolerate during an update - --update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h) + --update-max-failure-ratio float Failure rate to tolerate during an update (default 0) + --update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 5s) --update-order string Update order ("start-first"|"stop-first") (default "stop-first") --update-parallelism uint Maximum number of tasks updated simultaneously (0 to update all at once) (default 1) -u, --user string Username or UID (format: [:]) diff --git a/docs/reference/commandline/service_update.md b/docs/reference/commandline/service_update.md index f79caeb41c..fae6b0af89 100644 --- a/docs/reference/commandline/service_update.md +++ b/docs/reference/commandline/service_update.md @@ -21,43 +21,43 @@ Usage: docker service update [OPTIONS] SERVICE Update a service Options: - --args string Service command args - --constraint-add list Add or update a placement constraint (default []) - --constraint-rm list Remove a constraint (default []) - --container-label-add list Add or update a container label (default []) - --container-label-rm list Remove a container label by its key (default []) - -d, --detach Exit immediately instead of waiting for the service to converge - (default true) - --dns-add list Add or update a custom DNS server (default []) - --dns-option-add list Add or update a DNS option (default []) - --dns-option-rm list Remove a DNS option (default []) - --dns-rm list Remove a custom DNS server (default []) - --dns-search-add list Add or update a custom DNS search domain (default []) - --dns-search-rm list Remove a DNS search domain (default []) - --endpoint-mode string Endpoint mode ("vip"|"dnsrr") (default "vip") - --env-add list Add or update an environment variable (default []) - --env-rm list Remove an environment variable (default []) + --args command Service command args + --constraint-add list Add or update a placement constraint + --constraint-rm list Remove a constraint + --container-label-add list Add or update a container label + --container-label-rm list Remove a container label by its key + -d, --detach Exit immediately instead of waiting for the service to converge (default true) + --dns-add list Add or update a custom DNS server + --dns-option-add list Add or update a DNS option + --dns-option-rm list Remove a DNS option + --dns-rm list Remove a custom DNS server + --dns-search-add list Add or update a custom DNS search domain + --dns-search-rm list Remove a DNS search domain + --endpoint-mode string Endpoint mode (vip or dnsrr) + --entrypoint command Overwrite the default ENTRYPOINT of the image + --env-add list Add or update an environment variable + --env-rm list Remove an environment variable --force Force update even if no changes require it - --group-add list Add an additional supplementary user group to the container (default []) - --group-rm list Remove a previously added supplementary user group from the container (default []) + --group-add list Add an additional supplementary user group to the container + --group-rm list Remove a previously added supplementary user group from the container --health-cmd string Command to run to check health --health-interval duration Time between running the check (ns|us|ms|s|m|h) --health-retries int Consecutive failures needed to report unhealthy + --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) --health-timeout duration Maximum time to allow one check to run (ns|us|ms|s|m|h) - --health-start-period duration Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h) (default 0s) --help Print usage - --host-add list Add or update a custom host-to-IP mapping (host:ip) (default []) - --host-rm list Remove a custom host-to-IP mapping (host:ip) (default []) + --host-add list Add or update a custom host-to-IP mapping (host:ip) + --host-rm list Remove a custom host-to-IP mapping (host:ip) --hostname string Container hostname --image string Service image tag - --label-add list Add or update a service label (default []) - --label-rm list Remove a label by its key (default []) - --limit-cpu decimal Limit CPUs (default 0.000) + --label-add list Add or update a service label + --label-rm list Remove a label by its key + --limit-cpu decimal Limit CPUs --limit-memory bytes Limit Memory --log-driver string Logging driver for service - --log-opt list Logging driver options (default []) + --log-opt list Logging driver options --mount-add mount Add or update a mount on a service - --mount-rm list Remove a mount by its target path (default []) + --mount-rm list Remove a mount by its target path --network-add list Add a network --network-rm list Remove a network --no-healthcheck Disable any container-specified HEALTHCHECK @@ -65,34 +65,33 @@ Options: --placement-pref-rm pref Remove a placement preference --publish-add port Add or update a published port --publish-rm port Remove a published port by its target port + -q, --quiet Suppress progress output --read-only Mount the container's root filesystem as read only --replicas uint Number of tasks - --reserve-cpu decimal Reserve CPUs (default 0.000) + --reserve-cpu decimal Reserve CPUs --reserve-memory bytes Reserve Memory --restart-condition string Restart when condition is met ("none"|"on-failure"|"any") --restart-delay duration Delay between restart attempts (ns|us|ms|s|m|h) --restart-max-attempts uint Maximum number of restarts before giving up --restart-window duration Window used to evaluate the restart policy (ns|us|ms|s|m|h) --rollback Rollback to previous specification - --rollback-delay duration Delay between task rollbacks (ns|us|ms|s|m|h) (default 0s) - --rollback-failure-action string Action on rollback failure ("pause"|"continue") (default "pause") + --rollback-delay duration Delay between task rollbacks (ns|us|ms|s|m|h) + --rollback-failure-action string Action on rollback failure ("pause"|"continue") --rollback-max-failure-ratio float Failure rate to tolerate during a rollback - --rollback-monitor duration Duration after each task rollback to monitor for failure - (ns|us|ms|s|m|h) (default 0s) + --rollback-monitor duration Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h) --rollback-order string Rollback order ("start-first"|"stop-first") (default "stop-first") - --rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll - back all at once) (default 1) + --rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll back all at once) --secret-add secret Add or update a secret on a service - --secret-rm list Remove a secret (default []) + --secret-rm list Remove a secret --stop-grace-period duration Time to wait before force killing a container (ns|us|ms|s|m|h) --stop-signal string Signal to stop the container -t, --tty Allocate a pseudo-TTY - --update-delay duration Delay between updates (ns|us|ms|s|m|h) (default 0s) - --update-failure-action string Action on update failure ("pause"|"continue"|"rollback") (default "pause") + --update-delay duration Delay between updates (ns|us|ms|s|m|h) + --update-failure-action string Action on update failure ("pause"|"continue"|"rollback") --update-max-failure-ratio float Failure rate to tolerate during an update - --update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h) - --update-order string Update order ("start-first"|"stop-first") (default "stop-first") - --update-parallelism uint Maximum number of tasks updated simultaneously (0 to update all at once) (default 1) + --update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h) + --update-order string Update order ("start-first"|"stop-first") + --update-parallelism uint Maximum number of tasks updated simultaneously (0 to update all at once) -u, --user string Username or UID (format: [:]) --with-registry-auth Send registry authentication details to swarm agents -w, --workdir string Working directory inside the container diff --git a/integration-cli/docker_api_swarm_service_test.go b/integration-cli/docker_api_swarm_service_test.go index a96f684965..6a3c9f170b 100644 --- a/integration-cli/docker_api_swarm_service_test.go +++ b/integration-cli/docker_api_swarm_service_test.go @@ -60,6 +60,16 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesCreate(c *check.C) { id := d.CreateService(c, simpleTestService, setInstances(instances)) waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, instances) + // insertDefaults inserts UpdateConfig when service is fetched by ID + _, out, err := d.SockRequest("GET", "/services/"+id+"?insertDefaults=true", nil) + c.Assert(err, checker.IsNil, check.Commentf("%s", out)) + c.Assert(string(out), checker.Contains, "UpdateConfig") + + // insertDefaults inserts UpdateConfig when service is fetched by ID + _, out, err = d.SockRequest("GET", "/services/top?insertDefaults=true", nil) + c.Assert(err, checker.IsNil, check.Commentf("%s", out)) + c.Assert(string(out), checker.Contains, "UpdateConfig") + service := d.GetService(c, id) instances = 5 d.UpdateService(c, service, setInstances(instances)) diff --git a/opts/opts.go b/opts/opts.go index 8c82960c20..f76f308051 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -38,7 +38,10 @@ func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { } func (opts *ListOpts) String() string { - return fmt.Sprintf("%v", []string((*opts.values))) + if len(*opts.values) == 0 { + return "" + } + return fmt.Sprintf("%v", *opts.values) } // Set validates if needed the input value and adds it to the @@ -343,6 +346,9 @@ type NanoCPUs int64 // String returns the string format of the number func (c *NanoCPUs) String() string { + if *c == 0 { + return "" + } return big.NewRat(c.Value(), 1e9).FloatString(3) } diff --git a/opts/opts_test.go b/opts/opts_test.go index e137127156..c1e7735b58 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -93,12 +93,12 @@ func TestListOptsWithValidator(t *testing.T) { // Re-using logOptsvalidator (used by MapOpts) o := NewListOpts(logOptsValidator) o.Set("foo") - if o.String() != "[]" { - t.Errorf("%s != []", o.String()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } o.Set("foo=bar") - if o.String() != "[]" { - t.Errorf("%s != []", o.String()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } o.Set("max-file=2") if o.Len() != 1 { @@ -111,8 +111,8 @@ func TestListOptsWithValidator(t *testing.T) { t.Error("o.Get(\"baz\") == true") } o.Delete("max-file=2") - if o.String() != "[]" { - t.Errorf("%s != []", o.String()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } }