github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/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/assert"
    18  	is "gotest.tools/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.Resource{
    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.Resources{
    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.Resource{
   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.Resources{
   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, err := 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.NilError(t, err)
   199  	assert.Check(t, is.DeepEqual(expected, *endpoint))
   200  }
   201  
   202  func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
   203  	networkConfigs := networkMap{}
   204  
   205  	configs, err := convertServiceNetworks(
   206  		nil, networkConfigs, NewNamespace("foo"), "service")
   207  
   208  	expected := []swarm.NetworkAttachmentConfig{
   209  		{
   210  			Target:  "foo_default",
   211  			Aliases: []string{"service"},
   212  		},
   213  	}
   214  
   215  	assert.NilError(t, err)
   216  	assert.Check(t, is.DeepEqual(expected, configs))
   217  }
   218  
   219  func TestConvertServiceNetworks(t *testing.T) {
   220  	networkConfigs := networkMap{
   221  		"front": composetypes.NetworkConfig{
   222  			External: composetypes.External{External: true},
   223  			Name:     "fronttier",
   224  		},
   225  		"back": composetypes.NetworkConfig{},
   226  	}
   227  	networks := map[string]*composetypes.ServiceNetworkConfig{
   228  		"front": {
   229  			Aliases: []string{"something"},
   230  		},
   231  		"back": {
   232  			Aliases: []string{"other"},
   233  		},
   234  	}
   235  
   236  	configs, err := convertServiceNetworks(
   237  		networks, networkConfigs, NewNamespace("foo"), "service")
   238  
   239  	expected := []swarm.NetworkAttachmentConfig{
   240  		{
   241  			Target:  "foo_back",
   242  			Aliases: []string{"other", "service"},
   243  		},
   244  		{
   245  			Target:  "fronttier",
   246  			Aliases: []string{"something", "service"},
   247  		},
   248  	}
   249  
   250  	assert.NilError(t, err)
   251  	assert.Check(t, is.DeepEqual(expected, configs))
   252  }
   253  
   254  func TestConvertServiceNetworksCustomDefault(t *testing.T) {
   255  	networkConfigs := networkMap{
   256  		"default": composetypes.NetworkConfig{
   257  			External: composetypes.External{External: true},
   258  			Name:     "custom",
   259  		},
   260  	}
   261  	networks := map[string]*composetypes.ServiceNetworkConfig{}
   262  
   263  	configs, err := convertServiceNetworks(
   264  		networks, networkConfigs, NewNamespace("foo"), "service")
   265  
   266  	expected := []swarm.NetworkAttachmentConfig{
   267  		{
   268  			Target:  "custom",
   269  			Aliases: []string{"service"},
   270  		},
   271  	}
   272  
   273  	assert.NilError(t, err)
   274  	assert.Check(t, is.DeepEqual(expected, []swarm.NetworkAttachmentConfig(configs)))
   275  }
   276  
   277  func TestConvertDNSConfigEmpty(t *testing.T) {
   278  	dnsConfig, err := convertDNSConfig(nil, nil)
   279  
   280  	assert.NilError(t, err)
   281  	assert.Check(t, is.DeepEqual((*swarm.DNSConfig)(nil), dnsConfig))
   282  }
   283  
   284  var (
   285  	nameservers = []string{"8.8.8.8", "9.9.9.9"}
   286  	search      = []string{"dc1.example.com", "dc2.example.com"}
   287  )
   288  
   289  func TestConvertDNSConfigAll(t *testing.T) {
   290  	dnsConfig, err := convertDNSConfig(nameservers, search)
   291  	assert.NilError(t, err)
   292  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   293  		Nameservers: nameservers,
   294  		Search:      search,
   295  	}, dnsConfig))
   296  }
   297  
   298  func TestConvertDNSConfigNameservers(t *testing.T) {
   299  	dnsConfig, err := convertDNSConfig(nameservers, nil)
   300  	assert.NilError(t, err)
   301  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   302  		Nameservers: nameservers,
   303  		Search:      nil,
   304  	}, dnsConfig))
   305  }
   306  
   307  func TestConvertDNSConfigSearch(t *testing.T) {
   308  	dnsConfig, err := convertDNSConfig(nil, search)
   309  	assert.NilError(t, err)
   310  	assert.Check(t, is.DeepEqual(&swarm.DNSConfig{
   311  		Nameservers: nil,
   312  		Search:      search,
   313  	}, dnsConfig))
   314  }
   315  
   316  func TestConvertCredentialSpec(t *testing.T) {
   317  	tests := []struct {
   318  		name        string
   319  		in          composetypes.CredentialSpecConfig
   320  		out         *swarm.CredentialSpec
   321  		configs     []*swarm.ConfigReference
   322  		expectedErr string
   323  	}{
   324  		{
   325  			name: "empty",
   326  		},
   327  		{
   328  			name:        "config-and-file",
   329  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json"},
   330  			expectedErr: `invalid credential spec: cannot specify both "Config" and "File"`,
   331  		},
   332  		{
   333  			name:        "config-and-registry",
   334  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", Registry: "testing"},
   335  			expectedErr: `invalid credential spec: cannot specify both "Config" and "Registry"`,
   336  		},
   337  		{
   338  			name:        "file-and-registry",
   339  			in:          composetypes.CredentialSpecConfig{File: "somefile.json", Registry: "testing"},
   340  			expectedErr: `invalid credential spec: cannot specify both "File" and "Registry"`,
   341  		},
   342  		{
   343  			name:        "config-and-file-and-registry",
   344  			in:          composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
   345  			expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`,
   346  		},
   347  		{
   348  			name:        "missing-config-reference",
   349  			in:          composetypes.CredentialSpecConfig{Config: "missing"},
   350  			expectedErr: "invalid credential spec: spec specifies config missing, but no such config can be found",
   351  			configs: []*swarm.ConfigReference{
   352  				{
   353  					ConfigName: "someName",
   354  					ConfigID:   "missing",
   355  				},
   356  			},
   357  		},
   358  		{
   359  			name: "namespaced-config",
   360  			in:   composetypes.CredentialSpecConfig{Config: "name"},
   361  			configs: []*swarm.ConfigReference{
   362  				{
   363  					ConfigName: "namespaced-config_name",
   364  					ConfigID:   "someID",
   365  				},
   366  			},
   367  			out: &swarm.CredentialSpec{Config: "someID"},
   368  		},
   369  		{
   370  			name: "config",
   371  			in:   composetypes.CredentialSpecConfig{Config: "someName"},
   372  			configs: []*swarm.ConfigReference{
   373  				{
   374  					ConfigName: "someOtherName",
   375  					ConfigID:   "someOtherID",
   376  				}, {
   377  					ConfigName: "someName",
   378  					ConfigID:   "someID",
   379  				},
   380  			},
   381  			out: &swarm.CredentialSpec{Config: "someID"},
   382  		},
   383  		{
   384  			name: "file",
   385  			in:   composetypes.CredentialSpecConfig{File: "somefile.json"},
   386  			out:  &swarm.CredentialSpec{File: "somefile.json"},
   387  		},
   388  		{
   389  			name: "registry",
   390  			in:   composetypes.CredentialSpecConfig{Registry: "testing"},
   391  			out:  &swarm.CredentialSpec{Registry: "testing"},
   392  		},
   393  	}
   394  
   395  	for _, tc := range tests {
   396  		tc := tc
   397  		t.Run(tc.name, func(t *testing.T) {
   398  			namespace := NewNamespace(tc.name)
   399  			swarmSpec, err := convertCredentialSpec(namespace, tc.in, tc.configs)
   400  
   401  			if tc.expectedErr != "" {
   402  				assert.Error(t, err, tc.expectedErr)
   403  			} else {
   404  				assert.NilError(t, err)
   405  			}
   406  			assert.DeepEqual(t, swarmSpec, tc.out)
   407  		})
   408  	}
   409  }
   410  
   411  func TestConvertUpdateConfigOrder(t *testing.T) {
   412  	// test default behavior
   413  	updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
   414  	assert.Check(t, is.Equal("", updateConfig.Order))
   415  
   416  	// test start-first
   417  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   418  		Order: "start-first",
   419  	})
   420  	assert.Check(t, is.Equal(updateConfig.Order, "start-first"))
   421  
   422  	// test stop-first
   423  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   424  		Order: "stop-first",
   425  	})
   426  	assert.Check(t, is.Equal(updateConfig.Order, "stop-first"))
   427  }
   428  
   429  func TestConvertFileObject(t *testing.T) {
   430  	namespace := NewNamespace("testing")
   431  	config := composetypes.FileReferenceConfig{
   432  		Source: "source",
   433  		Target: "target",
   434  		UID:    "user",
   435  		GID:    "group",
   436  		Mode:   uint32Ptr(0644),
   437  	}
   438  	swarmRef, err := convertFileObject(namespace, config, lookupConfig)
   439  	assert.NilError(t, err)
   440  
   441  	expected := swarmReferenceObject{
   442  		Name: "testing_source",
   443  		File: swarmReferenceTarget{
   444  			Name: config.Target,
   445  			UID:  config.UID,
   446  			GID:  config.GID,
   447  			Mode: os.FileMode(0644),
   448  		},
   449  	}
   450  	assert.Check(t, is.DeepEqual(expected, swarmRef))
   451  }
   452  
   453  func lookupConfig(key string) (composetypes.FileObjectConfig, error) {
   454  	if key != "source" {
   455  		return composetypes.FileObjectConfig{}, errors.New("bad key")
   456  	}
   457  	return composetypes.FileObjectConfig{}, nil
   458  }
   459  
   460  func TestConvertFileObjectDefaults(t *testing.T) {
   461  	namespace := NewNamespace("testing")
   462  	config := composetypes.FileReferenceConfig{Source: "source"}
   463  	swarmRef, err := convertFileObject(namespace, config, lookupConfig)
   464  	assert.NilError(t, err)
   465  
   466  	expected := swarmReferenceObject{
   467  		Name: "testing_source",
   468  		File: swarmReferenceTarget{
   469  			Name: config.Source,
   470  			UID:  "0",
   471  			GID:  "0",
   472  			Mode: os.FileMode(0444),
   473  		},
   474  	}
   475  	assert.Check(t, is.DeepEqual(expected, swarmRef))
   476  }
   477  
   478  func TestServiceConvertsIsolation(t *testing.T) {
   479  	src := composetypes.ServiceConfig{
   480  		Isolation: "hyperv",
   481  	}
   482  	result, err := Service("1.35", Namespace{name: "foo"}, src, nil, nil, nil, nil)
   483  	assert.NilError(t, err)
   484  	assert.Check(t, is.Equal(container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation))
   485  }
   486  
   487  func TestConvertServiceSecrets(t *testing.T) {
   488  	namespace := Namespace{name: "foo"}
   489  	secrets := []composetypes.ServiceSecretConfig{
   490  		{Source: "foo_secret"},
   491  		{Source: "bar_secret"},
   492  	}
   493  	secretSpecs := map[string]composetypes.SecretConfig{
   494  		"foo_secret": {
   495  			Name: "foo_secret",
   496  		},
   497  		"bar_secret": {
   498  			Name: "bar_secret",
   499  		},
   500  	}
   501  	client := &fakeClient{
   502  		secretListFunc: func(opts types.SecretListOptions) ([]swarm.Secret, error) {
   503  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_secret"))
   504  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_secret"))
   505  			return []swarm.Secret{
   506  				{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}},
   507  				{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}},
   508  			}, nil
   509  		},
   510  	}
   511  	refs, err := convertServiceSecrets(client, namespace, secrets, secretSpecs)
   512  	assert.NilError(t, err)
   513  	expected := []*swarm.SecretReference{
   514  		{
   515  			SecretName: "bar_secret",
   516  			File: &swarm.SecretReferenceFileTarget{
   517  				Name: "bar_secret",
   518  				UID:  "0",
   519  				GID:  "0",
   520  				Mode: 0444,
   521  			},
   522  		},
   523  		{
   524  			SecretName: "foo_secret",
   525  			File: &swarm.SecretReferenceFileTarget{
   526  				Name: "foo_secret",
   527  				UID:  "0",
   528  				GID:  "0",
   529  				Mode: 0444,
   530  			},
   531  		},
   532  	}
   533  	assert.DeepEqual(t, expected, refs)
   534  }
   535  
   536  func TestConvertServiceConfigs(t *testing.T) {
   537  	namespace := Namespace{name: "foo"}
   538  	service := composetypes.ServiceConfig{
   539  		Configs: []composetypes.ServiceConfigObjConfig{
   540  			{Source: "foo_config"},
   541  			{Source: "bar_config"},
   542  		},
   543  		CredentialSpec: composetypes.CredentialSpecConfig{
   544  			Config: "baz_config",
   545  		},
   546  	}
   547  	configSpecs := map[string]composetypes.ConfigObjConfig{
   548  		"foo_config": {
   549  			Name: "foo_config",
   550  		},
   551  		"bar_config": {
   552  			Name: "bar_config",
   553  		},
   554  		"baz_config": {
   555  			Name: "baz_config",
   556  		},
   557  	}
   558  	client := &fakeClient{
   559  		configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) {
   560  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config"))
   561  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config"))
   562  			assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config"))
   563  			return []swarm.Config{
   564  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
   565  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
   566  				{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}},
   567  			}, nil
   568  		},
   569  	}
   570  	refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs)
   571  	assert.NilError(t, err)
   572  	expected := []*swarm.ConfigReference{
   573  		{
   574  			ConfigName: "bar_config",
   575  			File: &swarm.ConfigReferenceFileTarget{
   576  				Name: "bar_config",
   577  				UID:  "0",
   578  				GID:  "0",
   579  				Mode: 0444,
   580  			},
   581  		},
   582  		{
   583  			ConfigName: "baz_config",
   584  			Runtime:    &swarm.ConfigReferenceRuntimeTarget{},
   585  		},
   586  		{
   587  			ConfigName: "foo_config",
   588  			File: &swarm.ConfigReferenceFileTarget{
   589  				Name: "foo_config",
   590  				UID:  "0",
   591  				GID:  "0",
   592  				Mode: 0444,
   593  			},
   594  		},
   595  	}
   596  	assert.DeepEqual(t, expected, refs)
   597  }
   598  
   599  type fakeClient struct {
   600  	client.Client
   601  	secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error)
   602  	configListFunc func(types.ConfigListOptions) ([]swarm.Config, error)
   603  }
   604  
   605  func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
   606  	if c.secretListFunc != nil {
   607  		return c.secretListFunc(options)
   608  	}
   609  	return []swarm.Secret{}, nil
   610  }
   611  
   612  func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
   613  	if c.configListFunc != nil {
   614  		return c.configListFunc(options)
   615  	}
   616  	return []swarm.Config{}, nil
   617  }
   618  
   619  func TestConvertUpdateConfigParallelism(t *testing.T) {
   620  	parallel := uint64(4)
   621  
   622  	// test default behavior
   623  	updateConfig := convertUpdateConfig(&composetypes.UpdateConfig{})
   624  	assert.Check(t, is.Equal(uint64(1), updateConfig.Parallelism))
   625  
   626  	// Non default value
   627  	updateConfig = convertUpdateConfig(&composetypes.UpdateConfig{
   628  		Parallelism: &parallel,
   629  	})
   630  	assert.Check(t, is.Equal(parallel, updateConfig.Parallelism))
   631  }