Move ConvertService to composetransform package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2016-11-30 17:38:40 -05:00
parent af6a411358
commit 31355030b3
6 changed files with 30 additions and 342 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/composetransform" "github.com/docker/docker/pkg/composetransform"
) )
@ -16,6 +17,18 @@ func getStackFilter(namespace string) filters.Args {
return filter 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( func getServices(
ctx context.Context, ctx context.Context,
apiclient client.APIClient, apiclient client.APIClient,

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"sort" "sort"
"strings" "strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -15,15 +14,11 @@ import (
"github.com/aanand/compose-file/loader" "github.com/aanand/compose-file/loader"
composetypes "github.com/aanand/compose-file/types" composetypes "github.com/aanand/compose-file/types"
"github.com/docker/docker/api/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/api/types/swarm"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/composetransform" "github.com/docker/docker/pkg/composetransform"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
) )
const ( const (
@ -129,7 +124,7 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
return err return err
} }
services, err := convertServices(namespace, config) services, err := composetransform.ConvertServices(namespace, config)
if err != nil { if err != nil {
return err return err
} }
@ -237,54 +232,17 @@ func createNetworks(
return nil 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( func deployServices(
ctx context.Context, ctx context.Context,
dockerCli *command.DockerCli, dockerCli *command.DockerCli,
services map[string]swarm.ServiceSpec, services map[string]swarm.ServiceSpec,
namespace namespace, namespace composetransform.Namespace,
sendAuth bool, sendAuth bool,
) error { ) error {
apiClient := dockerCli.Client() apiClient := dockerCli.Client()
out := dockerCli.Out() out := dockerCli.Out()
existingServices, err := getServices(ctx, apiClient, namespace.name) existingServices, err := getServices(ctx, apiClient, namespace.Name())
if err != nil { if err != nil {
return err return err
} }
@ -295,7 +253,7 @@ func deployServices(
} }
for internalName, serviceSpec := range services { for internalName, serviceSpec := range services {
name := namespace.scope(internalName) name := namespace.Scope(internalName)
encodedAuth := "" encodedAuth := ""
if sendAuth { if sendAuth {
@ -343,280 +301,3 @@ func deployServices(
return nil 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
}

View File

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

View File

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

View File

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

View File

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