Browse code

Move ConvertService to composetransform package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2016/12/01 07:38:40
Showing 8 changed files
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"github.com/docker/docker/api/types/filters"
8 8
 	"github.com/docker/docker/api/types/swarm"
9 9
 	"github.com/docker/docker/client"
10
+	"github.com/docker/docker/opts"
10 11
 	"github.com/docker/docker/pkg/composetransform"
11 12
 )
12 13
 
... ...
@@ -16,6 +17,18 @@ func getStackFilter(namespace string) filters.Args {
16 16
 	return filter
17 17
 }
18 18
 
19
+func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {
20
+	filter := opt.Value()
21
+	filter.Add("label", composetransform.LabelNamespace+"="+namespace)
22
+	return filter
23
+}
24
+
25
+func getAllStacksFilter() filters.Args {
26
+	filter := filters.NewArgs()
27
+	filter.Add("label", composetransform.LabelNamespace)
28
+	return filter
29
+}
30
+
19 31
 func getServices(
20 32
 	ctx context.Context,
21 33
 	apiclient client.APIClient,
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"os"
8 8
 	"sort"
9 9
 	"strings"
10
-	"time"
11 10
 
12 11
 	"github.com/spf13/cobra"
13 12
 	"golang.org/x/net/context"
... ...
@@ -15,15 +14,11 @@ import (
15 15
 	"github.com/aanand/compose-file/loader"
16 16
 	composetypes "github.com/aanand/compose-file/types"
17 17
 	"github.com/docker/docker/api/types"
18
-	"github.com/docker/docker/api/types/container"
19 18
 	"github.com/docker/docker/api/types/swarm"
20 19
 	"github.com/docker/docker/cli"
21 20
 	"github.com/docker/docker/cli/command"
22 21
 	dockerclient "github.com/docker/docker/client"
23
-	"github.com/docker/docker/opts"
24 22
 	"github.com/docker/docker/pkg/composetransform"
25
-	runconfigopts "github.com/docker/docker/runconfig/opts"
26
-	"github.com/docker/go-connections/nat"
27 23
 )
28 24
 
29 25
 const (
... ...
@@ -129,7 +124,7 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
129 129
 	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
130 130
 		return err
131 131
 	}
132
-	services, err := convertServices(namespace, config)
132
+	services, err := composetransform.ConvertServices(namespace, config)
133 133
 	if err != nil {
134 134
 		return err
135 135
 	}
... ...
@@ -237,54 +232,17 @@ func createNetworks(
237 237
 	return nil
238 238
 }
239 239
 
240
-func convertServiceNetworks(
241
-	networks map[string]*composetypes.ServiceNetworkConfig,
242
-	networkConfigs map[string]composetypes.NetworkConfig,
243
-	namespace composetransform.Namespace,
244
-	name string,
245
-) ([]swarm.NetworkAttachmentConfig, error) {
246
-	if len(networks) == 0 {
247
-		return []swarm.NetworkAttachmentConfig{
248
-			{
249
-				Target:  namespace.scope("default"),
250
-				Aliases: []string{name},
251
-			},
252
-		}, nil
253
-	}
254
-
255
-	nets := []swarm.NetworkAttachmentConfig{}
256
-	for networkName, network := range networks {
257
-		networkConfig, ok := networkConfigs[networkName]
258
-		if !ok {
259
-			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName)
260
-		}
261
-		var aliases []string
262
-		if network != nil {
263
-			aliases = network.Aliases
264
-		}
265
-		target := namespace.scope(networkName)
266
-		if networkConfig.External.External {
267
-			target = networkName
268
-		}
269
-		nets = append(nets, swarm.NetworkAttachmentConfig{
270
-			Target:  target,
271
-			Aliases: append(aliases, name),
272
-		})
273
-	}
274
-	return nets, nil
275
-}
276
-
277 240
 func deployServices(
278 241
 	ctx context.Context,
279 242
 	dockerCli *command.DockerCli,
280 243
 	services map[string]swarm.ServiceSpec,
281
-	namespace namespace,
244
+	namespace composetransform.Namespace,
282 245
 	sendAuth bool,
283 246
 ) error {
284 247
 	apiClient := dockerCli.Client()
285 248
 	out := dockerCli.Out()
286 249
 
287
-	existingServices, err := getServices(ctx, apiClient, namespace.name)
250
+	existingServices, err := getServices(ctx, apiClient, namespace.Name())
288 251
 	if err != nil {
289 252
 		return err
290 253
 	}
... ...
@@ -295,7 +253,7 @@ func deployServices(
295 295
 	}
296 296
 
297 297
 	for internalName, serviceSpec := range services {
298
-		name := namespace.scope(internalName)
298
+		name := namespace.Scope(internalName)
299 299
 
300 300
 		encodedAuth := ""
301 301
 		if sendAuth {
... ...
@@ -343,280 +301,3 @@ func deployServices(
343 343
 
344 344
 	return nil
345 345
 }
346
-
347
-func convertServices(
348
-	namespace namespace,
349
-	config *composetypes.Config,
350
-) (map[string]swarm.ServiceSpec, error) {
351
-	result := make(map[string]swarm.ServiceSpec)
352
-
353
-	services := config.Services
354
-	volumes := config.Volumes
355
-	networks := config.Networks
356
-
357
-	for _, service := range services {
358
-		serviceSpec, err := convertService(namespace, service, networks, volumes)
359
-		if err != nil {
360
-			return nil, err
361
-		}
362
-		result[service.Name] = serviceSpec
363
-	}
364
-
365
-	return result, nil
366
-}
367
-
368
-func convertService(
369
-	namespace namespace,
370
-	service composetypes.ServiceConfig,
371
-	networkConfigs map[string]composetypes.NetworkConfig,
372
-	volumes map[string]composetypes.VolumeConfig,
373
-) (swarm.ServiceSpec, error) {
374
-	name := namespace.scope(service.Name)
375
-
376
-	endpoint, err := convertEndpointSpec(service.Ports)
377
-	if err != nil {
378
-		return swarm.ServiceSpec{}, err
379
-	}
380
-
381
-	mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
382
-	if err != nil {
383
-		return swarm.ServiceSpec{}, err
384
-	}
385
-
386
-	mounts, err := composetransform.ConvertVolumes(service.Volumes, volumes, namespace)
387
-	if err != nil {
388
-		// TODO: better error message (include service name)
389
-		return swarm.ServiceSpec{}, err
390
-	}
391
-
392
-	resources, err := convertResources(service.Deploy.Resources)
393
-	if err != nil {
394
-		return swarm.ServiceSpec{}, err
395
-	}
396
-
397
-	restartPolicy, err := convertRestartPolicy(
398
-		service.Restart, service.Deploy.RestartPolicy)
399
-	if err != nil {
400
-		return swarm.ServiceSpec{}, err
401
-	}
402
-
403
-	healthcheck, err := convertHealthcheck(service.HealthCheck)
404
-	if err != nil {
405
-		return swarm.ServiceSpec{}, err
406
-	}
407
-
408
-	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
409
-	if err != nil {
410
-		return swarm.ServiceSpec{}, err
411
-	}
412
-
413
-	var logDriver *swarm.Driver
414
-	if service.Logging != nil {
415
-		logDriver = &swarm.Driver{
416
-			Name:    service.Logging.Driver,
417
-			Options: service.Logging.Options,
418
-		}
419
-	}
420
-
421
-	serviceSpec := swarm.ServiceSpec{
422
-		Annotations: swarm.Annotations{
423
-			Name:   name,
424
-			Labels: getStackLabels(namespace.name, service.Deploy.Labels),
425
-		},
426
-		TaskTemplate: swarm.TaskSpec{
427
-			ContainerSpec: swarm.ContainerSpec{
428
-				Image:           service.Image,
429
-				Command:         service.Entrypoint,
430
-				Args:            service.Command,
431
-				Hostname:        service.Hostname,
432
-				Hosts:           convertExtraHosts(service.ExtraHosts),
433
-				Healthcheck:     healthcheck,
434
-				Env:             convertEnvironment(service.Environment),
435
-				Labels:          getStackLabels(namespace.name, service.Labels),
436
-				Dir:             service.WorkingDir,
437
-				User:            service.User,
438
-				Mounts:          mounts,
439
-				StopGracePeriod: service.StopGracePeriod,
440
-				TTY:             service.Tty,
441
-				OpenStdin:       service.StdinOpen,
442
-			},
443
-			LogDriver:     logDriver,
444
-			Resources:     resources,
445
-			RestartPolicy: restartPolicy,
446
-			Placement: &swarm.Placement{
447
-				Constraints: service.Deploy.Placement.Constraints,
448
-			},
449
-		},
450
-		EndpointSpec: endpoint,
451
-		Mode:         mode,
452
-		Networks:     networks,
453
-		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
454
-	}
455
-
456
-	return serviceSpec, nil
457
-}
458
-
459
-func convertExtraHosts(extraHosts map[string]string) []string {
460
-	hosts := []string{}
461
-	for host, ip := range extraHosts {
462
-		hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
463
-	}
464
-	return hosts
465
-}
466
-
467
-func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
468
-	if healthcheck == nil {
469
-		return nil, nil
470
-	}
471
-	var (
472
-		err               error
473
-		timeout, interval time.Duration
474
-		retries           int
475
-	)
476
-	if healthcheck.Disable {
477
-		if len(healthcheck.Test) != 0 {
478
-			return nil, fmt.Errorf("command and disable key can't be set at the same time")
479
-		}
480
-		return &container.HealthConfig{
481
-			Test: []string{"NONE"},
482
-		}, nil
483
-
484
-	}
485
-	if healthcheck.Timeout != "" {
486
-		timeout, err = time.ParseDuration(healthcheck.Timeout)
487
-		if err != nil {
488
-			return nil, err
489
-		}
490
-	}
491
-	if healthcheck.Interval != "" {
492
-		interval, err = time.ParseDuration(healthcheck.Interval)
493
-		if err != nil {
494
-			return nil, err
495
-		}
496
-	}
497
-	if healthcheck.Retries != nil {
498
-		retries = int(*healthcheck.Retries)
499
-	}
500
-	return &container.HealthConfig{
501
-		Test:     healthcheck.Test,
502
-		Timeout:  timeout,
503
-		Interval: interval,
504
-		Retries:  retries,
505
-	}, nil
506
-}
507
-
508
-func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
509
-	// TODO: log if restart is being ignored
510
-	if source == nil {
511
-		policy, err := runconfigopts.ParseRestartPolicy(restart)
512
-		if err != nil {
513
-			return nil, err
514
-		}
515
-		// TODO: is this an accurate convertion?
516
-		switch {
517
-		case policy.IsNone():
518
-			return nil, nil
519
-		case policy.IsAlways(), policy.IsUnlessStopped():
520
-			return &swarm.RestartPolicy{
521
-				Condition: swarm.RestartPolicyConditionAny,
522
-			}, nil
523
-		case policy.IsOnFailure():
524
-			attempts := uint64(policy.MaximumRetryCount)
525
-			return &swarm.RestartPolicy{
526
-				Condition:   swarm.RestartPolicyConditionOnFailure,
527
-				MaxAttempts: &attempts,
528
-			}, nil
529
-		}
530
-	}
531
-	return &swarm.RestartPolicy{
532
-		Condition:   swarm.RestartPolicyCondition(source.Condition),
533
-		Delay:       source.Delay,
534
-		MaxAttempts: source.MaxAttempts,
535
-		Window:      source.Window,
536
-	}, nil
537
-}
538
-
539
-func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
540
-	if source == nil {
541
-		return nil
542
-	}
543
-	parallel := uint64(1)
544
-	if source.Parallelism != nil {
545
-		parallel = *source.Parallelism
546
-	}
547
-	return &swarm.UpdateConfig{
548
-		Parallelism:     parallel,
549
-		Delay:           source.Delay,
550
-		FailureAction:   source.FailureAction,
551
-		Monitor:         source.Monitor,
552
-		MaxFailureRatio: source.MaxFailureRatio,
553
-	}
554
-}
555
-
556
-func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
557
-	resources := &swarm.ResourceRequirements{}
558
-	if source.Limits != nil {
559
-		cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs)
560
-		if err != nil {
561
-			return nil, err
562
-		}
563
-		resources.Limits = &swarm.Resources{
564
-			NanoCPUs:    cpus,
565
-			MemoryBytes: int64(source.Limits.MemoryBytes),
566
-		}
567
-	}
568
-	if source.Reservations != nil {
569
-		cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs)
570
-		if err != nil {
571
-			return nil, err
572
-		}
573
-		resources.Reservations = &swarm.Resources{
574
-			NanoCPUs:    cpus,
575
-			MemoryBytes: int64(source.Reservations.MemoryBytes),
576
-		}
577
-	}
578
-	return resources, nil
579
-}
580
-
581
-func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
582
-	portConfigs := []swarm.PortConfig{}
583
-	ports, portBindings, err := nat.ParsePortSpecs(source)
584
-	if err != nil {
585
-		return nil, err
586
-	}
587
-
588
-	for port := range ports {
589
-		portConfigs = append(
590
-			portConfigs,
591
-			opts.ConvertPortToPortConfig(port, portBindings)...)
592
-	}
593
-
594
-	return &swarm.EndpointSpec{Ports: portConfigs}, nil
595
-}
596
-
597
-func convertEnvironment(source map[string]string) []string {
598
-	var output []string
599
-
600
-	for name, value := range source {
601
-		output = append(output, fmt.Sprintf("%s=%s", name, value))
602
-	}
603
-
604
-	return output
605
-}
606
-
607
-func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
608
-	serviceMode := swarm.ServiceMode{}
609
-
610
-	switch mode {
611
-	case "global":
612
-		if replicas != nil {
613
-			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
614
-		}
615
-		serviceMode.Global = &swarm.GlobalService{}
616
-	case "replicated", "":
617
-		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
618
-	default:
619
-		return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
620
-	}
621
-	return serviceMode, nil
622
-}
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/docker/docker/api/types"
7 7
 	"github.com/docker/docker/api/types/swarm"
