github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/compose/convert/service_test.go (about)

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