Преглед на файлове

Merge pull request #32284 from aaronlehmann/fix-service-defaults

Improve default handling for "service create"
Sebastiaan van Stijn преди 8 години
родител
ревизия
a258ef58d8

+ 3 - 3
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
 }

+ 11 - 1
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

+ 1 - 1
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

+ 5 - 0
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"

+ 8 - 2
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
 }

+ 2 - 1
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)
 	}

+ 2 - 1
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
 		}

+ 2 - 2
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
 	}

+ 3 - 1
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
 		}

+ 1 - 1
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) {

+ 226 - 37
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,15 +200,75 @@ 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 {
@@ -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.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, 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.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")
 

+ 1 - 1
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
 		}

+ 1 - 1
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
 	}

+ 6 - 6
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)

+ 3 - 1
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})
 	}
 }
 

+ 1 - 1
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)

+ 7 - 2
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}

+ 4 - 3
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)
 	}

+ 11 - 3
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) {

+ 5 - 5
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
 		}

+ 1 - 1
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
 				}

+ 27 - 28
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: <name|uid>[:<group|gid>])

+ 38 - 39
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: <name|uid>[:<group|gid>])
       --with-registry-auth                 Send registry authentication details to swarm agents
   -w, --workdir string                     Working directory inside the container

+ 10 - 0
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))

+ 7 - 1
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)
 }
 

+ 6 - 6
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())
 	}
 }