Explorar el Código

Move ConvertService to composetransform package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin hace 8 años
padre
commit
655e48e653

+ 13 - 0
cli/command/stack/common.go

@@ -7,6 +7,7 @@ import (
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
+	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/composetransform"
 )
 
@@ -16,6 +17,18 @@ func getStackFilter(namespace string) filters.Args {
 	return filter
 }
 
+func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {
+	filter := opt.Value()
+	filter.Add("label", composetransform.LabelNamespace+"="+namespace)
+	return filter
+}
+
+func getAllStacksFilter() filters.Args {
+	filter := filters.NewArgs()
+	filter.Add("label", composetransform.LabelNamespace)
+	return filter
+}
+
 func getServices(
 	ctx context.Context,
 	apiclient client.APIClient,

+ 4 - 323
cli/command/stack/deploy.go

@@ -7,7 +7,6 @@ import (
 	"os"
 	"sort"
 	"strings"
-	"time"
 
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -15,15 +14,11 @@ import (
 	"github.com/aanand/compose-file/loader"
 	composetypes "github.com/aanand/compose-file/types"
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	dockerclient "github.com/docker/docker/client"
-	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/composetransform"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
-	"github.com/docker/go-connections/nat"
 )
 
 const (
@@ -129,7 +124,7 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
 	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
 		return err
 	}
-	services, err := convertServices(namespace, config)
+	services, err := composetransform.ConvertServices(namespace, config)
 	if err != nil {
 		return err
 	}
@@ -237,54 +232,17 @@ func createNetworks(
 	return nil
 }
 
-func convertServiceNetworks(
-	networks map[string]*composetypes.ServiceNetworkConfig,
-	networkConfigs map[string]composetypes.NetworkConfig,
-	namespace composetransform.Namespace,
-	name string,
-) ([]swarm.NetworkAttachmentConfig, error) {
-	if len(networks) == 0 {
-		return []swarm.NetworkAttachmentConfig{
-			{
-				Target:  namespace.scope("default"),
-				Aliases: []string{name},
-			},
-		}, nil
-	}
-
-	nets := []swarm.NetworkAttachmentConfig{}
-	for networkName, network := range networks {
-		networkConfig, ok := networkConfigs[networkName]
-		if !ok {
-			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName)
-		}
-		var aliases []string
-		if network != nil {
-			aliases = network.Aliases
-		}
-		target := namespace.scope(networkName)
-		if networkConfig.External.External {
-			target = networkName
-		}
-		nets = append(nets, swarm.NetworkAttachmentConfig{
-			Target:  target,
-			Aliases: append(aliases, name),
-		})
-	}
-	return nets, nil
-}
-
 func deployServices(
 	ctx context.Context,
 	dockerCli *command.DockerCli,
 	services map[string]swarm.ServiceSpec,
-	namespace namespace,
+	namespace composetransform.Namespace,
 	sendAuth bool,
 ) error {
 	apiClient := dockerCli.Client()
 	out := dockerCli.Out()
 
-	existingServices, err := getServices(ctx, apiClient, namespace.name)
+	existingServices, err := getServices(ctx, apiClient, namespace.Name())
 	if err != nil {
 		return err
 	}
@@ -295,7 +253,7 @@ func deployServices(
 	}
 
 	for internalName, serviceSpec := range services {
-		name := namespace.scope(internalName)
+		name := namespace.Scope(internalName)
 
 		encodedAuth := ""
 		if sendAuth {
@@ -343,280 +301,3 @@ func deployServices(
 
 	return nil
 }
-
-func convertServices(
-	namespace namespace,
-	config *composetypes.Config,
-) (map[string]swarm.ServiceSpec, error) {
-	result := make(map[string]swarm.ServiceSpec)
-
-	services := config.Services
-	volumes := config.Volumes
-	networks := config.Networks
-
-	for _, service := range services {
-		serviceSpec, err := convertService(namespace, service, networks, volumes)
-		if err != nil {
-			return nil, err
-		}
-		result[service.Name] = serviceSpec
-	}
-
-	return result, nil
-}
-
-func convertService(
-	namespace namespace,
-	service composetypes.ServiceConfig,
-	networkConfigs map[string]composetypes.NetworkConfig,
-	volumes map[string]composetypes.VolumeConfig,
-) (swarm.ServiceSpec, error) {
-	name := namespace.scope(service.Name)
-
-	endpoint, err := convertEndpointSpec(service.Ports)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	mounts, err := composetransform.ConvertVolumes(service.Volumes, volumes, namespace)
-	if err != nil {
-		// TODO: better error message (include service name)
-		return swarm.ServiceSpec{}, err
-	}
-
-	resources, err := convertResources(service.Deploy.Resources)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	restartPolicy, err := convertRestartPolicy(
-		service.Restart, service.Deploy.RestartPolicy)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	healthcheck, err := convertHealthcheck(service.HealthCheck)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
-	if err != nil {
-		return swarm.ServiceSpec{}, err
-	}
-
-	var logDriver *swarm.Driver
-	if service.Logging != nil {
-		logDriver = &swarm.Driver{
-			Name:    service.Logging.Driver,
-			Options: service.Logging.Options,
-		}
-	}
-
-	serviceSpec := swarm.ServiceSpec{
-		Annotations: swarm.Annotations{
-			Name:   name,
-			Labels: getStackLabels(namespace.name, service.Deploy.Labels),
-		},
-		TaskTemplate: swarm.TaskSpec{
-			ContainerSpec: swarm.ContainerSpec{
-				Image:           service.Image,
-				Command:         service.Entrypoint,
-				Args:            service.Command,
-				Hostname:        service.Hostname,
-				Hosts:           convertExtraHosts(service.ExtraHosts),
-				Healthcheck:     healthcheck,
-				Env:             convertEnvironment(service.Environment),
-				Labels:          getStackLabels(namespace.name, service.Labels),
-				Dir:             service.WorkingDir,
-				User:            service.User,
-				Mounts:          mounts,
-				StopGracePeriod: service.StopGracePeriod,
-				TTY:             service.Tty,
-				OpenStdin:       service.StdinOpen,
-			},
-			LogDriver:     logDriver,
-			Resources:     resources,
-			RestartPolicy: restartPolicy,
-			Placement: &swarm.Placement{
-				Constraints: service.Deploy.Placement.Constraints,
-			},
-		},
-		EndpointSpec: endpoint,
-		Mode:         mode,
-		Networks:     networks,
-		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
-	}
-
-	return serviceSpec, nil
-}
-
-func convertExtraHosts(extraHosts map[string]string) []string {
-	hosts := []string{}
-	for host, ip := range extraHosts {
-		hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
-	}
-	return hosts
-}
-
-func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
-	if healthcheck == nil {
-		return nil, nil
-	}
-	var (
-		err               error
-		timeout, interval time.Duration
-		retries           int
-	)
-	if healthcheck.Disable {
-		if len(healthcheck.Test) != 0 {
-			return nil, fmt.Errorf("command and disable key can't be set at the same time")
-		}
-		return &container.HealthConfig{
-			Test: []string{"NONE"},
-		}, nil
-
-	}
-	if healthcheck.Timeout != "" {
-		timeout, err = time.ParseDuration(healthcheck.Timeout)
-		if err != nil {
-			return nil, err
-		}
-	}
-	if healthcheck.Interval != "" {
-		interval, err = time.ParseDuration(healthcheck.Interval)
-		if err != nil {
-			return nil, err
-		}
-	}
-	if healthcheck.Retries != nil {
-		retries = int(*healthcheck.Retries)
-	}
-	return &container.HealthConfig{
-		Test:     healthcheck.Test,
-		Timeout:  timeout,
-		Interval: interval,
-		Retries:  retries,
-	}, nil
-}
-
-func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
-	// TODO: log if restart is being ignored
-	if source == nil {
-		policy, err := runconfigopts.ParseRestartPolicy(restart)
-		if err != nil {
-			return nil, err
-		}
-		// TODO: is this an accurate convertion?
-		switch {
-		case policy.IsNone():
-			return nil, nil
-		case policy.IsAlways(), policy.IsUnlessStopped():
-			return &swarm.RestartPolicy{
-				Condition: swarm.RestartPolicyConditionAny,
-			}, nil
-		case policy.IsOnFailure():
-			attempts := uint64(policy.MaximumRetryCount)
-			return &swarm.RestartPolicy{
-				Condition:   swarm.RestartPolicyConditionOnFailure,
-				MaxAttempts: &attempts,
-			}, nil
-		}
-	}
-	return &swarm.RestartPolicy{
-		Condition:   swarm.RestartPolicyCondition(source.Condition),
-		Delay:       source.Delay,
-		MaxAttempts: source.MaxAttempts,
-		Window:      source.Window,
-	}, nil
-}
-
-func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
-	if source == nil {
-		return nil
-	}
-	parallel := uint64(1)
-	if source.Parallelism != nil {
-		parallel = *source.Parallelism
-	}
-	return &swarm.UpdateConfig{
-		Parallelism:     parallel,
-		Delay:           source.Delay,
-		FailureAction:   source.FailureAction,
-		Monitor:         source.Monitor,
-		MaxFailureRatio: source.MaxFailureRatio,
-	}
-}
-
-func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
-	resources := &swarm.ResourceRequirements{}
-	if source.Limits != nil {
-		cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs)
-		if err != nil {
-			return nil, err
-		}
-		resources.Limits = &swarm.Resources{
-			NanoCPUs:    cpus,
-			MemoryBytes: int64(source.Limits.MemoryBytes),
-		}
-	}
-	if source.Reservations != nil {
-		cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs)
-		if err != nil {
-			return nil, err
-		}
-		resources.Reservations = &swarm.Resources{
-			NanoCPUs:    cpus,
-			MemoryBytes: int64(source.Reservations.MemoryBytes),
-		}
-	}
-	return resources, nil
-}
-
-func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
-	portConfigs := []swarm.PortConfig{}
-	ports, portBindings, err := nat.ParsePortSpecs(source)
-	if err != nil {
-		return nil, err
-	}
-
-	for port := range ports {
-		portConfigs = append(
-			portConfigs,
-			opts.ConvertPortToPortConfig(port, portBindings)...)
-	}
-
-	return &swarm.EndpointSpec{Ports: portConfigs}, nil
-}
-
-func convertEnvironment(source map[string]string) []string {
-	var output []string
-
-	for name, value := range source {
-		output = append(output, fmt.Sprintf("%s=%s", name, value))
-	}
-
-	return output
-}
-
-func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
-	serviceMode := swarm.ServiceMode{}
-
-	switch mode {
-	case "global":
-		if replicas != nil {
-			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
-		}
-		serviceMode.Global = &swarm.GlobalService{}
-	case "replicated", "":
-		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
-	default:
-		return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
-	}
-	return serviceMode, nil
-}

