github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/compose/convert/service_test.go (about)

     1  package convert
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"sort"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	composetypes "github.com/docker/cli/cli/compose/types"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/api/types/swarm"
    15  	"github.com/docker/docker/client"
    16  	"github.com/pkg/errors"
    17  	"gotest.tools/v3/assert"
    18  	is "gotest.tools/v3/assert/cmp"
    19  )
    20  
    21  func TestConvertRestartPolicyFromNone(t *testing.T) {
    22  	policy, err := convertRestartPolicy("no", nil)
    23  	assert.NilError(t, err)
    24  	assert.Check(t, is.DeepEqual((*swarm.RestartPolicy)(nil), policy))
    25  }
    26  
    27  func TestConvertRestartPolicyFromUnknown(t *testing.T) {
    28  	_, err := convertRestartPolicy("unknown", nil)
    29  	assert.Error(t, err, "unknown restart policy: unknown")
    30  }
    31  
    32  func TestConvertRestartPolicyFromAlways(t *testing.T) {
    33  	policy, err := convertRestartPolicy("always", nil)
    34  	expected := &swarm.RestartPolicy{
    35  		Condition: swarm.RestartPolicyConditionAny,
    36  	}
    37  	assert.NilError(t, err)
    38  	assert.Check(t, is.DeepEqual(expected, policy))
    39  }
    40  
    41  func TestConvertRestartPolicyFromFailure(t *testing.T) {
    42  	policy, err := convertRestartPolicy("on-failure:4", nil)
    43  	attempts := uint64(4)
    44  	expected := &swarm.RestartPolicy{
    45  		Condition:   swarm.RestartPolicyConditionOnFailure,
    46  		MaxAttempts: &attempts,
    47  	}
    48  	assert.NilError(t, err)
    49  	assert.Check(t, is.DeepEqual(expected, policy))
    50  }
    51  
    52  func strPtr(val string) *string {
    53  	return &val
    54  }
    55  
    56  func TestConvertEnvironment(t *testing.T) {
    57  	source := map[string]*string{
    58  		"foo": strPtr("bar"),
    59  		"key": strPtr("value"),
    60  	}
    61  	env := convertEnvironment(source)
    62  	sort.Strings(env)
    63  	assert.Check(t, is.DeepEqual([]string{"foo=bar", "key=value"}, env))
    64  }
    65  
    66  func TestConvertExtraHosts(t *testing.T) {
    67  	source := composetypes.HostsList{
    68  		"zulu:127.0.0.2",
    69  		"alpha:127.0.0.1",
    70  		"zulu:ff02::1",
    71  	}
    72  	assert.Check(t, is.DeepEqual([]string{"127.0.0.2 zulu", "127.0.0.1 alpha", "ff02::1 zulu"}, convertExtraHosts(source)))
    73  }
    74  
    75  func TestConvertResourcesFull(t *testing.T) {
    76  	source := composetypes.Resources{
    77  		Limits: &composetypes.ResourceLimit{
    78  			NanoCPUs:    "0.003",
    79  			MemoryBytes: composetypes.UnitBytes(300000000),
    80  		},
    81  		Reservations: &composetypes.Resource{
    82  			NanoCPUs:    "0.002",
    83  			MemoryBytes: composetypes.UnitBytes(200000000),
    84  		},
    85  	}
    86  	resources, err := convertResources(source)
    87  	assert.NilError(t, err)
    88  
    89  	expected := &swarm.ResourceRequirements{
    90  		Limits: &swarm.Limit{
    91  			NanoCPUs:    3000000,
    92  			MemoryBytes: 300000000,
    93  		},
    94  		Reservations: &swarm.Resources{
    95  			NanoCPUs:    2000000,
    96  			MemoryBytes: 200000000,
    97  		},
    98  	}
    99  	assert.Check(t, is.DeepEqual(expected, resources))
   100  }
   101  
   102  func TestConvertResourcesOnlyMemory(t *testing.T) {
   103  	source := composetypes.Resources{
   104  		Limits: &composetypes.ResourceLimit{
   105  			MemoryBytes: composetypes.UnitBytes(300000000),
   106  		},
   107  		Reservations: &composetypes.Resource{
   108  			MemoryBytes: composetypes.UnitBytes(200000000),
   109  		},
   110  	}
   111  	resources, err := convertResources(source)
   112  	assert.NilError(t, err)
   113  
   114  	expected := &swarm.ResourceRequirements{
   115  		Limits: &swarm.Limit{
   116  			MemoryBytes: 300000000,
   117  		},
   118  		Reservations: &swarm.Resources{
   119  			MemoryBytes: 200000000,
   120  		},
   121  	}
   122  	assert.Check(t, is.DeepEqual(expected, resources))
   123  }
   124  
   125  func TestConvertHealthcheck(t *testing.T) {
   126  	retries := uint64(10)
   127  	timeout := composetypes.Duration(30 * time.Second)
   128  	interval := composetypes.Duration(2 * time.Millisecond)
   129  	source := &composetypes.HealthCheckConfig{
   130  		Test:     []string{"EXEC", "touch", "/foo"},
   131  		Timeout:  &timeout,
   132  		Interval: &interval,
   133  		Retries:  &retries,
   134  	}
   135  	expected := &container.HealthConfig{
   136  		Test:     source.Test,
   137  		Timeout:  time.Duration(timeout),
   138  		Interval: time.Duration(interval),
   139  		Retries:  10,
   140  	}
   141  
   142  	healthcheck, err := convertHealthcheck(source)
   143  	assert.NilError(t, err)
   144  	assert.Check(t, is.DeepEqual(expected, healthcheck))
   145  }
   146  
   147  func TestConvertHealthcheckDisable(t *testing.T) {
   148  	source := &composetypes.HealthCheckConfig{Disable: true}
   149  	expected := &container.HealthConfig{
   150  		Test: []string{"NONE"},
   151  	}
   152  
   153  	healthcheck, err := convertHealthcheck(source)
   154  	assert.NilError(t, err)
   155  	assert.Check(t, is.DeepEqual(expected, healthcheck))
   156  }
   157  
   158  func TestConvertHealthcheckDisableWithTest(t *testing.T) {
   159  	source := &composetypes.HealthCheckConfig{
   160  		Disable: true,
   161  		Test:    []string{"EXEC", "touch"},
   162  	}
   163  	_, err := convertHealthcheck(source)
   164  	assert.Error(t, err, "test and disable can't be set at the same time")
   165  }
   166  
   167  func TestConvertEndpointSpec(t *testing.T) {
   168  	source := []composetypes.ServicePortConfig{
   169  		{
   170  			Protocol:  "udp",
   171  			Target:    53,
   172  			Published: 1053,
   173  			Mode:      "host",
   174  		},
   175  		{
   176  			Target:    8080,
   177  			Published: 80,
   178  		},
   179  	}
   180  	endpoint := convertEndpointSpec("vip", source)
   181  
   182  	expected := swarm.EndpointSpec{
   183  		Mode: swarm.ResolutionMode(strings.ToLower("vip")),
   184  		Ports: []swarm.PortConfig{
   185  			{
   186  				TargetPort:    8080,
   187  				PublishedPort: 80,
   188  			},
   189  			{
   190  				Protocol:      "udp",
   191  				TargetPort:    53,
   192  				PublishedPort: 1053,
   193  				PublishMode:   "host",
   194  			},
   195  		},
   196  	}
   197  
   198  	assert.Check(t, is.DeepEqual(expected, *endpoint))
   199  }
   200  
   201  func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
   202  	networkConfigs := networkMap{}
   203  
   204  	configs, err := convertServiceNetworks(
   205  		nil, networkConfigs, NewNamespace("foo"), "service")
   206  
   207  	expected := []swarm.NetworkAttachmentConfig{
   208  		{
   209  			Target:  "foo_default",
   210  			Aliases: []string{"service"},
   211  		},
   212  	}
   213  
   214  	assert.NilError(t, err)
   215  	assert.Check(t, is.DeepEqual(expected, configs))
   216  }
   217  
   218  func TestConvertServiceNetworks(t *testing.T) {
   219  	networkConfigs := networkMap{
   220  		"front": composetypes.NetworkConfig{
   221  			External: composetypes.External{External: true},
   222  			Name:     "fronttier",
   223  		},
   224  		"back": composetypes.NetworkConfig{},
   225  	}
   226  	networks := map[string]*composetypes.ServiceNetworkConfig{
   227  		"front": {
   228  			Aliases: []string{"something"},
   229  		},
   230  		"back": {
   231  			Aliases: []string{"other"},
   232  		},
   233  	}
   234  
   235  	configs, err := convertServiceNetworks(
   236  		networks, networkConfigs, NewNamespace("foo"), "service")
   237  
   238  	expected := []swarm.NetworkAttachmentConfig{
   239  		{
   240  			Target:  "foo_back",
   241  			Aliases: []string{"other", "service"},
   242  		},
   243  		{
   244  			Target:  "fronttier",
   245  			Aliases: []string{"something", "service"},
   246  		},
   247  	}
   248  
   249  	assert.NilError(t, err)
   250  	assert.Check(t, is.DeepEqual(expected, configs))
   251  }
   252  
   253  func TestConvertServiceNetworksCustomDefault(t *testing.T) {
   254  	networkConfigs := networkMap{
   255  		"default": composetypes.NetworkConfig{
   256  			External: composetypes.External{External: true},
   257  			Name:     "custom",
   258  		},
   259  	}
   260  	networks := map[string]*composetypes.ServiceNetworkConfig{}
   261  
   262  	configs, err := convertServiceNetworks(
   263  		networks, networkConfigs, NewNamespace("foo"), "service")
   264  
   265  	expected := []swarm.NetworkAttachmentConfig{
   266  		{
   267  			Target:  "custom",
   268  			Aliases: []string{"service"},
   269  		},
   270  	}
   271  
   272  	assert.NilError(t, err)
   273  	assert.Check(t, is.DeepEqual(expected, configs))
   274  }
   275  
   276  func TestConvertDNSConfigEmpty(t *testing.T) {
   277  	dnsConfig := convertDNSConfig(nil, nil)
   278  	assert.Check(t, is.DeepEqual((*swarm.DNSConfig)(nil), dnsConfig))
   279  }
   280  
   281  var (
   282  	nameservers = []string{"8.8.8.8", "9.9.9.9"}
   283  	search      = []string{"dc1.example.com", "dc2.example.com"}
   284  )
   285  
   286  func TestConvertDNSConfigAll(t *testing.T) {
   287  	dnsConfig := convertDNSConfig(nameservers, search)
   288  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   289  		Nameservers: nameservers,
   290  		Search:      search,
   291  	}, dnsConfig))
   292  }
   293  
   294  func TestConvertDNSConfigNameservers(t *testing.T) {
   295  	dnsConfig := convertDNSConfig(nameservers, nil)
   296  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   297  		Nameservers: nameservers,
   298  		Search:      nil,
   299  	}, dnsConfig))
   300  }
   301  
   302  func TestConvertDNSConfigSearch(t *testing.T) {
   303  	dnsConfig := convertDNSConfig(nil, search)
   304  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   305  		Nameservers: nil,
   306  		Search:      search,
   307  	}, dnsConfig))
   308  }
   309  
   310  func TestConvertCredentialSpec(t *testing.T) {
   311  	tests := []struct {
   312  		name        string
   313  		in          composetypes.CredentialSpecConfig
   314  		out         *swarm.CredentialSpec
   315  		configs     []*swarm.ConfigReference
   316  		expectedErr string
   317  	}{
   318  		{
   319  			name: "empty",
   320  		},
   321  		{
   322  			name:        "config-and-file",
   323  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json"},
   324  			expectedErr: `invalid credential spec: cannot specify both "Config" and "File"`,
   325  		},
   326  		{
   327  			name:        "config-and-registry",
   328  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", Registry: "testing"},
   329  			expectedErr: `invalid credential spec: cannot specify both "Config" and "Registry"`,
   330  		},
   331  		{
   332  			name:        "file-and-registry",
   333  			in:          composetypes.CredentialSpecConfig{File: "somefile.json", Registry: "testing"},
   334  			expectedErr: `invalid credential spec: cannot specify both "File" and "Registry"`,
   335  		},
   336  		{
   337  			name:        "config-and-file-and-registry",
   338  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
   339  			expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`,
   340  		},
   341  		{
   342  			name:        "missing-config-reference",
   343  			in:          composetypes.CredentialSpecConfig{Config: "missing"},
   344  			expectedErr: "invalid credential spec: spec specifies config missing, but no such config can be found",
   345  			configs: []*swarm.ConfigReference{
   346  				{
   347  					ConfigName: "someName",
   348  					ConfigID:   "missing",
   349  				},
   350  			},
   351  		},
   352  		{
   353  			name: "namespaced-config",
   354  			in:   composetypes.CredentialSpecConfig{Config: "name"},
   355  			configs: []*swarm.ConfigReference{
   356  				{
   357  					ConfigName: "namespaced-config_name",
   358  					ConfigID:   "someID",
   359  				},
   360  			},
   361  			out: &swarm.CredentialSpec{Config: "someID"},
   362  		},
   363  		{
   364  			name: "config",
   365  			in:   composetypes.CredentialSpecConfig{Config: "someName"},
   366  			configs: []*swarm.ConfigReference{
   367  				{
   368  					ConfigName: "someOtherName",
   369  					ConfigID:   "someOtherID",
   370  				}, {
   371  					ConfigName: "someName",
   372  					ConfigID:   "someID",
   373  				},
   374  			},
   375  			out: &swarm.CredentialSpec{Config: "someID"},
   376  		},
   377  		{
   378  			name: "file",
   379  			in:   composetypes.CredentialSpecConfig{File: "somefile.json"},
   380  			out:  &swarm.CredentialSpec{File: "somefile.json"},
   381  		},
   382  		{
   383  			name: "registry",
   384  			in:   composetypes.CredentialSpecConfig{Registry: "testing"},
   385  			out:  &swarm.CredentialSpec{Registry: "testing"},
   386  		},
   387  	}
   388  
   389  	for _, tc := range tests {
   390  		tc := tc
   391  		t.Run(tc.name, func(t *testing.T) {
   392  			namespace := NewNamespace(tc.name)
   393  			swarmSpec, err := convertCredentialSpec(namespace, tc.in, tc.configs)
   394  
   395  			if tc.expectedErr != "" {
   396  				assert.Error(t, err, tc.expectedErr)
   397  			} else {
   398  				assert.NilError(t, err)
   399  			}
   400  			assert.DeepEqual(t, swarmSpec, tc.out)
   401  		})
   402  	}
   403  }
   404  
   405  func TestConvertUpdateConfigOrder(t *testing.T) {
   406  	// test default behavior
   407  	updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
   408  	assert.Check(t, is.Equal("", updateConfig.Order))
   409  
   410  	// test start-first
   411  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   412  		Order: "start-first",
   413  	})
   414  	assert.Check(t, is.Equal(updateConfig.Order, "start-first"))
   415  
   416  	// test stop-first
   417  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   418  		Order: "stop-first",
   419  	})
   420  	assert.Check(t, is.Equal(updateConfig.Order, "stop-first"))
   421  }
   422  
   423  func TestConvertFileObject(t *testing.T) {
   424  	namespace := NewNamespace("testing")
   425  	config := composetypes.FileReferenceConfig{
   426  		Source: "source",
   427  		Target: "target",
   428  		UID:    "user",
   429  		GID:    "group",
   430  		Mode:   uint32Ptr(0644),
   431  	}
   432  	swarmRef, err := convertFileObject(namespace, config, lookupConfig)
   433  	assert.NilError(t, err)
   434  
   435  	expected := swarmReferenceObject{
   436  		Name: "testing_source",
   437  		File: swarmReferenceTarget{
   438  			Name: config.Target,
   439  			UID:  config.UID,
   440  			GID:  config.GID,
   441  			Mode: os.FileMode(0644),
   442  		},
   443  	}
   444  	assert.Check(t, is.DeepEqual(expected, swarmRef))
   445  }
   446  
   447  func lookupConfig(key string) (composetypes.FileObjectConfig, error) {
   448  	if key != "source" {
   449  		return composetypes.FileObjectConfig{}, errors.New("bad key")
   450  	}
   451  	return composetypes.FileObjectConfig{}, nil
   452  }
   453  
   454  func TestConvertFileObjectDefaults(t *testing.T) {
   455  	namespace := NewNamespace("testing")
   456  	config := composetypes.FileReferenceConfig{Source: "source"}
   457  	swarmRef, err := convertFileObject(namespace, config, lookupConfig)
   458  	assert.NilError(t, err)
   459  
   460  	expected := swarmReferenceObject{
   461  		Name: "testing_source",
   462  		File: swarmReferenceTarget{
   463  			Name: config.Source,
   464  			UID:  "0",
   465  			GID:  "0",
   466  			Mode: os.FileMode(0444),
   467  		},
   468  	}
   469  	assert.Check(t, is.DeepEqual(expected, swarmRef))
   470  }
   471  
   472  func TestServiceConvertsIsolation(t *testing.T) {
   473  	src := composetypes.ServiceConfig{
   474  		Isolation: "hyperv",
   475  	}
   476  	result, err := Service("1.35", Namespace{name: "foo"}, src, nil, nil, nil, nil)
   477  	assert.NilError(t, err)
   478  	assert.Check(t, is.Equal(container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation))
   479  }
   480  
   481  func TestConvertServiceSecrets(t *testing.T) {
   482  	namespace := Namespace{name: "foo"}
   483  	secrets := []composetypes.ServiceSecretConfig{
   484  		{Source: "foo_secret"},
   485  		{Source: "bar_secret"},
   486  	}
   487  	secretSpecs := map[string]composetypes.SecretConfig{
   488  		"foo_secret": {
   489  			Name: "foo_secret",
   490  		},
   491  		"bar_secret": {
   492  			Name: "bar_secret",
   493  		},
   494  	}
   495  	client := &fakeClient{
   496  		secretListFunc: func(opts types.SecretListOptions) ([]swarm.Secret, error) {
   497  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_secret"))
   498  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_secret"))
   499  			return []swarm.Secret{
   500  				{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}},
   501  				{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}},
   502  			}, nil
   503  		},
   504  	}
   505  	refs, err := convertServiceSecrets(client, namespace, secrets, secretSpecs)
   506  	assert.NilError(t, err)
   507  	expected := []*swarm.SecretReference{
   508  		{
   509  			SecretName: "bar_secret",
   510  			File: &swarm.SecretReferenceFileTarget{
   511  				Name: "bar_secret",
   512  				UID:  "0",
   513  				GID:  "0",
   514  				Mode: 0444,
   515  			},
   516  		},
   517  		{
   518  			SecretName: "foo_secret",
   519  			File: &swarm.SecretReferenceFileTarget{
   520  				Name: "foo_secret",
   521  				UID:  "0",
   522  				GID:  "0",
   523  				Mode: 0444,
   524  			},
   525  		},
   526  	}
   527  	assert.DeepEqual(t, expected, refs)
   528  }
   529  
   530  func TestConvertServiceConfigs(t *testing.T) {
   531  	namespace := Namespace{name: "foo"}
   532  	service := composetypes.ServiceConfig{
   533  		Configs: []composetypes.ServiceConfigObjConfig{
   534  			{Source: "foo_config"},
   535  			{Source: "bar_config"},
   536  		},
   537  		CredentialSpec: composetypes.CredentialSpecConfig{
   538  			Config: "baz_config",
   539  		},
   540  	}
   541  	configSpecs := map[string]composetypes.ConfigObjConfig{
   542  		"foo_config": {
   543  			Name: "foo_config",
   544  		},
   545  		"bar_config": {
   546  			Name: "bar_config",
   547  		},
   548  		"baz_config": {
   549  			Name: "baz_config",
   550  		},
   551  	}
   552  	client := &fakeClient{
   553  		configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) {
   554  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config"))
   555  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config"))
   556  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config"))
   557  			return []swarm.Config{
   558  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
   559  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
   560  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}},
   561  			}, nil
   562  		},
   563  	}
   564  	refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs)
   565  	assert.NilError(t, err)
   566  	expected := []*swarm.ConfigReference{
   567  		{
   568  			ConfigName: "bar_config",
   569  			File: &swarm.ConfigReferenceFileTarget{
   570  				Name: "bar_config",
   571  				UID:  "0",
   572  				GID:  "0",
   573  				Mode: 0444,
   574  			},
   575  		},
   576  		{
   577  			ConfigName: "baz_config",
   578  			Runtime:    &swarm.ConfigReferenceRuntimeTarget{},
   579  		},
   580  		{
   581  			ConfigName: "foo_config",
   582  			File: &swarm.ConfigReferenceFileTarget{
   583  				Name: "foo_config",
   584  				UID:  "0",
   585  				GID:  "0",
   586  				Mode: 0444,
   587  			},
   588  		},
   589  	}
   590  	assert.DeepEqual(t, expected, refs)
   591  }
   592  
   593  type fakeClient struct {
   594  	client.Client
   595  	secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error)
   596  	configListFunc func(types.ConfigListOptions) ([]swarm.Config, error)
   597  }
   598  
   599  func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
   600  	if c.secretListFunc != nil {
   601  		return c.secretListFunc(options)
   602  	}
   603  	return []swarm.Secret{}, nil
   604  }
   605  
   606  func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
   607  	if c.configListFunc != nil {
   608  		return c.configListFunc(options)
   609  	}
   610  	return []swarm.Config{}, nil
   611  }
   612  
   613  func TestConvertUpdateConfigParallelism(t *testing.T) {
   614  	parallel := uint64(4)
   615  
   616  	// test default behavior
   617  	updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
   618  	assert.Check(t, is.Equal(uint64(1), updateConfig.Parallelism))
   619  
   620  	// Non default value
   621  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   622  		Parallelism: &parallel,
   623  	})
   624  	assert.Check(t, is.Equal(parallel, updateConfig.Parallelism))
   625  }
   626  
   627  func TestConvertServiceCapAddAndCapDrop(t *testing.T) {
   628  	tests := []struct {
   629  		title   string
   630  		in, out composetypes.ServiceConfig
   631  	}{
   632  		{
   633  			title: "default behavior",
   634  		},
   635  		{
   636  			title: "some values",
   637  			in: composetypes.ServiceConfig{
   638  				CapAdd:  []string{"SYS_NICE", "CAP_NET_ADMIN"},
   639  				CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
   640  			},
   641  			out: composetypes.ServiceConfig{
   642  				CapAdd:  []string{"CAP_NET_ADMIN", "CAP_SYS_NICE"},
   643  				CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID"},
   644  			},
   645  		},
   646  		{
   647  			title: "adding ALL capabilities",
   648  			in: composetypes.ServiceConfig{
   649  				CapAdd:  []string{"ALL", "CAP_NET_ADMIN"},
   650  				CapDrop: []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
   651  			},
   652  			out: composetypes.ServiceConfig{
   653  				CapAdd:  []string{"ALL"},
   654  				CapDrop: []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"},
   655  			},
   656  		},
   657  		{
   658  			title: "dropping ALL capabilities",
   659  			in: composetypes.ServiceConfig{
   660  				CapAdd:  []string{"CHOWN", "CAP_NET_ADMIN", "DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER"},
   661  				CapDrop: []string{"ALL", "CAP_NET_ADMIN", "CAP_FOO"},
   662  			},
   663  			out: composetypes.ServiceConfig{
   664  				CapAdd:  []string{"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_NET_ADMIN"},
   665  				CapDrop: []string{"ALL"},
   666  			},
   667  		},
   668  	}
   669  	for _, tc := range tests {
   670  		tc := tc
   671  		t.Run(tc.title, func(t *testing.T) {
   672  			result, err := Service("1.41", Namespace{name: "foo"}, tc.in, nil, nil, nil, nil)
   673  			assert.NilError(t, err)
   674  			assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityAdd, tc.out.CapAdd))
   675  			assert.Check(t, is.DeepEqual(result.TaskTemplate.ContainerSpec.CapabilityDrop, tc.out.CapDrop))
   676  		})
   677  	}
   678  }