From 5537dbde8494324a08e381f0afa87da119d3a733 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 30 Nov 2016 17:38:40 -0500 Subject: [PATCH] Move ConvertService to composetransform package. Signed-off-by: Daniel Nephin --- cli/command/stack/common.go | 13 + cli/command/stack/deploy.go | 334 +----------------------- cli/command/stack/deploy_bundlefile.go | 13 +- cli/command/stack/list.go | 12 +- cli/command/stack/ps.go | 4 +- cli/command/stack/services.go | 4 +- pkg/composetransform/service.go | 337 +++++++++++++++++++++++++ pkg/composetransform/service_test.go | 40 +++ 8 files changed, 407 insertions(+), 350 deletions(-) create mode 100644 pkg/composetransform/service.go create mode 100644 pkg/composetransform/service_test.go diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 050528de4e..c3a43f2cd8 100644 --- a/cli/command/stack/common.go +++ b/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, diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index b4753dd67e..d84a80e4fb 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -7,20 +7,15 @@ import ( "os" "sort" "strings" - "time" "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" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -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 } @@ -250,54 +245,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 = networkConfig.External.Name - } - 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 } @@ -308,7 +266,7 @@ func deployServices( } for internalName, serviceSpec := range services { - name := namespace.scope(internalName) + name := namespace.Scope(internalName) encodedAuth := "" if sendAuth { @@ -356,287 +314,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{} - var err error - if source.Limits != nil { - var cpus int64 - if source.Limits.NanoCPUs != "" { - 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 { - var cpus int64 - if source.Reservations.NanoCPUs != "" { - 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 -} diff --git a/cli/command/stack/deploy_bundlefile.go b/cli/command/stack/deploy_bundlefile.go index c82c46e424..f9a4162389 100644 --- a/cli/command/stack/deploy_bundlefile.go +++ b/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{ diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index f655b929ad..52e593316e 100644 --- a/cli/command/stack/list.go +++ b/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 { diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index 73ceb33128..e4351bfc7c 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -46,9 +46,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) tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) if err != nil { return err diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index 1ca1c8c129..a46652df7c 100644 --- a/cli/command/stack/services.go +++ b/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 diff --git a/pkg/composetransform/service.go b/pkg/composetransform/service.go new file mode 100644 index 0000000000..1946b21955 --- /dev/null +++ b/pkg/composetransform/service.go @@ -0,0 +1,337 @@ +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 = networkConfig.External.Name + } + 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{} + var err error + if source.Limits != nil { + var cpus int64 + if source.Limits.NanoCPUs != "" { + 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 { + var cpus int64 + if source.Reservations.NanoCPUs != "" { + 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 +} diff --git a/pkg/composetransform/service_test.go b/pkg/composetransform/service_test.go new file mode 100644 index 0000000000..8d713c6ba8 --- /dev/null +++ b/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) +}