Older versions of Go do not format these comments, so we can already
reformat them ahead of time to prevent gofmt linting failing once
we update to Go 1.19 or up.
Result of:
gofmt -s -w $(find . -type f -name '*.go' | grep -v "/vendor/")
With some manual adjusting.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 82427d1a07)
Signed-off-by: Cory Snider <csnider@mirantis.com>
1707 lines
55 KiB
Go
1707 lines
55 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
mounttypes "github.com/docker/docker/api/types/mount"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/go-units"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
func TestUpdateServiceArgs(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("args", "the \"new args\"")
|
|
|
|
spec := &swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
cspec := spec.TaskTemplate.ContainerSpec
|
|
cspec.Args = []string{"old", "args"}
|
|
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.DeepEqual([]string{"the", "new args"}, cspec.Args))
|
|
}
|
|
|
|
func TestUpdateLabels(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("label-add", "add-beats-remove=value")
|
|
flags.Set("label-add", "to-add=value")
|
|
flags.Set("label-add", "to-update=new-value")
|
|
flags.Set("label-add", "to-replace=new-value")
|
|
flags.Set("label-rm", "add-beats-remove")
|
|
flags.Set("label-rm", "to-remove")
|
|
flags.Set("label-rm", "to-replace")
|
|
flags.Set("label-rm", "no-such-label")
|
|
|
|
labels := map[string]string{
|
|
"to-keep": "value",
|
|
"to-remove": "value",
|
|
"to-replace": "value",
|
|
"to-update": "value",
|
|
}
|
|
|
|
updateLabels(flags, &labels)
|
|
assert.DeepEqual(t, labels, map[string]string{
|
|
"add-beats-remove": "value",
|
|
"to-add": "value",
|
|
"to-keep": "value",
|
|
"to-replace": "new-value",
|
|
"to-update": "new-value",
|
|
})
|
|
}
|
|
|
|
func TestUpdateContainerLabels(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("container-label-add", "add-beats-remove=value")
|
|
flags.Set("container-label-add", "to-add=value")
|
|
flags.Set("container-label-add", "to-update=new-value")
|
|
flags.Set("container-label-add", "to-replace=new-value")
|
|
flags.Set("container-label-rm", "add-beats-remove")
|
|
flags.Set("container-label-rm", "to-remove")
|
|
flags.Set("container-label-rm", "to-replace")
|
|
flags.Set("container-label-rm", "no-such-label")
|
|
|
|
labels := map[string]string{
|
|
"to-keep": "value",
|
|
"to-remove": "value",
|
|
"to-replace": "value",
|
|
"to-update": "value",
|
|
}
|
|
|
|
updateContainerLabels(flags, &labels)
|
|
assert.DeepEqual(t, labels, map[string]string{
|
|
"add-beats-remove": "value",
|
|
"to-add": "value",
|
|
"to-keep": "value",
|
|
"to-replace": "new-value",
|
|
"to-update": "new-value",
|
|
})
|
|
}
|
|
|
|
func TestUpdatePlacementConstraints(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("constraint-add", "node=toadd")
|
|
flags.Set("constraint-rm", "node!=toremove")
|
|
|
|
placement := &swarm.Placement{
|
|
Constraints: []string{"node!=toremove", "container=tokeep"},
|
|
}
|
|
|
|
updatePlacementConstraints(flags, placement)
|
|
assert.Assert(t, is.Len(placement.Constraints, 2))
|
|
assert.Check(t, is.Equal("container=tokeep", placement.Constraints[0]))
|
|
assert.Check(t, is.Equal("node=toadd", placement.Constraints[1]))
|
|
}
|
|
|
|
func TestUpdatePlacementPrefs(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("placement-pref-add", "spread=node.labels.dc")
|
|
flags.Set("placement-pref-rm", "spread=node.labels.rack")
|
|
|
|
placement := &swarm.Placement{
|
|
Preferences: []swarm.PlacementPreference{
|
|
{
|
|
Spread: &swarm.SpreadOver{
|
|
SpreadDescriptor: "node.labels.rack",
|
|
},
|
|
},
|
|
{
|
|
Spread: &swarm.SpreadOver{
|
|
SpreadDescriptor: "node.labels.row",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
updatePlacementPreferences(flags, placement)
|
|
assert.Assert(t, is.Len(placement.Preferences, 2))
|
|
assert.Check(t, is.Equal("node.labels.row", placement.Preferences[0].Spread.SpreadDescriptor))
|
|
assert.Check(t, is.Equal("node.labels.dc", placement.Preferences[1].Spread.SpreadDescriptor))
|
|
}
|
|
|
|
func TestUpdateEnvironment(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("env-add", "toadd=newenv")
|
|
flags.Set("env-rm", "toremove")
|
|
|
|
envs := []string{"toremove=theenvtoremove", "tokeep=value"}
|
|
|
|
updateEnvironment(flags, &envs)
|
|
assert.Assert(t, is.Len(envs, 2))
|
|
// Order has been removed in updateEnvironment (map)
|
|
sort.Strings(envs)
|
|
assert.Check(t, is.Equal("toadd=newenv", envs[0]))
|
|
assert.Check(t, is.Equal("tokeep=value", envs[1]))
|
|
}
|
|
|
|
func TestUpdateEnvironmentWithDuplicateValues(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("env-rm", "foo")
|
|
flags.Set("env-add", "foo=first")
|
|
flags.Set("env-add", "foo=second")
|
|
|
|
envs := []string{"foo=value"}
|
|
|
|
updateEnvironment(flags, &envs)
|
|
assert.Check(t, is.Len(envs, 1))
|
|
assert.Equal(t, envs[0], "foo=second")
|
|
}
|
|
|
|
func TestUpdateEnvironmentWithDuplicateKeys(t *testing.T) {
|
|
// Test case for #25404
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("env-add", "A=b")
|
|
|
|
envs := []string{"A=c"}
|
|
|
|
updateEnvironment(flags, &envs)
|
|
assert.Assert(t, is.Len(envs, 1))
|
|
assert.Check(t, is.Equal("A=b", envs[0]))
|
|
}
|
|
|
|
func TestUpdateGroups(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("group-add", "wheel")
|
|
flags.Set("group-add", "docker")
|
|
flags.Set("group-rm", "root")
|
|
flags.Set("group-add", "foo")
|
|
flags.Set("group-rm", "docker")
|
|
|
|
groups := []string{"bar", "root"}
|
|
|
|
updateGroups(flags, &groups)
|
|
assert.Assert(t, is.Len(groups, 3))
|
|
assert.Check(t, is.Equal("bar", groups[0]))
|
|
assert.Check(t, is.Equal("foo", groups[1]))
|
|
assert.Check(t, is.Equal("wheel", groups[2]))
|
|
}
|
|
|
|
func TestUpdateDNSConfig(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
|
|
// IPv4, with duplicates
|
|
flags.Set("dns-add", "1.1.1.1")
|
|
flags.Set("dns-add", "1.1.1.1")
|
|
flags.Set("dns-add", "2.2.2.2")
|
|
flags.Set("dns-rm", "3.3.3.3")
|
|
flags.Set("dns-rm", "2.2.2.2")
|
|
// IPv6
|
|
flags.Set("dns-add", "2001:db8:abc8::1")
|
|
// Invalid dns record
|
|
assert.ErrorContains(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
|
|
|
|
// domains with duplicates
|
|
flags.Set("dns-search-add", "example.com")
|
|
flags.Set("dns-search-add", "example.com")
|
|
flags.Set("dns-search-add", "example.org")
|
|
flags.Set("dns-search-rm", "example.org")
|
|
// Invalid dns search domain
|
|
assert.ErrorContains(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
|
|
|
|
flags.Set("dns-option-add", "ndots:9")
|
|
flags.Set("dns-option-rm", "timeout:3")
|
|
|
|
config := &swarm.DNSConfig{
|
|
Nameservers: []string{"3.3.3.3", "5.5.5.5"},
|
|
Search: []string{"localdomain"},
|
|
Options: []string{"timeout:3"},
|
|
}
|
|
|
|
updateDNSConfig(flags, &config)
|
|
|
|
assert.Assert(t, is.Len(config.Nameservers, 3))
|
|
assert.Check(t, is.Equal("1.1.1.1", config.Nameservers[0]))
|
|
assert.Check(t, is.Equal("2001:db8:abc8::1", config.Nameservers[1]))
|
|
assert.Check(t, is.Equal("5.5.5.5", config.Nameservers[2]))
|
|
|
|
assert.Assert(t, is.Len(config.Search, 2))
|
|
assert.Check(t, is.Equal("example.com", config.Search[0]))
|
|
assert.Check(t, is.Equal("localdomain", config.Search[1]))
|
|
|
|
assert.Assert(t, is.Len(config.Options, 1))
|
|
assert.Check(t, is.Equal(config.Options[0], "ndots:9"))
|
|
}
|
|
|
|
func TestUpdateMounts(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")
|
|
flags.Set("mount-rm", "/toremove")
|
|
|
|
mounts := []mounttypes.Mount{
|
|
{Target: "/toremove", Source: "vol1", Type: mounttypes.TypeBind},
|
|
{Target: "/tokeep", Source: "vol3", Type: mounttypes.TypeBind},
|
|
}
|
|
|
|
updateMounts(flags, &mounts)
|
|
assert.Assert(t, is.Len(mounts, 2))
|
|
assert.Check(t, is.Equal("/toadd", mounts[0].Target))
|
|
assert.Check(t, is.Equal("/tokeep", mounts[1].Target))
|
|
}
|
|
|
|
func TestUpdateMountsWithDuplicateMounts(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("mount-add", "type=volume,source=vol4,target=/toadd")
|
|
|
|
mounts := []mounttypes.Mount{
|
|
{Target: "/tokeep1", Source: "vol1", Type: mounttypes.TypeBind},
|
|
{Target: "/toadd", Source: "vol2", Type: mounttypes.TypeBind},
|
|
{Target: "/tokeep2", Source: "vol3", Type: mounttypes.TypeBind},
|
|
}
|
|
|
|
updateMounts(flags, &mounts)
|
|
assert.Assert(t, is.Len(mounts, 3))
|
|
assert.Check(t, is.Equal("/tokeep1", mounts[0].Target))
|
|
assert.Check(t, is.Equal("/tokeep2", mounts[1].Target))
|
|
assert.Check(t, is.Equal("/toadd", mounts[2].Target))
|
|
}
|
|
|
|
func TestUpdatePorts(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("publish-add", "1000:1000")
|
|
flags.Set("publish-rm", "333/udp")
|
|
|
|
portConfigs := []swarm.PortConfig{
|
|
{TargetPort: 333, Protocol: swarm.PortConfigProtocolUDP},
|
|
{TargetPort: 555},
|
|
}
|
|
|
|
err := updatePorts(flags, &portConfigs)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(portConfigs, 2))
|
|
// Do a sort to have the order (might have changed by map)
|
|
targetPorts := []int{int(portConfigs[0].TargetPort), int(portConfigs[1].TargetPort)}
|
|
sort.Ints(targetPorts)
|
|
assert.Check(t, is.Equal(555, targetPorts[0]))
|
|
assert.Check(t, is.Equal(1000, targetPorts[1]))
|
|
}
|
|
|
|
func TestUpdatePortsDuplicate(t *testing.T) {
|
|
// Test case for #25375
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("publish-add", "80:80")
|
|
|
|
portConfigs := []swarm.PortConfig{
|
|
{
|
|
TargetPort: 80,
|
|
PublishedPort: 80,
|
|
Protocol: swarm.PortConfigProtocolTCP,
|
|
PublishMode: swarm.PortConfigPublishModeIngress,
|
|
},
|
|
}
|
|
|
|
err := updatePorts(flags, &portConfigs)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(portConfigs, 1))
|
|
assert.Check(t, is.Equal(uint32(80), portConfigs[0].TargetPort))
|
|
}
|
|
|
|
func TestUpdateHealthcheckTable(t *testing.T) {
|
|
type test struct {
|
|
flags [][2]string
|
|
initial *container.HealthConfig
|
|
expected *container.HealthConfig
|
|
err string
|
|
}
|
|
testCases := []test{
|
|
{
|
|
flags: [][2]string{{"no-healthcheck", "true"}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}, Retries: 10},
|
|
expected: &container.HealthConfig{Test: []string{"NONE"}},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-cmd", "cmd1"}},
|
|
initial: &container.HealthConfig{Test: []string{"NONE"}},
|
|
expected: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-retries", "10"}},
|
|
initial: &container.HealthConfig{Test: []string{"NONE"}},
|
|
expected: &container.HealthConfig{Retries: 10},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-retries", "10"}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
|
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-interval", "1m"}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
|
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Interval: time.Minute},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-cmd", ""}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
|
expected: &container.HealthConfig{Retries: 10},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-retries", "0"}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
|
|
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-start-period", "1m"}},
|
|
initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
|
|
expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, StartPeriod: time.Minute},
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-cmd", "cmd1"}, {"no-healthcheck", "true"}},
|
|
err: "--no-healthcheck conflicts with --health-* options",
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-interval", "10m"}, {"no-healthcheck", "true"}},
|
|
err: "--no-healthcheck conflicts with --health-* options",
|
|
},
|
|
{
|
|
flags: [][2]string{{"health-timeout", "1m"}, {"no-healthcheck", "true"}},
|
|
err: "--no-healthcheck conflicts with --health-* options",
|
|
},
|
|
}
|
|
for i, c := range testCases {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
for _, flag := range c.flags {
|
|
flags.Set(flag[0], flag[1])
|
|
}
|
|
cspec := &swarm.ContainerSpec{
|
|
Healthcheck: c.initial,
|
|
}
|
|
err := updateHealthcheck(flags, cspec)
|
|
if c.err != "" {
|
|
assert.Error(t, err, c.err)
|
|
} else {
|
|
assert.NilError(t, err)
|
|
if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
|
|
t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateHosts(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-add", "example.net:2.2.2.2")
|
|
flags.Set("host-add", "ipv6.net:2001:db8:abc8::1")
|
|
// adding the special "host-gateway" target should work
|
|
flags.Set("host-add", "host.docker.internal:host-gateway")
|
|
// remove with ipv6 should work
|
|
flags.Set("host-rm", "example.net:2001:db8:abc8::1")
|
|
// just hostname should work as well
|
|
flags.Set("host-rm", "example.net")
|
|
// removing the special "host-gateway" target should work
|
|
flags.Set("host-rm", "gateway.docker.internal:host-gateway")
|
|
// bad format error
|
|
assert.ErrorContains(t, flags.Set("host-add", "$example.com$"), `bad format for add-host: "$example.com$"`)
|
|
|
|
hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net", "gateway.docker.internal:host-gateway"}
|
|
expected := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2.2.2.2 example.net", "2001:db8:abc8::1 ipv6.net", "host-gateway host.docker.internal"}
|
|
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual(expected, hosts))
|
|
}
|
|
|
|
func TestUpdateHostsPreservesOrder(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-add", "foobar:127.0.0.2")
|
|
flags.Set("host-add", "foobar:127.0.0.1")
|
|
flags.Set("host-add", "foobar:127.0.0.3")
|
|
|
|
hosts := []string{}
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.2 foobar", "127.0.0.1 foobar", "127.0.0.3 foobar"}, hosts))
|
|
}
|
|
|
|
func TestUpdateHostsReplaceEntry(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-add", "foobar:127.0.0.4")
|
|
flags.Set("host-rm", "foobar:127.0.0.2")
|
|
|
|
hosts := []string{"127.0.0.2 foobar", "127.0.0.1 foobar", "127.0.0.3 foobar"}
|
|
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.1 foobar", "127.0.0.3 foobar", "127.0.0.4 foobar"}, hosts))
|
|
}
|
|
|
|
func TestUpdateHostsRemoveHost(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-rm", "host1")
|
|
|
|
hosts := []string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1"}
|
|
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
|
|
// Removing host `host1` should remove the entry from each line it appears in.
|
|
// If there are no other hosts in the entry, the entry itself should be removed.
|
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host3 host2 host4", "127.0.0.1 host4"}, hosts))
|
|
}
|
|
|
|
func TestUpdateHostsRemoveHostIP(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-rm", "host1:127.0.0.1")
|
|
|
|
hosts := []string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host1 host4", "127.0.0.3 host1", "127.0.0.1 host1"}
|
|
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
|
|
// Removing host `host1` should remove the entry from each line it appears in,
|
|
// but only if the IP-address matches. If there are no other hosts in the entry,
|
|
// the entry itself should be removed.
|
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host3 host1 host2 host4", "127.0.0.1 host4", "127.0.0.3 host1"}, hosts))
|
|
}
|
|
|
|
func TestUpdateHostsRemoveAll(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("host-add", "host-three:127.0.0.4")
|
|
flags.Set("host-add", "host-one:127.0.0.5")
|
|
flags.Set("host-rm", "host-one")
|
|
|
|
hosts := []string{"127.0.0.1 host-one", "127.0.0.2 host-two", "127.0.0.3 host-one"}
|
|
|
|
err := updateHosts(flags, &hosts)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.2 host-two", "127.0.0.4 host-three", "127.0.0.5 host-one"}, hosts))
|
|
}
|
|
|
|
func TestUpdatePortsRmWithProtocol(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("publish-add", "8081:81")
|
|
flags.Set("publish-add", "8082:82")
|
|
flags.Set("publish-rm", "80")
|
|
flags.Set("publish-rm", "81/tcp")
|
|
flags.Set("publish-rm", "82/udp")
|
|
|
|
portConfigs := []swarm.PortConfig{
|
|
{
|
|
TargetPort: 80,
|
|
PublishedPort: 8080,
|
|
Protocol: swarm.PortConfigProtocolTCP,
|
|
PublishMode: swarm.PortConfigPublishModeIngress,
|
|
},
|
|
}
|
|
|
|
err := updatePorts(flags, &portConfigs)
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(portConfigs, 2))
|
|
assert.Check(t, is.Equal(uint32(81), portConfigs[0].TargetPort))
|
|
assert.Check(t, is.Equal(uint32(82), portConfigs[1].TargetPort))
|
|
}
|
|
|
|
type secretAPIClientMock struct {
|
|
listResult []swarm.Secret
|
|
}
|
|
|
|
func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
|
|
return s.listResult, nil
|
|
}
|
|
func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
|
|
return types.SecretCreateResponse{}, nil
|
|
}
|
|
func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error {
|
|
return nil
|
|
}
|
|
func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) {
|
|
return swarm.Secret{}, []byte{}, nil
|
|
}
|
|
func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
|
|
return nil
|
|
}
|
|
|
|
// TestUpdateSecretUpdateInPlace tests the ability to update the "target" of a
|
|
// secret with "docker service update" by combining "--secret-rm" and
|
|
// "--secret-add" for the same secret.
|
|
func TestUpdateSecretUpdateInPlace(t *testing.T) {
|
|
apiClient := secretAPIClientMock{
|
|
listResult: []swarm.Secret{
|
|
{
|
|
ID: "tn9qiblgnuuut11eufquw5dev",
|
|
Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo"}},
|
|
},
|
|
},
|
|
}
|
|
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("secret-add", "source=foo,target=foo2")
|
|
flags.Set("secret-rm", "foo")
|
|
|
|
secrets := []*swarm.SecretReference{
|
|
{
|
|
File: &swarm.SecretReferenceFileTarget{
|
|
Name: "foo",
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 292,
|
|
},
|
|
SecretID: "tn9qiblgnuuut11eufquw5dev",
|
|
SecretName: "foo",
|
|
},
|
|
}
|
|
|
|
updatedSecrets, err := getUpdatedSecrets(apiClient, flags, secrets)
|
|
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(updatedSecrets, 1))
|
|
assert.Check(t, is.Equal("tn9qiblgnuuut11eufquw5dev", updatedSecrets[0].SecretID))
|
|
assert.Check(t, is.Equal("foo", updatedSecrets[0].SecretName))
|
|
assert.Check(t, is.Equal("foo2", updatedSecrets[0].File.Name))
|
|
}
|
|
|
|
func TestUpdateReadOnly(t *testing.T) {
|
|
spec := &swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
cspec := spec.TaskTemplate.ContainerSpec
|
|
|
|
// Update with --read-only=true, changed to true
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("read-only", "true")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, cspec.ReadOnly)
|
|
|
|
// Update without --read-only, no change
|
|
flags = newUpdateCommand(nil).Flags()
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, cspec.ReadOnly)
|
|
|
|
// Update with --read-only=false, changed to false
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set("read-only", "false")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, !cspec.ReadOnly)
|
|
}
|
|
|
|
func TestUpdateInit(t *testing.T) {
|
|
spec := &swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
cspec := spec.TaskTemplate.ContainerSpec
|
|
|
|
// Update with --init=true
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("init", "true")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal(true, *cspec.Init))
|
|
|
|
// Update without --init, no change
|
|
flags = newUpdateCommand(nil).Flags()
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal(true, *cspec.Init))
|
|
|
|
// Update with --init=false
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set("init", "false")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal(false, *cspec.Init))
|
|
}
|
|
|
|
func TestUpdateStopSignal(t *testing.T) {
|
|
spec := &swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
cspec := spec.TaskTemplate.ContainerSpec
|
|
|
|
// Update with --stop-signal=SIGUSR1
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set("stop-signal", "SIGUSR1")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal("SIGUSR1", cspec.StopSignal))
|
|
|
|
// Update without --stop-signal, no change
|
|
flags = newUpdateCommand(nil).Flags()
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal("SIGUSR1", cspec.StopSignal))
|
|
|
|
// Update with --stop-signal=SIGWINCH
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set("stop-signal", "SIGWINCH")
|
|
updateService(context.TODO(), nil, flags, spec)
|
|
assert.Check(t, is.Equal("SIGWINCH", cspec.StopSignal))
|
|
}
|
|
|
|
func TestUpdateIsolationValid(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set("isolation", "process")
|
|
assert.NilError(t, err)
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(container.IsolationProcess, spec.TaskTemplate.ContainerSpec.Isolation))
|
|
}
|
|
|
|
// TestUpdateLimitsReservations tests that limits and reservations are updated,
|
|
// and that values are not updated are not reset to their default value
|
|
func TestUpdateLimitsReservations(t *testing.T) {
|
|
// test that updating works if the service did not previously
|
|
// have limits set (https://github.com/moby/moby/issues/38363)
|
|
t.Run("update limits from scratch", func(t *testing.T) {
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagLimitCPU, "2")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagLimitMemory, "200M")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagLimitPids, "100")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(209715200)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(100)))
|
|
})
|
|
|
|
// test that updating works if the service did not previously
|
|
// have reservations set (https://github.com/moby/moby/issues/38363)
|
|
t.Run("update reservations from scratch", func(t *testing.T) {
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagReserveCPU, "2")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagReserveMemory, "200M")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
})
|
|
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
Resources: &swarm.ResourceRequirements{
|
|
Limits: &swarm.Limit{
|
|
NanoCPUs: 1000000000,
|
|
MemoryBytes: 104857600,
|
|
Pids: 100,
|
|
},
|
|
Reservations: &swarm.Resources{
|
|
NanoCPUs: 1000000000,
|
|
MemoryBytes: 104857600,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Updating without flags set should not modify existing values
|
|
t.Run("update without flags set", func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(1000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(104857600)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(100)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.NanoCPUs, int64(1000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.MemoryBytes, int64(104857600)))
|
|
})
|
|
|
|
// Updating CPU limit/reservation should not affect memory limit/reservation
|
|
// and pids-limt
|
|
t.Run("update cpu limit and reservation", func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagLimitCPU, "2")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagReserveCPU, "2")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(104857600)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(100)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.MemoryBytes, int64(104857600)))
|
|
})
|
|
|
|
// Updating Memory limit/reservation should not affect CPU limit/reservation
|
|
// and pids-limt
|
|
t.Run("update memory limit and reservation", func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagLimitMemory, "200M")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagReserveMemory, "200M")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(209715200)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(100)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.MemoryBytes, int64(209715200)))
|
|
})
|
|
|
|
// Updating PidsLimit should only modify PidsLimit, other values unchanged
|
|
t.Run("update pids limit", func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagLimitPids, "2")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(209715200)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(2)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.MemoryBytes, int64(209715200)))
|
|
})
|
|
|
|
t.Run("update pids limit to default", func(t *testing.T) {
|
|
// Updating PidsLimit to 0 should work
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagLimitPids, "0")
|
|
assert.NilError(t, err)
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.MemoryBytes, int64(209715200)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Limits.Pids, int64(0)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.NanoCPUs, int64(2000000000)))
|
|
assert.Check(t, is.Equal(spec.TaskTemplate.Resources.Reservations.MemoryBytes, int64(209715200)))
|
|
})
|
|
}
|
|
|
|
func TestUpdateIsolationInvalid(t *testing.T) {
|
|
// validation depends on daemon os / version so validation should be done on the daemon side
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set("isolation", "test")
|
|
assert.NilError(t, err)
|
|
spec := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
},
|
|
}
|
|
err = updateService(context.Background(), nil, flags, &spec)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(container.Isolation("test"), spec.TaskTemplate.ContainerSpec.Isolation))
|
|
}
|
|
|
|
func TestAddGenericResources(t *testing.T) {
|
|
task := &swarm.TaskSpec{}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
|
|
assert.Check(t, addGenericResources(flags, task))
|
|
|
|
flags.Set(flagGenericResourcesAdd, "foo=1")
|
|
assert.Check(t, addGenericResources(flags, task))
|
|
assert.Check(t, is.Len(task.Resources.Reservations.GenericResources, 1))
|
|
|
|
// Checks that foo isn't added a 2nd time
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set(flagGenericResourcesAdd, "bar=1")
|
|
assert.Check(t, addGenericResources(flags, task))
|
|
assert.Check(t, is.Len(task.Resources.Reservations.GenericResources, 2))
|
|
}
|
|
|
|
func TestRemoveGenericResources(t *testing.T) {
|
|
task := &swarm.TaskSpec{}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
|
|
assert.Check(t, removeGenericResources(flags, task))
|
|
|
|
flags.Set(flagGenericResourcesRemove, "foo")
|
|
assert.Check(t, is.ErrorContains(removeGenericResources(flags, task), ""))
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set(flagGenericResourcesAdd, "foo=1")
|
|
addGenericResources(flags, task)
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set(flagGenericResourcesAdd, "bar=1")
|
|
addGenericResources(flags, task)
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
flags.Set(flagGenericResourcesRemove, "foo")
|
|
assert.Check(t, removeGenericResources(flags, task))
|
|
assert.Check(t, is.Len(task.Resources.Reservations.GenericResources, 1))
|
|
}
|
|
|
|
func TestUpdateNetworks(t *testing.T) {
|
|
ctx := context.Background()
|
|
nws := []types.NetworkResource{
|
|
{Name: "aaa-network", ID: "id555"},
|
|
{Name: "mmm-network", ID: "id999"},
|
|
{Name: "zzz-network", ID: "id111"},
|
|
}
|
|
|
|
client := &fakeClient{
|
|
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
|
|
for _, network := range nws {
|
|
if network.ID == networkID || network.Name == networkID {
|
|
return network, nil
|
|
}
|
|
}
|
|
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
|
|
},
|
|
}
|
|
|
|
svc := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
Networks: []swarm.NetworkAttachmentConfig{
|
|
{Target: "id999"},
|
|
},
|
|
},
|
|
}
|
|
|
|
flags := newUpdateCommand(nil).Flags()
|
|
err := flags.Set(flagNetworkAdd, "aaa-network")
|
|
assert.NilError(t, err)
|
|
err = updateService(ctx, client, flags, &svc)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks))
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
err = flags.Set(flagNetworkAdd, "aaa-network")
|
|
assert.NilError(t, err)
|
|
err = updateService(ctx, client, flags, &svc)
|
|
assert.Error(t, err, "service is already attached to network aaa-network")
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks))
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
err = flags.Set(flagNetworkAdd, "id555")
|
|
assert.NilError(t, err)
|
|
err = updateService(ctx, client, flags, &svc)
|
|
assert.Error(t, err, "service is already attached to network id555")
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks))
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
err = flags.Set(flagNetworkRemove, "id999")
|
|
assert.NilError(t, err)
|
|
err = updateService(ctx, client, flags, &svc)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id555"}}, svc.TaskTemplate.Networks))
|
|
|
|
flags = newUpdateCommand(nil).Flags()
|
|
err = flags.Set(flagNetworkAdd, "mmm-network")
|
|
assert.NilError(t, err)
|
|
err = flags.Set(flagNetworkRemove, "aaa-network")
|
|
assert.NilError(t, err)
|
|
err = updateService(ctx, client, flags, &svc)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id999"}}, svc.TaskTemplate.Networks))
|
|
}
|
|
|
|
func TestUpdateMaxReplicas(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
svc := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{},
|
|
Placement: &swarm.Placement{
|
|
MaxReplicas: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set(flagMaxReplicas, "2")
|
|
err := updateService(ctx, nil, flags, &svc)
|
|
assert.NilError(t, err)
|
|
|
|
assert.DeepEqual(t, svc.TaskTemplate.Placement, &swarm.Placement{MaxReplicas: uint64(2)})
|
|
}
|
|
|
|
func TestUpdateSysCtls(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
spec map[string]string
|
|
add []string
|
|
rm []string
|
|
expected map[string]string
|
|
}{
|
|
{
|
|
name: "from scratch",
|
|
add: []string{"sysctl.zet=value-99", "sysctl.alpha=value-1"},
|
|
expected: map[string]string{"sysctl.zet": "value-99", "sysctl.alpha": "value-1"},
|
|
},
|
|
{
|
|
name: "append new",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"new.sysctl=newvalue"},
|
|
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
|
|
},
|
|
{
|
|
name: "append duplicate is a no-op",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"sysctl.one=value-1"},
|
|
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
},
|
|
{
|
|
name: "remove and append existing is a no-op",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"sysctl.one=value-1"},
|
|
rm: []string{"sysctl.one=value-1"},
|
|
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
},
|
|
{
|
|
name: "remove and append new should append",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"new.sysctl=newvalue"},
|
|
rm: []string{"new.sysctl=newvalue"},
|
|
expected: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2", "new.sysctl": "newvalue"},
|
|
},
|
|
{
|
|
name: "update existing",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"sysctl.one=newvalue"},
|
|
expected: map[string]string{"sysctl.one": "newvalue", "sysctl.two": "value-2"},
|
|
},
|
|
{
|
|
name: "update existing twice",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
add: []string{"sysctl.one=newvalue", "sysctl.one=evennewervalue"},
|
|
expected: map[string]string{"sysctl.one": "evennewervalue", "sysctl.two": "value-2"},
|
|
},
|
|
{
|
|
name: "remove all",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
rm: []string{"sysctl.one=value-1", "sysctl.two=value-2"},
|
|
expected: map[string]string{},
|
|
},
|
|
{
|
|
name: "remove by key",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
rm: []string{"sysctl.one"},
|
|
expected: map[string]string{"sysctl.two": "value-2"},
|
|
},
|
|
{
|
|
name: "remove by key and different value",
|
|
spec: map[string]string{"sysctl.one": "value-1", "sysctl.two": "value-2"},
|
|
rm: []string{"sysctl.one=anyvalueyoulike"},
|
|
expected: map[string]string{"sysctl.two": "value-2"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
svc := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{Sysctls: tc.spec},
|
|
},
|
|
}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
for _, v := range tc.add {
|
|
assert.NilError(t, flags.Set(flagSysCtlAdd, v))
|
|
}
|
|
for _, v := range tc.rm {
|
|
assert.NilError(t, flags.Set(flagSysCtlRemove, v))
|
|
}
|
|
err := updateService(ctx, &fakeClient{}, flags, &svc)
|
|
assert.NilError(t, err)
|
|
if !assert.Check(t, is.DeepEqual(svc.TaskTemplate.ContainerSpec.Sysctls, tc.expected)) {
|
|
t.Logf("expected: %v", tc.expected)
|
|
t.Logf("actual: %v", svc.TaskTemplate.ContainerSpec.Sysctls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateGetUpdatedConfigs(t *testing.T) {
|
|
// cannedConfigs is a set of configs that we'll use over and over in the
|
|
// tests. it's a map of Name to Config
|
|
cannedConfigs := map[string]*swarm.Config{
|
|
"bar": {
|
|
ID: "barID",
|
|
Spec: swarm.ConfigSpec{
|
|
Annotations: swarm.Annotations{
|
|
Name: "bar",
|
|
},
|
|
},
|
|
},
|
|
"cred": {
|
|
ID: "credID",
|
|
Spec: swarm.ConfigSpec{
|
|
Annotations: swarm.Annotations{
|
|
Name: "cred",
|
|
},
|
|
},
|
|
},
|
|
"newCred": {
|
|
ID: "newCredID",
|
|
Spec: swarm.ConfigSpec{
|
|
Annotations: swarm.Annotations{
|
|
Name: "newCred",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// cannedConfigRefs is the same thing, but with config references instead
|
|
// instead of ID, however, it just maps an arbitrary string value. this is
|
|
// so we could have multiple config refs using the same config
|
|
cannedConfigRefs := map[string]*swarm.ConfigReference{
|
|
"fooRef": {
|
|
ConfigID: "fooID",
|
|
ConfigName: "foo",
|
|
File: &swarm.ConfigReferenceFileTarget{
|
|
Name: "foo",
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 0444,
|
|
},
|
|
},
|
|
"barRef": {
|
|
ConfigID: "barID",
|
|
ConfigName: "bar",
|
|
File: &swarm.ConfigReferenceFileTarget{
|
|
Name: "bar",
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 0444,
|
|
},
|
|
},
|
|
"bazRef": {
|
|
ConfigID: "bazID",
|
|
ConfigName: "baz",
|
|
File: &swarm.ConfigReferenceFileTarget{
|
|
Name: "baz",
|
|
UID: "0",
|
|
GID: "0",
|
|
Mode: 0444,
|
|
},
|
|
},
|
|
"credRef": {
|
|
ConfigID: "credID",
|
|
ConfigName: "cred",
|
|
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
|
|
},
|
|
"newCredRef": {
|
|
ConfigID: "newCredID",
|
|
ConfigName: "newCred",
|
|
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
|
|
},
|
|
}
|
|
|
|
type flagVal [2]string
|
|
type test struct {
|
|
// the name of the subtest
|
|
name string
|
|
// flags are the flags we'll be setting
|
|
flags []flagVal
|
|
// oldConfigs are the configs that would already be on the service
|
|
// it is a slice of strings corresponding to the key of
|
|
// cannedConfigRefs
|
|
oldConfigs []string
|
|
// oldCredSpec is the credentialSpec being carried over from the old
|
|
// object
|
|
oldCredSpec *swarm.CredentialSpec
|
|
// lookupConfigs are the configs we're expecting to be listed. it is a
|
|
// slice of strings corresponding to the key of cannedConfigs
|
|
lookupConfigs []string
|
|
// expected is the configs we should get as a result. it is a slice of
|
|
// strings corresponding to the key in cannedConfigRefs
|
|
expected []string
|
|
}
|
|
|
|
testCases := []test{
|
|
{
|
|
name: "no configs added or removed",
|
|
oldConfigs: []string{"fooRef"},
|
|
expected: []string{"fooRef"},
|
|
}, {
|
|
name: "add a config",
|
|
flags: []flagVal{{"config-add", "bar"}},
|
|
oldConfigs: []string{"fooRef"},
|
|
lookupConfigs: []string{"bar"},
|
|
expected: []string{"fooRef", "barRef"},
|
|
}, {
|
|
name: "remove a config",
|
|
flags: []flagVal{{"config-rm", "bar"}},
|
|
oldConfigs: []string{"fooRef", "barRef"},
|
|
expected: []string{"fooRef"},
|
|
}, {
|
|
name: "include an old credential spec",
|
|
oldConfigs: []string{"credRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{Config: "credID"},
|
|
expected: []string{"credRef"},
|
|
}, {
|
|
name: "add a credential spec",
|
|
oldConfigs: []string{"fooRef"},
|
|
flags: []flagVal{{"credential-spec", "config://cred"}},
|
|
lookupConfigs: []string{"cred"},
|
|
expected: []string{"fooRef", "credRef"},
|
|
}, {
|
|
name: "change a credential spec",
|
|
oldConfigs: []string{"fooRef", "credRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{Config: "credID"},
|
|
flags: []flagVal{{"credential-spec", "config://newCred"}},
|
|
lookupConfigs: []string{"newCred"},
|
|
expected: []string{"fooRef", "newCredRef"},
|
|
}, {
|
|
name: "credential spec no longer config",
|
|
oldConfigs: []string{"fooRef", "credRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{Config: "credID"},
|
|
flags: []flagVal{{"credential-spec", "file://someFile"}},
|
|
lookupConfigs: []string{},
|
|
expected: []string{"fooRef"},
|
|
}, {
|
|
name: "credential spec becomes config",
|
|
oldConfigs: []string{"fooRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{File: "someFile"},
|
|
flags: []flagVal{{"credential-spec", "config://cred"}},
|
|
lookupConfigs: []string{"cred"},
|
|
expected: []string{"fooRef", "credRef"},
|
|
}, {
|
|
name: "remove credential spec",
|
|
oldConfigs: []string{"fooRef", "credRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{Config: "credID"},
|
|
flags: []flagVal{{"credential-spec", ""}},
|
|
lookupConfigs: []string{},
|
|
expected: []string{"fooRef"},
|
|
}, {
|
|
name: "just frick my stuff up",
|
|
// a more complicated test. add barRef, remove bazRef, keep fooRef,
|
|
// change credentialSpec from credRef to newCredRef
|
|
oldConfigs: []string{"fooRef", "bazRef", "credRef"},
|
|
oldCredSpec: &swarm.CredentialSpec{Config: "cred"},
|
|
flags: []flagVal{
|
|
{"config-add", "bar"},
|
|
{"config-rm", "baz"},
|
|
{"credential-spec", "config://newCred"},
|
|
},
|
|
lookupConfigs: []string{"bar", "newCred"},
|
|
expected: []string{"fooRef", "barRef", "newCredRef"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
for _, f := range tc.flags {
|
|
flags.Set(f[0], f[1])
|
|
}
|
|
|
|
// fakeConfigAPIClientList is actually defined in create_test.go,
|
|
// but we'll use it here as well
|
|
var fakeClient fakeConfigAPIClientList = func(_ context.Context, opts types.ConfigListOptions) ([]swarm.Config, error) {
|
|
names := opts.Filters.Get("name")
|
|
assert.Equal(t, len(names), len(tc.lookupConfigs))
|
|
|
|
configs := []swarm.Config{}
|
|
for _, lookup := range tc.lookupConfigs {
|
|
assert.Assert(t, is.Contains(names, lookup))
|
|
cfg, ok := cannedConfigs[lookup]
|
|
assert.Assert(t, ok)
|
|
configs = append(configs, *cfg)
|
|
}
|
|
return configs, nil
|
|
}
|
|
|
|
// build the actual set of old configs and the container spec
|
|
oldConfigs := []*swarm.ConfigReference{}
|
|
for _, config := range tc.oldConfigs {
|
|
cfg, ok := cannedConfigRefs[config]
|
|
assert.Assert(t, ok)
|
|
oldConfigs = append(oldConfigs, cfg)
|
|
}
|
|
|
|
containerSpec := &swarm.ContainerSpec{
|
|
Configs: oldConfigs,
|
|
Privileges: &swarm.Privileges{
|
|
CredentialSpec: tc.oldCredSpec,
|
|
},
|
|
}
|
|
|
|
finalConfigs, err := getUpdatedConfigs(fakeClient, flags, containerSpec)
|
|
assert.NilError(t, err)
|
|
|
|
// ensure that the finalConfigs consists of all of the expected
|
|
// configs
|
|
assert.Equal(t, len(finalConfigs), len(tc.expected),
|
|
"%v final configs, %v expected",
|
|
len(finalConfigs), len(tc.expected),
|
|
)
|
|
for _, expected := range tc.expected {
|
|
assert.Assert(t, is.Contains(finalConfigs, cannedConfigRefs[expected]))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateCredSpec(t *testing.T) {
|
|
type testCase struct {
|
|
// name is the name of the subtest
|
|
name string
|
|
// flagVal is the value we're setting flagCredentialSpec to
|
|
flagVal string
|
|
// spec is the existing serviceSpec with its configs
|
|
spec *swarm.ContainerSpec
|
|
// expected is the expected value of the credential spec after the
|
|
// function. it may be nil
|
|
expected *swarm.CredentialSpec
|
|
}
|
|
|
|
testCases := []testCase{
|
|
{
|
|
name: "add file credential spec",
|
|
flagVal: "file://somefile",
|
|
spec: &swarm.ContainerSpec{},
|
|
expected: &swarm.CredentialSpec{File: "somefile"},
|
|
}, {
|
|
name: "remove a file credential spec",
|
|
flagVal: "",
|
|
spec: &swarm.ContainerSpec{
|
|
Privileges: &swarm.Privileges{
|
|
CredentialSpec: &swarm.CredentialSpec{
|
|
File: "someFile",
|
|
},
|
|
},
|
|
},
|
|
expected: nil,
|
|
}, {
|
|
name: "remove when no CredentialSpec exists",
|
|
flagVal: "",
|
|
spec: &swarm.ContainerSpec{},
|
|
expected: nil,
|
|
}, {
|
|
name: "add a config credenital spec",
|
|
flagVal: "config://someConfigName",
|
|
spec: &swarm.ContainerSpec{
|
|
Configs: []*swarm.ConfigReference{
|
|
{
|
|
ConfigName: "someConfigName",
|
|
ConfigID: "someConfigID",
|
|
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
|
|
},
|
|
},
|
|
},
|
|
expected: &swarm.CredentialSpec{
|
|
Config: "someConfigID",
|
|
},
|
|
}, {
|
|
name: "remove a config credential spec",
|
|
flagVal: "",
|
|
spec: &swarm.ContainerSpec{
|
|
Privileges: &swarm.Privileges{
|
|
CredentialSpec: &swarm.CredentialSpec{
|
|
Config: "someConfigID",
|
|
},
|
|
},
|
|
},
|
|
expected: nil,
|
|
}, {
|
|
name: "update a config credential spec",
|
|
flagVal: "config://someConfigName",
|
|
spec: &swarm.ContainerSpec{
|
|
Configs: []*swarm.ConfigReference{
|
|
{
|
|
ConfigName: "someConfigName",
|
|
ConfigID: "someConfigID",
|
|
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
|
|
},
|
|
},
|
|
Privileges: &swarm.Privileges{
|
|
CredentialSpec: &swarm.CredentialSpec{
|
|
Config: "someDifferentConfigID",
|
|
},
|
|
},
|
|
},
|
|
expected: &swarm.CredentialSpec{
|
|
Config: "someConfigID",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
flags.Set(flagCredentialSpec, tc.flagVal)
|
|
|
|
updateCredSpecConfig(flags, tc.spec)
|
|
// handle the case where tc.spec.Privileges is nil
|
|
if tc.expected == nil {
|
|
assert.Assert(t, tc.spec.Privileges == nil || tc.spec.Privileges.CredentialSpec == nil)
|
|
return
|
|
}
|
|
|
|
assert.Assert(t, tc.spec.Privileges != nil)
|
|
assert.DeepEqual(t, tc.spec.Privileges.CredentialSpec, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateCaps(t *testing.T) {
|
|
tests := []struct {
|
|
// name is the name of the testcase
|
|
name string
|
|
// flagAdd is the value passed to --cap-add
|
|
flagAdd []string
|
|
// flagDrop is the value passed to --cap-drop
|
|
flagDrop []string
|
|
// spec is the original ContainerSpec, before being updated
|
|
spec *swarm.ContainerSpec
|
|
// expectedAdd is the set of requested caps the ContainerSpec should have once updated
|
|
expectedAdd []string
|
|
// expectedDrop is the set of dropped caps the ContainerSpec should have once updated
|
|
expectedDrop []string
|
|
}{
|
|
{
|
|
// Note that this won't be run as updateCapabilities is gated by anyChanged(flags, flagCapAdd, flagCapDrop)
|
|
name: "Empty spec, no updates",
|
|
spec: &swarm.ContainerSpec{},
|
|
},
|
|
{
|
|
// Note that this won't be run as updateCapabilities is gated by anyChanged(flags, flagCapAdd, flagCapDrop)
|
|
name: "No updates",
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
CapabilityDrop: []string{"CAP_CHOWN", "CAP_SYS_ADMIN"},
|
|
},
|
|
expectedAdd: []string{"CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
expectedDrop: []string{"CAP_CHOWN", "CAP_SYS_ADMIN"},
|
|
},
|
|
{
|
|
// Note that this won't be run as updateCapabilities is gated by anyChanged(flags, flagCapAdd, flagCapDrop)
|
|
name: "Empty updates",
|
|
flagAdd: []string{},
|
|
flagDrop: []string{},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
CapabilityDrop: []string{"CAP_CHOWN", "CAP_SYS_ADMIN"},
|
|
},
|
|
expectedAdd: []string{"CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
expectedDrop: []string{"CAP_CHOWN", "CAP_SYS_ADMIN"},
|
|
},
|
|
{
|
|
// Note that this won't be run as updateCapabilities is gated by anyChanged(flags, flagCapAdd, flagCapDrop)
|
|
name: "Normalize cap-add only",
|
|
flagAdd: []string{},
|
|
flagDrop: []string{},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"ALL", "CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
// Note that this won't be run as updateCapabilities is gated by anyChanged(flags, flagCapAdd, flagCapDrop)
|
|
name: "Normalize cap-drop only",
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityDrop: []string{"ALL", "CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
},
|
|
expectedDrop: []string{"ALL"},
|
|
},
|
|
{
|
|
name: "Add new caps",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
flagDrop: []string{},
|
|
spec: &swarm.ContainerSpec{},
|
|
expectedAdd: []string{"CAP_NET_ADMIN"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Drop new caps",
|
|
flagAdd: []string{},
|
|
flagDrop: []string{"CAP_NET_ADMIN"},
|
|
spec: &swarm.ContainerSpec{},
|
|
expectedAdd: nil,
|
|
expectedDrop: []string{"CAP_NET_ADMIN"},
|
|
},
|
|
{
|
|
name: "Add a previously dropped cap",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
flagDrop: []string{},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityDrop: []string{"CAP_NET_ADMIN"},
|
|
},
|
|
expectedAdd: nil,
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Drop a previously requested cap, and add a new one",
|
|
flagAdd: []string{"CAP_CHOWN"},
|
|
flagDrop: []string{"CAP_NET_ADMIN"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_NET_ADMIN"},
|
|
},
|
|
expectedAdd: []string{"CAP_CHOWN"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Add caps to service that has ALL caps has no effect",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"ALL"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Drop ALL caps, then add new caps to service that has ALL caps",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
flagDrop: []string{"ALL"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"ALL"},
|
|
},
|
|
expectedAdd: []string{"CAP_NET_ADMIN"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Add takes precedence on empty spec",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
flagDrop: []string{"CAP_NET_ADMIN"},
|
|
spec: &swarm.ContainerSpec{},
|
|
expectedAdd: []string{"CAP_NET_ADMIN"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Add takes precedence on existing spec",
|
|
flagAdd: []string{"CAP_NET_ADMIN"},
|
|
flagDrop: []string{"CAP_NET_ADMIN"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_NET_ADMIN"},
|
|
CapabilityDrop: []string{"CAP_NET_ADMIN"},
|
|
},
|
|
expectedAdd: []string{"CAP_NET_ADMIN"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Drop all, and add new caps",
|
|
flagAdd: []string{"CAP_CHOWN"},
|
|
flagDrop: []string{"ALL"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_NET_ADMIN", "CAP_MOUNT"},
|
|
CapabilityDrop: []string{"CAP_NET_ADMIN", "CAP_MOUNT"},
|
|
},
|
|
expectedAdd: []string{"CAP_CHOWN", "CAP_MOUNT", "CAP_NET_ADMIN"},
|
|
expectedDrop: []string{"ALL"},
|
|
},
|
|
{
|
|
name: "Add all caps",
|
|
flagAdd: []string{"ALL"},
|
|
flagDrop: []string{"CAP_NET_ADMIN", "CAP_SYS_ADMIN"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_NET_ADMIN"},
|
|
CapabilityDrop: []string{"CAP_CHOWN"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: []string{"CAP_CHOWN", "CAP_SYS_ADMIN"},
|
|
},
|
|
{
|
|
name: "Drop all, and add all",
|
|
flagAdd: []string{"ALL"},
|
|
flagDrop: []string{"ALL"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_NET_ADMIN"},
|
|
CapabilityDrop: []string{"CAP_CHOWN"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: []string{"CAP_CHOWN"},
|
|
},
|
|
{
|
|
name: "Caps are normalized and sorted",
|
|
flagAdd: []string{"bbb", "aaa", "cAp_bBb", "cAp_aAa"},
|
|
flagDrop: []string{"zzz", "yyy", "cAp_yYy", "cAp_yYy"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"ccc", "CAP_DDD"},
|
|
CapabilityDrop: []string{"www", "CAP_XXX"},
|
|
},
|
|
expectedAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
|
|
expectedDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
|
|
},
|
|
{
|
|
name: "Reset capabilities",
|
|
flagAdd: []string{"RESET"},
|
|
flagDrop: []string{"RESET"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
|
|
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
|
|
},
|
|
expectedAdd: nil,
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Reset capabilities, and update after",
|
|
flagAdd: []string{"RESET", "CAP_ADD_ONE", "CAP_FOO"},
|
|
flagDrop: []string{"RESET", "CAP_DROP_ONE", "CAP_FOO"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
|
|
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
|
|
},
|
|
expectedAdd: []string{"CAP_ADD_ONE", "CAP_FOO"},
|
|
expectedDrop: []string{"CAP_DROP_ONE"},
|
|
},
|
|
{
|
|
name: "Reset capabilities, and add ALL",
|
|
flagAdd: []string{"RESET", "ALL"},
|
|
flagDrop: []string{"RESET", "ALL"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
|
|
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: nil,
|
|
},
|
|
{
|
|
name: "Add ALL and RESET",
|
|
flagAdd: []string{"ALL", "RESET"},
|
|
flagDrop: []string{"ALL", "RESET"},
|
|
spec: &swarm.ContainerSpec{
|
|
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
|
|
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
|
|
},
|
|
expectedAdd: []string{"ALL"},
|
|
expectedDrop: nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
flags := newUpdateCommand(nil).Flags()
|
|
for _, c := range tc.flagAdd {
|
|
_ = flags.Set(flagCapAdd, c)
|
|
}
|
|
for _, c := range tc.flagDrop {
|
|
_ = flags.Set(flagCapDrop, c)
|
|
}
|
|
|
|
updateCapabilities(flags, tc.spec)
|
|
|
|
assert.DeepEqual(t, tc.spec.CapabilityAdd, tc.expectedAdd)
|
|
assert.DeepEqual(t, tc.spec.CapabilityDrop, tc.expectedDrop)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateUlimits(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
spec []*units.Ulimit
|
|
rm []string
|
|
add []string
|
|
expected []*units.Ulimit
|
|
}{
|
|
{
|
|
name: "from scratch",
|
|
add: []string{"nofile=512:1024", "core=1024:1024"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "append new",
|
|
spec: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
add: []string{"core=1024:1024"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "remove and append new should append",
|
|
spec: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
rm: []string{"nofile=512:1024"},
|
|
add: []string{"nofile=512:1024"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "update existing",
|
|
spec: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 2048, Soft: 1024},
|
|
},
|
|
add: []string{"nofile=512:1024"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "update existing twice",
|
|
spec: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 2048, Soft: 1024},
|
|
},
|
|
add: []string{"nofile=256:512", "nofile=512:1024"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "remove all",
|
|
spec: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
rm: []string{"nofile=512:1024", "core=1024:1024"},
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "remove by key",
|
|
spec: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
rm: []string{"core"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
{
|
|
name: "remove by key and different value",
|
|
spec: []*units.Ulimit{
|
|
{Name: "core", Hard: 1024, Soft: 1024},
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
rm: []string{"core=1234:5678"},
|
|
expected: []*units.Ulimit{
|
|
{Name: "nofile", Hard: 1024, Soft: 512},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
svc := swarm.ServiceSpec{
|
|
TaskTemplate: swarm.TaskSpec{
|
|
ContainerSpec: &swarm.ContainerSpec{Ulimits: tc.spec},
|
|
},
|
|
}
|
|
flags := newUpdateCommand(nil).Flags()
|
|
for _, v := range tc.add {
|
|
assert.NilError(t, flags.Set(flagUlimitAdd, v))
|
|
}
|
|
for _, v := range tc.rm {
|
|
assert.NilError(t, flags.Set(flagUlimitRemove, v))
|
|
}
|
|
err := updateService(ctx, &fakeClient{}, flags, &svc)
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, svc.TaskTemplate.ContainerSpec.Ulimits, tc.expected)
|
|
})
|
|
}
|
|
}
|