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