8 8
 	"github.com/docker/docker/cli/command"
9
+	"github.com/docker/docker/pkg/composetransform"
9 10
 )
10 11
 
11 12
 func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
... ...
@@ -18,20 +19,20 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
18 18
 		return err
19 19
 	}
20 20
 
21
-	namespace := namespace{name: opts.namespace}
21
+	namespace := composetransform.NewNamespace(opts.namespace)
22 22
 
23 23
 	networks := make(map[string]types.NetworkCreate)
24 24
 	for _, service := range bundle.Services {
25 25
 		for _, networkName := range service.Networks {
26 26
 			networks[networkName] = types.NetworkCreate{
27
-				Labels: getStackLabels(namespace.name, nil),
27
+				Labels: composetransform.AddStackLabel(namespace, nil),
28 28
 			}
29 29
 		}
30 30
 	}
31 31
 
32 32
 	services := make(map[string]swarm.ServiceSpec)
33 33
 	for internalName, service := range bundle.Services {
34
-		name := namespace.scope(internalName)
34
+		name := namespace.Scope(internalName)
35 35
 
36 36
 		var ports []swarm.PortConfig
37 37
 		for _, portSpec := range service.Ports {
... ...
@@ -44,7 +45,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
44 44
 		nets := []swarm.NetworkAttachmentConfig{}
45 45
 		for _, networkName := range service.Networks {
46 46
 			nets = append(nets, swarm.NetworkAttachmentConfig{
47
-				Target:  namespace.scope(networkName),
47
+				Target:  namespace.Scope(networkName),
48 48
 				Aliases: []string{networkName},
49 49
 			})
50 50
 		}
... ...
@@ -52,7 +53,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
52 52
 		serviceSpec := swarm.ServiceSpec{
53 53
 			Annotations: swarm.Annotations{
54 54
 				Name:   name,
55
-				Labels: getStackLabels(namespace.name, service.Labels),
55
+				Labels: composetransform.AddStackLabel(namespace, service.Labels),
56 56
 			},
57 57
 			TaskTemplate: swarm.TaskSpec{
58 58
 				ContainerSpec: swarm.ContainerSpec{
... ...
@@ -63,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
63 63
 					// Service Labels will not be copied to Containers
64 64
 					// automatically during the deployment so we apply
65 65
 					// it here.
66
-					Labels: getStackLabels(namespace.name, nil),
66
+					Labels: composetransform.AddStackLabel(namespace, nil),
67 67
 				},
68 68
 			},
69 69
 			EndpointSpec: &swarm.EndpointSpec{
... ...
@@ -9,10 +9,10 @@ import (
9 9
 	"golang.org/x/net/context"
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/api/types/filters"
13 12
 	"github.com/docker/docker/cli"
14 13
 	"github.com/docker/docker/cli/command"
15 14
 	"github.com/docker/docker/client"
15
+	"github.com/docker/docker/pkg/composetransform"
16 16
 	"github.com/spf13/cobra"
17 17
 )
18 18
 
... ...
@@ -81,23 +81,19 @@ func getStacks(
81 81
 	ctx context.Context,
82 82
 	apiclient client.APIClient,
83 83
 ) ([]*stack, error) {
84
-
85
-	filter := filters.NewArgs()
86
-	filter.Add("label", labelNamespace)
87
-
88 84
 	services, err := apiclient.ServiceList(
89 85
 		ctx,
90
-		types.ServiceListOptions{Filters: filter})
86
+		types.ServiceListOptions{Filters: getAllStacksFilter()})
91 87
 	if err != nil {
92 88
 		return nil, err
93 89
 	}
94 90
 	m := make(map[string]*stack, 0)
95 91
 	for _, service := range services {
96 92
 		labels := service.Spec.Labels
97
-		name, ok := labels[labelNamespace]
93
+		name, ok := labels[composetransform.LabelNamespace]
98 94
 		if !ok {
99 95
 			return nil, fmt.Errorf("cannot get label %s for service %s",
100
-				labelNamespace, service.ID)
96
+				composetransform.LabelNamespace, service.ID)
101 97
 		}
102 98
 		ztack, ok := m[name]
103 99
 		if !ok {
... ...
@@ -49,8 +49,7 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error {
49 49
 	client := dockerCli.Client()
50 50
 	ctx := context.Background()
51 51
 
52
-	filter := opts.filter.Value()
53
-	filter.Add("label", labelNamespace+"="+opts.namespace)
52
+	filter := getStackFilterFromOpt(opts.namespace, opts.filter)
54 53
 	if !opts.all && !filter.Include("desired-state") {
55 54
 		filter.Add("desired-state", string(swarm.TaskStateRunning))
56 55
 		filter.Add("desired-state", string(swarm.TaskStateAccepted))
... ...
@@ -43,9 +43,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
43 43
 	ctx := context.Background()
44 44
 	client := dockerCli.Client()
45 45
 
46
-	filter := opts.filter.Value()
47
-	filter.Add("label", labelNamespace+"="+opts.namespace)
48
-
46
+	filter := getStackFilterFromOpt(opts.namespace, opts.filter)
49 47
 	services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
50 48
 	if err != nil {
51 49
 		return err
52 50
new file mode 100644
... ...
@@ -0,0 +1,329 @@
0
+package composetransform
1
+
2
+import (
3
+	"fmt"
4
+	"time"
5
+
6
+	composetypes "github.com/aanand/compose-file/types"
7
+	"github.com/docker/docker/api/types/container"
8
+	"github.com/docker/docker/api/types/swarm"
9
+	"github.com/docker/docker/opts"
10
+	runconfigopts "github.com/docker/docker/runconfig/opts"
11
+	"github.com/docker/go-connections/nat"
12
+)
13
+
14
+// ConvertServices from compose-file types to engine API types
15
+func ConvertServices(
16
+	namespace Namespace,
17
+	config *composetypes.Config,
18
+) (map[string]swarm.ServiceSpec, error) {
19
+	result := make(map[string]swarm.ServiceSpec)
20
+
21
+	services := config.Services
22
+	volumes := config.Volumes
23
+	networks := config.Networks
24
+
25
+	for _, service := range services {
26
+		serviceSpec, err := convertService(namespace, service, networks, volumes)
27
+		if err != nil {
28
+			return nil, err
29
+		}
30
+		result[service.Name] = serviceSpec
31
+	}
32
+
33
+	return result, nil
34
+}
35
+
36
+func convertService(
37
+	namespace Namespace,
38
+	service composetypes.ServiceConfig,
39
+	networkConfigs map[string]composetypes.NetworkConfig,
40
+	volumes map[string]composetypes.VolumeConfig,
41
+) (swarm.ServiceSpec, error) {
42
+	name := namespace.Scope(service.Name)
43
+
44
+	endpoint, err := convertEndpointSpec(service.Ports)
45
+	if err != nil {
46
+		return swarm.ServiceSpec{}, err
47
+	}
48
+
49
+	mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
50
+	if err != nil {
51
+		return swarm.ServiceSpec{}, err
52
+	}
53
+
54
+	mounts, err := ConvertVolumes(service.Volumes, volumes, namespace)
55
+	if err != nil {
56
+		// TODO: better error message (include service name)
57
+		return swarm.ServiceSpec{}, err
58
+	}
59
+
60
+	resources, err := convertResources(service.Deploy.Resources)
61
+	if err != nil {
62
+		return swarm.ServiceSpec{}, err
63
+	}
64
+
65
+	restartPolicy, err := convertRestartPolicy(
66
+		service.Restart, service.Deploy.RestartPolicy)
67
+	if err != nil {
68
+		return swarm.ServiceSpec{}, err
69
+	}
70
+
71
+	healthcheck, err := convertHealthcheck(service.HealthCheck)
72
+	if err != nil {
73
+		return swarm.ServiceSpec{}, err
74
+	}
75
+
76
+	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
77
+	if err != nil {
78
+		return swarm.ServiceSpec{}, err
79
+	}
80
+
81
+	var logDriver *swarm.Driver
82
+	if service.Logging != nil {
83
+		logDriver = &swarm.Driver{
84
+			Name:    service.Logging.Driver,
85
+			Options: service.Logging.Options,
86
+		}
87
+	}
88
+
89
+	serviceSpec := swarm.ServiceSpec{
90
+		Annotations: swarm.Annotations{
91
+			Name:   name,
92
+			Labels: AddStackLabel(namespace, service.Deploy.Labels),
93
+		},
94
+		TaskTemplate: swarm.TaskSpec{
95
+			ContainerSpec: swarm.ContainerSpec{
96
+				Image:           service.Image,
97
+				Command:         service.Entrypoint,
98
+				Args:            service.Command,
99
+				Hostname:        service.Hostname,
100
+				Hosts:           convertExtraHosts(service.ExtraHosts),
101
+				Healthcheck:     healthcheck,
102
+				Env:             convertEnvironment(service.Environment),
103
+				Labels:          AddStackLabel(namespace, service.Labels),
104
+				Dir:             service.WorkingDir,
105
+				User:            service.User,
106
+				Mounts:          mounts,
107
+				StopGracePeriod: service.StopGracePeriod,
108
+				TTY:             service.Tty,
109
+				OpenStdin:       service.StdinOpen,
110
+			},
111
+			LogDriver:     logDriver,
112
+			Resources:     resources,
113
+			RestartPolicy: restartPolicy,
114
+			Placement: &swarm.Placement{
115
+				Constraints: service.Deploy.Placement.Constraints,
116
+			},
117
+		},
118
+		EndpointSpec: endpoint,
119
+		Mode:         mode,
120
+		Networks:     networks,
121
+		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
122
+	}
123
+
124
+	return serviceSpec, nil
125
+}
126
+
127
+func convertServiceNetworks(
128
+	networks map[string]*composetypes.ServiceNetworkConfig,
129
+	networkConfigs networks,
130
+	namespace Namespace,
131
+	name string,
132
+) ([]swarm.NetworkAttachmentConfig, error) {
133
+	if len(networks) == 0 {
134
+		return []swarm.NetworkAttachmentConfig{
135
+			{
136
+				Target:  namespace.Scope("default"),
137
+				Aliases: []string{name},
138
+			},
139
+		}, nil
140
+	}
141
+
142
+	nets := []swarm.NetworkAttachmentConfig{}
143
+	for networkName, network := range networks {
144
+		networkConfig, ok := networkConfigs[networkName]
145
+		if !ok {
146
+			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName)
147
+		}
148
+		var aliases []string
149
+		if network != nil {
150
+			aliases = network.Aliases
151
+		}
152
+		target := namespace.Scope(networkName)
153
+		if networkConfig.External.External {
154
+			target = networkName
155
+		}
156
+		nets = append(nets, swarm.NetworkAttachmentConfig{
157
+			Target:  target,
158
+			Aliases: append(aliases, name),
159
+		})
160
+	}
161
+	return nets, nil
162
+}
163
+
164
+func convertExtraHosts(extraHosts map[string]string) []string {
165
+	hosts := []string{}
166
+	for host, ip := range extraHosts {
167
+		hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
168
+	}
169
+	return hosts
170
+}
171
+
172
+func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
173
+	if healthcheck == nil {
174
+		return nil, nil
175
+	}
176
+	var (
177
+		err               error
178
+		timeout, interval time.Duration
179
+		retries           int
180
+	)
181
+	if healthcheck.Disable {
182
+		if len(healthcheck.Test) != 0 {
183
+			return nil, fmt.Errorf("command and disable key can't be set at the same time")
184
+		}
185
+		return &container.HealthConfig{
186
+			Test: []string{"NONE"},
187
+		}, nil
188
+
189
+	}
190
+	if healthcheck.Timeout != "" {
191
+		timeout, err = time.ParseDuration(healthcheck.Timeout)
192
+		if err != nil {
193
+			return nil, err
194
+		}
195
+	}
196
+	if healthcheck.Interval != "" {
197
+		interval, err = time.ParseDuration(healthcheck.Interval)
198
+		if err != nil {
199
+			return nil, err
200
+		}
201
+	}
202
+	if healthcheck.Retries != nil {
203
+		retries = int(*healthcheck.Retries)
204
+	}
205
+	return &container.HealthConfig{
206
+		Test:     healthcheck.Test,
207
+		Timeout:  timeout,
208
+		Interval: interval,
209
+		Retries:  retries,
210
+	}, nil
211
+}
212
+
213
+func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
214
+	// TODO: log if restart is being ignored
215
+	if source == nil {
216
+		policy, err := runconfigopts.ParseRestartPolicy(restart)
217
+		if err != nil {
218
+			return nil, err
219
+		}
220
+		switch {
221
+		case policy.IsNone():
222
+			return nil, nil
223
+		case policy.IsAlways(), policy.IsUnlessStopped():
224
+			return &swarm.RestartPolicy{
225
+				Condition: swarm.RestartPolicyConditionAny,
226
+			}, nil
227
+		case policy.IsOnFailure():
228
+			attempts := uint64(policy.MaximumRetryCount)
229
+			return &swarm.RestartPolicy{
230
+				Condition:   swarm.RestartPolicyConditionOnFailure,
231
+				MaxAttempts: &attempts,
232
+			}, nil
233
+		default:
234
+			return nil, fmt.Errorf("unknown restart policy: %s", restart)
235
+		}
236
+	}
237
+	return &swarm.RestartPolicy{
238
+		Condition:   swarm.RestartPolicyCondition(source.Condition),
239
+		Delay:       source.Delay,
240
+		MaxAttempts: source.MaxAttempts,
241
+		Window:      source.Window,
242
+	}, nil
243
+}
244
+
245
+func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
246
+	if source == nil {
247
+		return nil
248
+	}
249
+	parallel := uint64(1)
250
+	if source.Parallelism != nil {
251
+		parallel = *source.Parallelism
252
+	}
253
+	return &swarm.UpdateConfig{
254
+		Parallelism:     parallel,
255
+		Delay:           source.Delay,
256
+		FailureAction:   source.FailureAction,
257
+		Monitor:         source.Monitor,
258
+		MaxFailureRatio: source.MaxFailureRatio,
259
+	}
260
+}
261
+
262
+func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
263
+	resources := &swarm.ResourceRequirements{}
264
+	if source.Limits != nil {
265
+		cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs)
266
+		if err != nil {
267
+			return nil, err
268
+		}
269
+		resources.Limits = &swarm.Resources{
270
+			NanoCPUs:    cpus,
271
+			MemoryBytes: int64(source.Limits.MemoryBytes),
272
+		}
273
+	}
274
+	if source.Reservations != nil {
275
+		cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs)
276
+		if err != nil {
277
+			return nil, err
278
+		}
279
+		resources.Reservations = &swarm.Resources{
280
+			NanoCPUs:    cpus,
281
+			MemoryBytes: int64(source.Reservations.MemoryBytes),
282
+		}
283
+	}
284
+	return resources, nil
285
+}
286
+
287
+func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
288
+	portConfigs := []swarm.PortConfig{}
289
+	ports, portBindings, err := nat.ParsePortSpecs(source)
290
+	if err != nil {
291
+		return nil, err
292
+	}
293
+
294
+	for port := range ports {
295
+		portConfigs = append(
296
+			portConfigs,
297
+			opts.ConvertPortToPortConfig(port, portBindings)...)
298
+	}
299
+
300
+	return &swarm.EndpointSpec{Ports: portConfigs}, nil
301
+}
302
+
303
+func convertEnvironment(source map[string]string) []string {
304
+	var output []string
305
+
306
+	for name, value := range source {
307
+		output = append(output, fmt.Sprintf("%s=%s", name, value))
308
+	}
309
+
310
+	return output
311
+}
312
+
313
+func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
314
+	serviceMode := swarm.ServiceMode{}
315
+
316
+	switch mode {
317
+	case "global":
318
+		if replicas != nil {
319
+			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
320
+		}
321
+		serviceMode.Global = &swarm.GlobalService{}
322
+	case "replicated", "":
323
+		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
324
+	default:
325
+		return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
326
+	}
327
+	return serviceMode, nil
328
+}
0 329
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package composetransform
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/api/types/swarm"
6
+	"github.com/docker/docker/pkg/testutil/assert"
7
+)
8
+
9
+func TestConvertRestartPolicyFromNone(t *testing.T) {
10
+	policy, err := convertRestartPolicy("no", nil)
11
+	var expected *swarm.RestartPolicy
12
+	assert.NilError(t, err)
13
+	assert.Equal(t, policy, expected)
14
+}
15
+
16
+func TestConvertRestartPolicyFromUnknown(t *testing.T) {
17
+	_, err := convertRestartPolicy("unknown", nil)
18
+	assert.Error(t, err, "unknown restart policy: unknown")
19
+}
20
+
21
+func TestConvertRestartPolicyFromAlways(t *testing.T) {
22
+	policy, err := convertRestartPolicy("always", nil)
23
+	expected := &swarm.RestartPolicy{
24
+		Condition: swarm.RestartPolicyConditionAny,
25
+	}
26
+	assert.NilError(t, err)
27
+	assert.DeepEqual(t, policy, expected)
28
+}
29
+
30
+func TestConvertRestartPolicyFromFailure(t *testing.T) {
31
+	policy, err := convertRestartPolicy("on-failure:4", nil)
32
+	attempts := uint64(4)
33
+	expected := &swarm.RestartPolicy{
34
+		Condition:   swarm.RestartPolicyConditionOnFailure,
35
+		MaxAttempts: &attempts,
36
+	}
37
+	assert.NilError(t, err)
38
+	assert.DeepEqual(t, policy, expected)
39
+}