Ver código fonte

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

Improve default handling for "service create"
Sebastiaan van Stijn 8 anos atrás
pai
commit
a258ef58d8

+ 3 - 3
api/server/router/swarm/backend.go

@@ -17,7 +17,7 @@ type Backend interface {
 	GetUnlockKey() (string, error)
 	GetUnlockKey() (string, error)
 	UnlockSwarm(req types.UnlockRequest) error
 	UnlockSwarm(req types.UnlockRequest) error
 	GetServices(basictypes.ServiceListOptions) ([]types.Service, 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)
 	CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error)
 	UpdateService(string, uint64, types.ServiceSpec, basictypes.ServiceUpdateOptions) (*basictypes.ServiceUpdateResponse, error)
 	UpdateService(string, uint64, types.ServiceSpec, basictypes.ServiceUpdateOptions) (*basictypes.ServiceUpdateResponse, error)
 	RemoveService(string) error
 	RemoveService(string) error
@@ -30,7 +30,7 @@ type Backend interface {
 	GetTask(string) (types.Task, error)
 	GetTask(string) (types.Task, error)
 	GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error)
 	GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error)
 	CreateSecret(s types.SecretSpec) (string, error)
 	CreateSecret(s types.SecretSpec) (string, error)
-	RemoveSecret(id string) error
+	RemoveSecret(idOrName string) error
 	GetSecret(id string) (types.Secret, 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 {
 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 {
 	if err != nil {
 		logrus.Errorf("Error getting service %s: %v", vars["id"], err)
 		logrus.Errorf("Error getting service %s: %v", vars["id"], err)
 		return 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
 	// checking for whether logs are TTY involves iterating over every service
 	// and task. idk if there is a better way
 	// and task. idk if there is a better way
 	for _, service := range selector.Services {
 	for _, service := range selector.Services {
-		s, err := sr.backend.GetService(service)
+		s, err := sr.backend.GetService(service, false)
 		if err != nil {
 		if err != nil {
 			// maybe should return some context with this error?
 			// maybe should return some context with this error?
 			return err
 			return err

+ 5 - 0
api/swagger.yaml

@@ -7584,6 +7584,11 @@ paths:
           description: "ID or name of service."
           description: "ID or name of service."
           required: true
           required: true
           type: "string"
           type: "string"
+        - name: "insertDefaults"
+          in: "query"
+          description: "Fill empty fields with default values."
+          type: "boolean"
+          default: false
       tags: ["Service"]
       tags: ["Service"]
     delete:
     delete:
       summary: "Delete a service"
       summary: "Delete a service"

+ 8 - 2
api/types/client.go

@@ -316,12 +316,18 @@ type ServiceUpdateOptions struct {
 	Rollback string
 	Rollback string
 }
 }
 
 
-// ServiceListOptions holds parameters to list  services with.
+// ServiceListOptions holds parameters to list services with.
 type ServiceListOptions struct {
 type ServiceListOptions struct {
 	Filters filters.Args
 	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 {
 type TaskListOptions struct {
 	Filters filters.Args
 	Filters filters.Args
 }
 }

+ 2 - 1
cli/command/idresolver/client_test.go

@@ -1,6 +1,7 @@
 package idresolver
 package idresolver
 
 
 import (
 import (
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -19,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (s
 	return swarm.Node{}, []byte{}, nil
 	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 {
 	if cli.serviceInspectFunc != nil {
 		return cli.serviceInspectFunc(serviceID)
 		return cli.serviceInspectFunc(serviceID)
 	}
 	}

+ 2 - 1
cli/command/idresolver/idresolver.go

@@ -3,6 +3,7 @@ package idresolver
 import (
 import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
@@ -39,7 +40,7 @@ func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string,
 		}
 		}
 		return id, nil
 		return id, nil
 	case swarm.Service:
 	case swarm.Service:
-		service, _, err := r.client.ServiceInspectWithRaw(ctx, id)
+		service, _, err := r.client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{})
 		if err != nil {
 		if err != nil {
 			return id, 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.mode, flagMode, "replicated", "Service mode (replicated or global)")
 	flags.StringVar(&opts.name, flagName, "", "Service name")
 	flags.StringVar(&opts.name, flagName, "", "Service name")
 
 
-	addServiceFlags(flags, opts)
+	addServiceFlags(flags, opts, buildServiceDefaultFlagMapping())
 
 
 	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
 	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
 	flags.Var(&opts.containerLabels, flagContainerLabel, "Container 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()
 	ctx := context.Background()
 
 
-	service, err := opts.ToService(ctx, apiClient)
+	service, err := opts.ToService(ctx, apiClient, flags)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 3 - 1
cli/command/service/inspect.go

@@ -5,6 +5,7 @@ import (
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/formatter"
 	"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) {
 	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) {
 		if err == nil || !apiclient.IsErrServiceNotFound(err) {
 			return service, nil, 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
 		tty          bool
 	)
 	)
 
 
-	service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target)
+	service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target, types.ServiceInspectOptions{})
 	if err != nil {
 	if err != nil {
 		// if it's any error other than service not found, it's Real
 		// if it's any error other than service not found, it's Real
 		if !client.IsErrServiceNotFound(err) {
 		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/client"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	runconfigopts "github.com/docker/docker/runconfig/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"
 	shlex "github.com/flynn-archive/go-shlex"
+	gogotypes "github.com/gogo/protobuf/types"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -177,6 +180,9 @@ func (s *ShlexOpt) Type() string {
 }
 }
 
 
 func (s *ShlexOpt) String() string {
 func (s *ShlexOpt) String() string {
+	if len(*s) == 0 {
+		return ""
+	}
 	return fmt.Sprint(*s)
 	return fmt.Sprint(*s)
 }
 }
 
 
@@ -194,15 +200,75 @@ type updateOptions struct {
 	order           string
 	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{
 	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 {
 type resourceOptions struct {
@@ -232,13 +298,70 @@ type restartPolicyOptions struct {
 	window      DurationOpt
 	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 {
 type credentialSpecOpt struct {
@@ -463,7 +586,14 @@ func (opts *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) {
 	return serviceMode, nil
 	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
 	var service swarm.ServiceSpec
 
 
 	envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll())
 	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(),
 					Options:     opts.dnsOption.GetAll(),
 				},
 				},
 				Hosts:           convertExtraHostsToSwarmHosts(opts.hosts.GetAll()),
 				Hosts:           convertExtraHostsToSwarmHosts(opts.hosts.GetAll()),
-				StopGracePeriod: opts.stopGrace.Value(),
+				StopGracePeriod: opts.ToStopGracePeriod(flags),
 				Secrets:         nil,
 				Secrets:         nil,
 				Healthcheck:     healthConfig,
 				Healthcheck:     healthConfig,
 			},
 			},
 			Networks:      networks,
 			Networks:      networks,
 			Resources:     opts.resources.ToResourceRequirements(),
 			Resources:     opts.resources.ToResourceRequirements(),
-			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
+			RestartPolicy: opts.restartPolicy.ToRestartPolicy(flags),
 			Placement: &swarm.Placement{
 			Placement: &swarm.Placement{
 				Constraints: opts.constraints.GetAll(),
 				Constraints: opts.constraints.GetAll(),
 				Preferences: opts.placementPrefs.prefs,
 				Preferences: opts.placementPrefs.prefs,
@@ -540,8 +670,8 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIC
 			LogDriver: opts.logDriver.toLogDriver(),
 			LogDriver: opts.logDriver.toLogDriver(),
 		},
 		},
 		Mode:           serviceMode,
 		Mode:           serviceMode,
-		UpdateConfig:   opts.update.config(),
-		RollbackConfig: opts.rollback.config(),
+		UpdateConfig:   opts.update.updateConfig(flags),
+		RollbackConfig: opts.update.rollbackConfig(flags),
 		EndpointSpec:   opts.endpoint.ToEndpointSpec(),
 		EndpointSpec:   opts.endpoint.ToEndpointSpec(),
 	}
 	}
 
 
@@ -554,9 +684,67 @@ func (opts *serviceOptions) ToService(ctx context.Context, apiClient client.APIC
 	return service, nil
 	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`.
 // 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
 // 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.detach, "detach", "d", true, "Exit immediately instead of waiting for the service to converge")
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output")
 	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.limitMemBytes, flagLimitMemory, "Limit Memory")
 	flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
 	flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
 	flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
 	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.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.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.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.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.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.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.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.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.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.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")
 	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 {
 	for {
-		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
+		service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
 		if err != nil {
 		if err != nil {
 			return err
 			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()
 	client := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
-	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
+	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 6 - 6
cli/command/service/update.go

@@ -17,6 +17,7 @@ import (
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
+	"github.com/docker/swarmkit/api/defaults"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -42,7 +43,7 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.SetAnnotation("rollback", "version", []string{"1.25"})
 	flags.SetAnnotation("rollback", "version", []string{"1.25"})
 	flags.Bool("force", false, "Force update even if no changes require it")
 	flags.Bool("force", false, "Force update even if no changes require it")
 	flags.SetAnnotation("force", "version", []string{"1.25"})
 	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(), flagEnvRemove, "Remove an environment variable")
 	flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
 	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()
 	apiClient := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
-	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
+	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -294,9 +295,8 @@ func updateService(ctx context.Context, apiClient client.APIClient, flags *pflag
 
 
 	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
 	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
 		if task.RestartPolicy == nil {
 		if task.RestartPolicy == nil {
-			task.RestartPolicy = &swarm.RestartPolicy{}
+			task.RestartPolicy = defaultRestartPolicy()
 		}
 		}
-
 		if flags.Changed(flagRestartCondition) {
 		if flags.Changed(flagRestartCondition) {
 			value, _ := flags.GetString(flagRestartCondition)
 			value, _ := flags.GetString(flagRestartCondition)
 			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
 			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 anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio, flagUpdateOrder) {
 		if spec.UpdateConfig == nil {
 		if spec.UpdateConfig == nil {
-			spec.UpdateConfig = &swarm.UpdateConfig{}
+			spec.UpdateConfig = updateConfigFromDefaults(defaults.Service.Update)
 		}
 		}
 		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
 		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
 		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
 		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 anyChanged(flags, flagRollbackParallelism, flagRollbackDelay, flagRollbackMonitor, flagRollbackFailureAction, flagRollbackMaxFailureRatio, flagRollbackOrder) {
 		if spec.RollbackConfig == nil {
 		if spec.RollbackConfig == nil {
-			spec.RollbackConfig = &swarm.UpdateConfig{}
+			spec.RollbackConfig = updateConfigFromDefaults(defaults.Service.Rollback)
 		}
 		}
 		updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism)
 		updateUint64(flagRollbackParallelism, &spec.RollbackConfig.Parallelism)
 		updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay)
 		updateDuration(flagRollbackDelay, &spec.RollbackConfig.Delay)

+ 3 - 1
cli/command/system/inspect.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/inspect"
 	"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 {
 func inspectService(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc {
 	return func(ref string) (interface{}, []byte, error) {
 	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
 // ServiceAPIClient defines API client methods for the services
 type ServiceAPIClient interface {
 type ServiceAPIClient interface {
 	ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error)
 	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)
 	ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
 	ServiceRemove(ctx context.Context, serviceID string) 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)
 	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 (
 import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
+	"net/url"
 
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
 // ServiceInspectWithRaw returns the service information and the raw data.
 // 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 err != nil {
 		if serverResp.statusCode == http.StatusNotFound {
 		if serverResp.statusCode == http.StatusNotFound {
 			return swarm.Service{}, nil, serviceNotFoundError{serviceID}
 			return swarm.Service{}, nil, serviceNotFoundError{serviceID}

+ 4 - 3
client/service_inspect_test.go

@@ -9,6 +9,7 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -18,7 +19,7 @@ func TestServiceInspectError(t *testing.T) {
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 		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" {
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 		t.Fatalf("expected a Server Error, got %v", err)
 		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")),
 		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) {
 	if err == nil || !IsErrServiceNotFound(err) {
 		t.Fatalf("expected a serviceNotFoundError error, got %v", 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 {
 	if err != nil {
 		t.Fatal(err)
 		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
 	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.
 	// 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
 		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 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) {
 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.
 // 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
 	var service *swarmapi.Service
 	if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
 	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 {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -187,7 +187,7 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
 			return apierrors.NewBadRequestError(err)
 			return apierrors.NewBadRequestError(err)
 		}
 		}
 
 
-		currentService, err := getService(ctx, state.controlClient, serviceIDOrName)
+		currentService, err := getService(ctx, state.controlClient, serviceIDOrName, false)
 		if err != nil {
 		if err != nil {
 			return err
 			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.
 // RemoveService removes a service from a managed swarm cluster.
 func (c *Cluster) RemoveService(input string) error {
 func (c *Cluster) RemoveService(input string) error {
 	return c.lockedManagerAction(func(ctx context.Context, state nodeState) 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 {
 		if err != nil {
 			return err
 			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
 	// don't rely on swarmkit to resolve IDs, do it ourselves
 	swarmSelector := &swarmapi.LogSelector{}
 	swarmSelector := &swarmapi.LogSelector{}
 	for _, s := range selector.Services {
 	for _, s := range selector.Services {
-		service, err := getService(ctx, cc, s)
+		service, err := getService(ctx, cc, s, false)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			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") {
 		if filter.Include("service") {
 			serviceFilters := filter.Get("service")
 			serviceFilters := filter.Get("service")
 			for _, serviceFilter := range serviceFilters {
 			for _, serviceFilter := range serviceFilters {
-				service, err := c.GetService(serviceFilter)
+				service, err := c.GetService(serviceFilter, false)
 				if err != nil {
 				if err != nil {
 					return err
 					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
 Create a new service
 
 
 Options:
 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-cmd string                  Command to run to check health
       --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
       --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
       --health-retries int                 Consecutive failures needed to report unhealthy
       --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-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
       --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
       --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
       --limit-memory bytes                 Limit Memory
       --log-driver string                  Logging driver for service
       --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")
       --mode string                        Service mode (replicated or global) (default "replicated")
       --mount mount                        Attach a filesystem mount to the service
       --mount mount                        Attach a filesystem mount to the service
       --name string                        Service name
       --name string                        Service name
-      --network list                       Network attachments (default [])
+      --network list                       Network attachments
       --no-healthcheck                     Disable any container-specified HEALTHCHECK
       --no-healthcheck                     Disable any container-specified HEALTHCHECK
       --placement-pref pref                Add a placement preference
       --placement-pref pref                Add a placement preference
   -p, --publish port                       Publish a port as a node port
   -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
       --read-only                          Mount the container's root filesystem as read only
       --replicas uint                      Number of tasks
       --replicas uint                      Number of tasks
-      --reserve-cpu decimal                Reserve CPUs (default 0.000)
+      --reserve-cpu decimal                Reserve CPUs
       --reserve-memory bytes               Reserve Memory
       --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-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)
       --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-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-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-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
       --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
       --stop-signal string                 Signal to stop the container
   -t, --tty                                Allocate a pseudo-TTY
   -t, --tty                                Allocate a pseudo-TTY
       --update-delay duration              Delay between updates (ns|us|ms|s|m|h) (default 0s)
       --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-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-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-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>])
   -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
 Update a service
 
 
 Options:
 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
       --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-cmd string                  Command to run to check health
       --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
       --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
       --health-retries int                 Consecutive failures needed to report unhealthy
       --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-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
       --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
       --hostname string                    Container hostname
       --image string                       Service image tag
       --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
       --limit-memory bytes                 Limit Memory
       --log-driver string                  Logging driver for service
       --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-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-add list                   Add a network
       --network-rm list                    Remove a network
       --network-rm list                    Remove a network
       --no-healthcheck                     Disable any container-specified HEALTHCHECK
       --no-healthcheck                     Disable any container-specified HEALTHCHECK
@@ -65,34 +65,33 @@ Options:
       --placement-pref-rm pref             Remove a placement preference
       --placement-pref-rm pref             Remove a placement preference
       --publish-add port                   Add or update a published port
       --publish-add port                   Add or update a published port
       --publish-rm port                    Remove a published port by its target 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
       --read-only                          Mount the container's root filesystem as read only
       --replicas uint                      Number of tasks
       --replicas uint                      Number of tasks
-      --reserve-cpu decimal                Reserve CPUs (default 0.000)
+      --reserve-cpu decimal                Reserve CPUs
       --reserve-memory bytes               Reserve Memory
       --reserve-memory bytes               Reserve Memory
       --restart-condition string           Restart when condition is met ("none"|"on-failure"|"any")
       --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-delay duration             Delay between restart attempts (ns|us|ms|s|m|h)
       --restart-max-attempts uint          Maximum number of restarts before giving up
       --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)
       --restart-window duration            Window used to evaluate the restart policy (ns|us|ms|s|m|h)
       --rollback                           Rollback to previous specification
       --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-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-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-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-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
       --stop-signal string                 Signal to stop the container
   -t, --tty                                Allocate a pseudo-TTY
   -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-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>])
   -u, --user string                        Username or UID (format: <name|uid>[:<group|gid>])
       --with-registry-auth                 Send registry authentication details to swarm agents
       --with-registry-auth                 Send registry authentication details to swarm agents
   -w, --workdir string                     Working directory inside the container
   -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))
 	id := d.CreateService(c, simpleTestService, setInstances(instances))
 	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 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)
 	service := d.GetService(c, id)
 	instances = 5
 	instances = 5
 	d.UpdateService(c, service, setInstances(instances))
 	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 {
 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
 // 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
 // String returns the string format of the number
 func (c *NanoCPUs) String() string {
 func (c *NanoCPUs) String() string {
+	if *c == 0 {
+		return ""
+	}
 	return big.NewRat(c.Value(), 1e9).FloatString(3)
 	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)
 	// Re-using logOptsvalidator (used by MapOpts)
 	o := NewListOpts(logOptsValidator)
 	o := NewListOpts(logOptsValidator)
 	o.Set("foo")
 	o.Set("foo")
-	if o.String() != "[]" {
-		t.Errorf("%s != []", o.String())
+	if o.String() != "" {
+		t.Errorf(`%s != ""`, o.String())
 	}
 	}
 	o.Set("foo=bar")
 	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")
 	o.Set("max-file=2")
 	if o.Len() != 1 {
 	if o.Len() != 1 {
@@ -111,8 +111,8 @@ func TestListOptsWithValidator(t *testing.T) {
 		t.Error("o.Get(\"baz\") == true")
 		t.Error("o.Get(\"baz\") == true")
 	}
 	}
 	o.Delete("max-file=2")
 	o.Delete("max-file=2")
-	if o.String() != "[]" {
-		t.Errorf("%s != []", o.String())
+	if o.String() != "" {
+		t.Errorf(`%s != ""`, o.String())
 	}
 	}
 }
 }