+ 7 - 6
cli/command/stack/deploy_bundlefile.go

@@ -6,6 +6,7 @@ import (
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/pkg/composetransform"
 )
 
 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
 		return err
 	}
 
-	namespace := namespace{name: opts.namespace}
+	namespace := composetransform.NewNamespace(opts.namespace)
 
 	networks := make(map[string]types.NetworkCreate)
 	for _, service := range bundle.Services {
 		for _, networkName := range service.Networks {
 			networks[networkName] = types.NetworkCreate{
-				Labels: getStackLabels(namespace.name, nil),
+				Labels: composetransform.AddStackLabel(namespace, nil),
 			}
 		}
 	}
 
 	services := make(map[string]swarm.ServiceSpec)
 	for internalName, service := range bundle.Services {
-		name := namespace.scope(internalName)
+		name := namespace.Scope(internalName)
 
 		var ports []swarm.PortConfig
 		for _, portSpec := range service.Ports {
@@ -44,7 +45,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
 		nets := []swarm.NetworkAttachmentConfig{}
 		for _, networkName := range service.Networks {
 			nets = append(nets, swarm.NetworkAttachmentConfig{
-				Target:  namespace.scope(networkName),
+				Target:  namespace.Scope(networkName),
 				Aliases: []string{networkName},
 			})
 		}
@@ -52,7 +53,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
 		serviceSpec := swarm.ServiceSpec{
 			Annotations: swarm.Annotations{
 				Name:   name,
-				Labels: getStackLabels(namespace.name, service.Labels),
+				Labels: composetransform.AddStackLabel(namespace, service.Labels),
 			},
 			TaskTemplate: swarm.TaskSpec{
 				ContainerSpec: swarm.ContainerSpec{
@@ -63,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deploy
 					// Service Labels will not be copied to Containers
 					// automatically during the deployment so we apply
 					// it here.
-					Labels: getStackLabels(namespace.name, nil),
+					Labels: composetransform.AddStackLabel(namespace, nil),
 				},
 			},
 			EndpointSpec: &swarm.EndpointSpec{

+ 4 - 8
cli/command/stack/list.go

@@ -9,10 +9,10 @@ import (
 	"golang.org/x/net/context"
 
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/client"
+	"github.com/docker/docker/pkg/composetransform"
 	"github.com/spf13/cobra"
 )
 
@@ -81,23 +81,19 @@ func getStacks(
 	ctx context.Context,
 	apiclient client.APIClient,
 ) ([]*stack, error) {
-
-	filter := filters.NewArgs()
-	filter.Add("label", labelNamespace)
-
 	services, err := apiclient.ServiceList(
 		ctx,
-		types.ServiceListOptions{Filters: filter})
+		types.ServiceListOptions{Filters: getAllStacksFilter()})
 	if err != nil {
 		return nil, err
 	}
 	m := make(map[string]*stack, 0)
 	for _, service := range services {
 		labels := service.Spec.Labels
-		name, ok := labels[labelNamespace]
+		name, ok := labels[composetransform.LabelNamespace]
 		if !ok {
 			return nil, fmt.Errorf("cannot get label %s for service %s",
-				labelNamespace, service.ID)
+				composetransform.LabelNamespace, service.ID)
 		}
 		ztack, ok := m[name]
 		if !ok {

+ 1 - 2
cli/command/stack/ps.go

@@ -49,8 +49,7 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error {
 	client := dockerCli.Client()
 	ctx := context.Background()
 
-	filter := opts.filter.Value()
-	filter.Add("label", labelNamespace+"="+opts.namespace)
+	filter := getStackFilterFromOpt(opts.namespace, opts.filter)
 	if !opts.all && !filter.Include("desired-state") {
 		filter.Add("desired-state", string(swarm.TaskStateRunning))
 		filter.Add("desired-state", string(swarm.TaskStateAccepted))

+ 1 - 3
cli/command/stack/services.go

@@ -43,9 +43,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
 	ctx := context.Background()
 	client := dockerCli.Client()
 
-	filter := opts.filter.Value()
-	filter.Add("label", labelNamespace+"="+opts.namespace)
-
+	filter := getStackFilterFromOpt(opts.namespace, opts.filter)
 	services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
 	if err != nil {
 		return err

+ 329 - 0
pkg/composetransform/service.go

@@ -0,0 +1,329 @@
+package composetransform
+
+import (
+	"fmt"
+	"time"
+
+	composetypes "github.com/aanand/compose-file/types"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/go-connections/nat"
+)
+
+// ConvertServices from compose-file types to engine API types
+func ConvertServices(
+	namespace Namespace,
+	config *composetypes.Config,
+) (map[string]swarm.ServiceSpec, error) {
+	result := make(map[string]swarm.ServiceSpec)
+
+	services := config.Services
+	volumes := config.Volumes
+	networks := config.Networks
+
+	for _, service := range services {
+		serviceSpec, err := convertService(namespace, service, networks, volumes)
+		if err != nil {
+			return nil, err
+		}
+		result[service.Name] = serviceSpec
+	}
+
+	return result, nil
+}
+
+func convertService(
+	namespace Namespace,
+	service composetypes.ServiceConfig,
+	networkConfigs map[string]composetypes.NetworkConfig,
+	volumes map[string]composetypes.VolumeConfig,
+) (swarm.ServiceSpec, error) {
+	name := namespace.Scope(service.Name)
+
+	endpoint, err := convertEndpointSpec(service.Ports)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	mounts, err := ConvertVolumes(service.Volumes, volumes, namespace)
+	if err != nil {
+		// TODO: better error message (include service name)
+		return swarm.ServiceSpec{}, err
+	}
+
+	resources, err := convertResources(service.Deploy.Resources)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	restartPolicy, err := convertRestartPolicy(
+		service.Restart, service.Deploy.RestartPolicy)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	healthcheck, err := convertHealthcheck(service.HealthCheck)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
+	if err != nil {
+		return swarm.ServiceSpec{}, err
+	}
+
+	var logDriver *swarm.Driver
+	if service.Logging != nil {
+		logDriver = &swarm.Driver{
+			Name:    service.Logging.Driver,
+			Options: service.Logging.Options,
+		}
+	}
+
+	serviceSpec := swarm.ServiceSpec{
+		Annotations: swarm.Annotations{
+			Name:   name,
+			Labels: AddStackLabel(namespace, service.Deploy.Labels),
+		},
+		TaskTemplate: swarm.TaskSpec{
+			ContainerSpec: swarm.ContainerSpec{
+				Image:           service.Image,
+				Command:         service.Entrypoint,
+				Args:            service.Command,
+				Hostname:        service.Hostname,
+				Hosts:           convertExtraHosts(service.ExtraHosts),
+				Healthcheck:     healthcheck,
+				Env:             convertEnvironment(service.Environment),
+				Labels:          AddStackLabel(namespace, service.Labels),
+				Dir:             service.WorkingDir,
+				User:            service.User,
+				Mounts:          mounts,
+				StopGracePeriod: service.StopGracePeriod,
+				TTY:             service.Tty,
+				OpenStdin:       service.StdinOpen,
+			},
+			LogDriver:     logDriver,
+			Resources:     resources,
+			RestartPolicy: restartPolicy,
+			Placement: &swarm.Placement{
+				Constraints: service.Deploy.Placement.Constraints,
+			},
+		},
+		EndpointSpec: endpoint,
+		Mode:         mode,
+		Networks:     networks,
+		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
+	}
+
+	return serviceSpec, nil
+}
+
+func convertServiceNetworks(
+	networks map[string]*composetypes.ServiceNetworkConfig,
+	networkConfigs networks,
+	namespace Namespace,
+	name string,
+) ([]swarm.NetworkAttachmentConfig, error) {
+	if len(networks) == 0 {
+		return []swarm.NetworkAttachmentConfig{
+			{
+				Target:  namespace.Scope("default"),
+				Aliases: []string{name},
+			},
+		}, nil
+	}
+
+	nets := []swarm.NetworkAttachmentConfig{}
+	for networkName, network := range networks {
+		networkConfig, ok := networkConfigs[networkName]
+		if !ok {
+			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName)
+		}
+		var aliases []string
+		if network != nil {
+			aliases = network.Aliases
+		}
+		target := namespace.Scope(networkName)
+		if networkConfig.External.External {
+			target = networkName
+		}
+		nets = append(nets, swarm.NetworkAttachmentConfig{
+			Target:  target,
+			Aliases: append(aliases, name),
+		})
+	}
+	return nets, nil
+}
+
+func convertExtraHosts(extraHosts map[string]string) []string {
+	hosts := []string{}
+	for host, ip := range extraHosts {
+		hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
+	}
+	return hosts
+}
+
+func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
+	if healthcheck == nil {
+		return nil, nil
+	}
+	var (
+		err               error
+		timeout, interval time.Duration
+		retries           int
+	)
+	if healthcheck.Disable {
+		if len(healthcheck.Test) != 0 {
+			return nil, fmt.Errorf("command and disable key can't be set at the same time")
+		}
+		return &container.HealthConfig{
+			Test: []string{"NONE"},
+		}, nil
+
+	}
+	if healthcheck.Timeout != "" {
+		timeout, err = time.ParseDuration(healthcheck.Timeout)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if healthcheck.Interval != "" {
+		interval, err = time.ParseDuration(healthcheck.Interval)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if healthcheck.Retries != nil {
+		retries = int(*healthcheck.Retries)
+	}
+	return &container.HealthConfig{
+		Test:     healthcheck.Test,
+		Timeout:  timeout,
+		Interval: interval,
+		Retries:  retries,
+	}, nil
+}
+
+func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
+	// TODO: log if restart is being ignored
+	if source == nil {
+		policy, err := runconfigopts.ParseRestartPolicy(restart)
+		if err != nil {
+			return nil, err
+		}
+		switch {
+		case policy.IsNone():
+			return nil, nil
+		case policy.IsAlways(), policy.IsUnlessStopped():
+			return &swarm.RestartPolicy{
+				Condition: swarm.RestartPolicyConditionAny,
+			}, nil
+		case policy.IsOnFailure():
+			attempts := uint64(policy.MaximumRetryCount)
+			return &swarm.RestartPolicy{
+				Condition:   swarm.RestartPolicyConditionOnFailure,
+				MaxAttempts: &attempts,
+			}, nil
+		default:
+			return nil, fmt.Errorf("unknown restart policy: %s", restart)
+		}
+	}
+	return &swarm.RestartPolicy{
+		Condition:   swarm.RestartPolicyCondition(source.Condition),
+		Delay:       source.Delay,
+		MaxAttempts: source.MaxAttempts,
+		Window:      source.Window,
+	}, nil
+}
+
+func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
+	if source == nil {
+		return nil
+	}
+	parallel := uint64(1)
+	if source.Parallelism != nil {
+		parallel = *source.Parallelism
+	}
+	return &swarm.UpdateConfig{
+		Parallelism:     parallel,
+		Delay:           source.Delay,
+		FailureAction:   source.FailureAction,
+		Monitor:         source.Monitor,
+		MaxFailureRatio: source.MaxFailureRatio,
+	}
+}
+
+func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
+	resources := &swarm.ResourceRequirements{}
+	if source.Limits != nil {
+		cpus, err := opts.ParseCPUs(source.Limits.NanoCPUs)
+		if err != nil {
+			return nil, err
+		}
+		resources.Limits = &swarm.Resources{
+			NanoCPUs:    cpus,
+			MemoryBytes: int64(source.Limits.MemoryBytes),
+		}
+	}
+	if source.Reservations != nil {
+		cpus, err := opts.ParseCPUs(source.Reservations.NanoCPUs)
+		if err != nil {
+			return nil, err
+		}
+		resources.Reservations = &swarm.Resources{
+			NanoCPUs:    cpus,
+			MemoryBytes: int64(source.Reservations.MemoryBytes),
+		}
+	}
+	return resources, nil
+}
+
+func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
+	portConfigs := []swarm.PortConfig{}
+	ports, portBindings, err := nat.ParsePortSpecs(source)
+	if err != nil {
+		return nil, err
+	}
+
+	for port := range ports {
+		portConfigs = append(
+			portConfigs,
+			opts.ConvertPortToPortConfig(port, portBindings)...)
+	}
+
+	return &swarm.EndpointSpec{Ports: portConfigs}, nil
+}
+
+func convertEnvironment(source map[string]string) []string {
+	var output []string
+
+	for name, value := range source {
+		output = append(output, fmt.Sprintf("%s=%s", name, value))
+	}
+
+	return output
+}
+
+func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
+	serviceMode := swarm.ServiceMode{}
+
+	switch mode {
+	case "global":
+		if replicas != nil {
+			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
+		}
+		serviceMode.Global = &swarm.GlobalService{}
+	case "replicated", "":
+		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
+	default:
+		return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
+	}
+	return serviceMode, nil
+}

+ 40 - 0
pkg/composetransform/service_test.go

@@ -0,0 +1,40 @@
+package composetransform
+
+import (
+	"testing"
+
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/pkg/testutil/assert"
+)
+
+func TestConvertRestartPolicyFromNone(t *testing.T) {
+	policy, err := convertRestartPolicy("no", nil)
+	var expected *swarm.RestartPolicy
+	assert.NilError(t, err)
+	assert.Equal(t, policy, expected)
+}
+
+func TestConvertRestartPolicyFromUnknown(t *testing.T) {
+	_, err := convertRestartPolicy("unknown", nil)
+	assert.Error(t, err, "unknown restart policy: unknown")
+}
+
+func TestConvertRestartPolicyFromAlways(t *testing.T) {
+	policy, err := convertRestartPolicy("always", nil)
+	expected := &swarm.RestartPolicy{
+		Condition: swarm.RestartPolicyConditionAny,
+	}
+	assert.NilError(t, err)
+	assert.DeepEqual(t, policy, expected)
+}
+
+func TestConvertRestartPolicyFromFailure(t *testing.T) {
+	policy, err := convertRestartPolicy("on-failure:4", nil)
+	attempts := uint64(4)
+	expected := &swarm.RestartPolicy{
+		Condition:   swarm.RestartPolicyConditionOnFailure,
+		MaxAttempts: &attempts,
+	}
+	assert.NilError(t, err)
+	assert.DeepEqual(t, policy, expected)
+}