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: ¶llel, 629 }) 630 assert.Check(t, is.Equal(parallel, updateConfig.Parallelism)) 631 }