Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions cli/command/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -1219,25 +1219,37 @@ func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
return nil
}

// updateLogDriver updates the log driver only if the log driver flag is set.
// All options will be replaced with those provided on the command line.
// updateLogDriver updates the log driver only if the log driver or log options
// flags are set. If the log driver flag is set, all options will be replaced
// with those provided on the command line. If only log options are set, the
// service's current log driver is kept, and the options provided are merged
// with its existing options.
func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
if !flags.Changed(flagLogDriver) {
if !anyChanged(flags, flagLogDriver, flagLogOpt) {
return nil
}

name, err := flags.GetString(flagLogDriver)
if err != nil {
return err
}
options := opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetSlice())

if !flags.Changed(flagLogDriver) && taskTemplate.LogDriver != nil {
name = taskTemplate.LogDriver.Name
merged := make(map[string]string, len(taskTemplate.LogDriver.Options)+len(options))
maps.Copy(merged, taskTemplate.LogDriver.Options)
maps.Copy(merged, options)
options = merged
}

if name == "" {
return nil
}

taskTemplate.LogDriver = &swarm.Driver{
Name: name,
Options: opts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetSlice()),
Options: options,
}

return nil
Expand Down
89 changes: 89 additions & 0 deletions cli/command/service/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"maps"
"net/netip"
"slices"
"strconv"
Expand Down Expand Up @@ -333,6 +334,94 @@ func TestUpdatePortsDuplicate(t *testing.T) {
assert.Check(t, is.Equal(uint32(80), portConfigs[0].TargetPort))
}

func TestUpdateLogDriver(t *testing.T) {
tests := []struct {
name string
flags [][2]string
initial *swarm.Driver
expected *swarm.Driver
}{
{
name: "no changes",
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
},
{
name: "set driver and options",
flags: [][2]string{{flagLogDriver, "local"}, {flagLogOpt, "max-file=15"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "local", Options: map[string]string{"max-file": "15"}},
},
{
name: "set driver without options",
flags: [][2]string{{flagLogDriver, "local"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "local", Options: map[string]string{}},
},
{
name: "set empty driver",
flags: [][2]string{{flagLogDriver, ""}, {flagLogOpt, "max-file=15"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
},
{
name: "set options with existing driver",
flags: [][2]string{{flagLogOpt, "max-file=15"}, {flagLogOpt, "max-size=10m"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "15", "max-size": "10m"}},
},
{
name: "set options merges with existing options",
flags: [][2]string{{flagLogOpt, "max-size=10m"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5", "max-size": "10m"}},
},
{
name: "set options overrides existing option",
flags: [][2]string{{flagLogOpt, "max-file=3"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5", "max-size": "10m"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "3", "max-size": "10m"}},
},
{
name: "set options with existing driver without options",
flags: [][2]string{{flagLogOpt, "max-file=15"}},
initial: &swarm.Driver{Name: "json-file"},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "15"}},
},
{
name: "set driver and options replaces existing options",
flags: [][2]string{{flagLogDriver, "json-file"}, {flagLogOpt, "max-size=10m"}},
initial: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-file": "5"}},
expected: &swarm.Driver{Name: "json-file", Options: map[string]string{"max-size": "10m"}},
},
{
// Options are not applied if no log driver is set on the service,
// as they would be applied to the daemon's default log driver,
// which may differ between nodes.
name: "set options without existing driver",
flags: [][2]string{{flagLogOpt, "max-file=15"}},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
flags := newUpdateCommand(nil).Flags()
for _, flag := range tc.flags {
assert.NilError(t, flags.Set(flag[0], flag[1]))
}
var initial *swarm.Driver
if tc.initial != nil {
initial = &swarm.Driver{Name: tc.initial.Name, Options: maps.Clone(tc.initial.Options)}
}
taskTemplate := &swarm.TaskSpec{LogDriver: tc.initial}
err := updateLogDriver(flags, taskTemplate)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(taskTemplate.LogDriver, tc.expected))
// The existing log driver must not be modified in place.
assert.Check(t, is.DeepEqual(tc.initial, initial))
})
}
}

func TestUpdateHealthcheckTable(t *testing.T) {
type test struct {
flags [][2]string
Expand Down
33 changes: 32 additions & 1 deletion docs/reference/commandline/service_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Update a service
| `--limit-memory` | `bytes` | `0` | Limit Memory |
| `--limit-pids` | `int64` | `0` | Limit maximum number of processes (default 0 = unlimited) |
| `--log-driver` | `string` | | Logging driver for service |
| `--log-opt` | `list` | | Logging driver options |
| [`--log-opt`](#log-opt) | `list` | | Logging driver options |
| `--max-concurrent` | `uint` | | Number of job tasks to run concurrently (default equal to --replicas) |
| `--memory-swap` | `bytes` | `0` | Swap Bytes (-1 for unlimited) |
| `--memory-swappiness` | `int64` | `-1` | Tune memory swappiness (0-100), -1 to reset to default |
Expand Down Expand Up @@ -211,6 +211,37 @@ $ docker service update \
myservice
```

### <a name="log-opt"></a> Update logging driver options (--log-driver, --log-opt)

Use the `--log-opt` flag to update the options of the logging driver that is
configured for a service. If `--log-driver` is not set, the service keeps its
current logging driver, and the options that you set are merged with the
options that are already set for the service. If the service has no logging
driver configured, `--log-opt` has no effect unless `--log-driver` is also set.

The following example sets the `max-size` option for a service that uses the
`json-file` logging driver, and keeps its existing `max-file` option:

```console
$ docker service create --log-driver=json-file --log-opt max-file=5 --name myservice nginx:alpine

$ docker service update --log-opt max-size=10m myservice

$ docker service inspect --format='{{json .Spec.TaskTemplate.LogDriver}}' myservice
{"Name":"json-file","Options":{"max-file":"5","max-size":"10m"}}
```

If you set `--log-driver`, all existing options are replaced with the options
that you set. To remove an option, set the current logging driver again
together with the options that you want to keep:

```console
$ docker service update --log-driver=json-file --log-opt max-size=10m myservice

$ docker service inspect --format='{{json .Spec.TaskTemplate.LogDriver}}' myservice
{"Name":"json-file","Options":{"max-size":"10m"}}
```

### <a name="rollback"></a> Roll back to the previous version of a service (--rollback)

Use the `--rollback` option to roll back to the previous version of the service.
Expand Down