github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/cli/compose/convert/service.go (about)

     1  package convert
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/docker/docker/api/types/container"
     8  	"github.com/docker/docker/api/types/swarm"
     9  	composetypes "github.com/docker/docker/cli/compose/types"
    10  	"github.com/docker/docker/opts"
    11  	runconfigopts "github.com/docker/docker/runconfig/opts"
    12  	"github.com/docker/go-connections/nat"
    13  )
    14  
    15  // Services from compose-file types to engine API types
    16  func Services(
    17  	namespace Namespace,
    18  	config *composetypes.Config,
    19  ) (map[string]swarm.ServiceSpec, error) {
    20  	result := make(map[string]swarm.ServiceSpec)
    21  
    22  	services := config.Services
    23  	volumes := config.Volumes
    24  	networks := config.Networks
    25  
    26  	for _, service := range services {
    27  		serviceSpec, err := convertService(namespace, service, networks, volumes)
    28  		if err != nil {
    29  			return nil, err
    30  		}
    31  		result[service.Name] = serviceSpec
    32  	}
    33  
    34  	return result, nil
    35  }
    36  
    37  func convertService(
    38  	namespace Namespace,
    39  	service composetypes.ServiceConfig,
    40  	networkConfigs map[string]composetypes.NetworkConfig,
    41  	volumes map[string]composetypes.VolumeConfig,
    42  ) (swarm.ServiceSpec, error) {
    43  	name := namespace.Scope(service.Name)
    44  
    45  	endpoint, err := convertEndpointSpec(service.Ports)
    46  	if err != nil {
    47  		return swarm.ServiceSpec{}, err
    48  	}
    49  
    50  	mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
    51  	if err != nil {
    52  		return swarm.ServiceSpec{}, err
    53  	}
    54  
    55  	mounts, err := Volumes(service.Volumes, volumes, namespace)
    56  	if err != nil {
    57  		// TODO: better error message (include service name)
    58  		return swarm.ServiceSpec{}, err
    59  	}
    60  
    61  	resources, err := convertResources(service.Deploy.Resources)
    62  	if err != nil {
    63  		return swarm.ServiceSpec{}, err
    64  	}
    65  
    66  	restartPolicy, err := convertRestartPolicy(
    67  		service.Restart, service.Deploy.RestartPolicy)
    68  	if err != nil {
    69  		return swarm.ServiceSpec{}, err
    70  	}
    71  
    72  	healthcheck, err := convertHealthcheck(service.HealthCheck)
    73  	if err != nil {
    74  		return swarm.ServiceSpec{}, err
    75  	}
    76  
    77  	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
    78  	if err != nil {
    79  		return swarm.ServiceSpec{}, err
    80  	}
    81  
    82  	var logDriver *swarm.Driver
    83  	if service.Logging != nil {
    84  		logDriver = &swarm.Driver{
    85  			Name:    service.Logging.Driver,
    86  			Options: service.Logging.Options,
    87  		}
    88  	}
    89  
    90  	serviceSpec := swarm.ServiceSpec{
    91  		Annotations: swarm.Annotations{
    92  			Name:   name,
    93  			Labels: AddStackLabel(namespace, service.Deploy.Labels),
    94  		},
    95  		TaskTemplate: swarm.TaskSpec{
    96  			ContainerSpec: swarm.ContainerSpec{
    97  				Image:           service.Image,
    98  				Command:         service.Entrypoint,
    99  				Args:            service.Command,
   100  				Hostname:        service.Hostname,
   101  				Hosts:           convertExtraHosts(service.ExtraHosts),
   102  				Healthcheck:     healthcheck,
   103  				Env:             convertEnvironment(service.Environment),
   104  				Labels:          AddStackLabel(namespace, service.Labels),
   105  				Dir:             service.WorkingDir,
   106  				User:            service.User,
   107  				Mounts:          mounts,
   108  				StopGracePeriod: service.StopGracePeriod,
   109  				TTY:             service.Tty,
   110  				OpenStdin:       service.StdinOpen,
   111  			},
   112  			LogDriver:     logDriver,
   113  			Resources:     resources,
   114  			RestartPolicy: restartPolicy,
   115  			Placement: &swarm.Placement{
   116  				Constraints: service.Deploy.Placement.Constraints,
   117  			},
   118  		},
   119  		EndpointSpec: endpoint,
   120  		Mode:         mode,
   121  		Networks:     networks,
   122  		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
   123  	}
   124  
   125  	return serviceSpec, nil
   126  }
   127  
   128  func convertServiceNetworks(
   129  	networks map[string]*composetypes.ServiceNetworkConfig,
   130  	networkConfigs networkMap,
   131  	namespace Namespace,
   132  	name string,
   133  ) ([]swarm.NetworkAttachmentConfig, error) {
   134  	if len(networks) == 0 {
   135  		return []swarm.NetworkAttachmentConfig{
   136  			{
   137  				Target:  namespace.Scope("default"),
   138  				Aliases: []string{name},
   139  			},
   140  		}, nil
   141  	}
   142  
   143  	nets := []swarm.NetworkAttachmentConfig{}
   144  	for networkName, network := range networks {
   145  		networkConfig, ok := networkConfigs[networkName]
   146  		if !ok {
   147  			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf(
   148  				"service %q references network %q, which is not declared", name, networkName)
   149  		}
   150  		var aliases []string
   151  		if network != nil {
   152  			aliases = network.Aliases
   153  		}
   154  		target := namespace.Scope(networkName)
   155  		if networkConfig.External.External {
   156  			target = networkConfig.External.Name
   157  		}
   158  		nets = append(nets, swarm.NetworkAttachmentConfig{
   159  			Target:  target,
   160  			Aliases: append(aliases, name),
   161  		})
   162  	}
   163  	return nets, nil
   164  }
   165  
   166  func convertExtraHosts(extraHosts map[string]string) []string {
   167  	hosts := []string{}
   168  	for host, ip := range extraHosts {
   169  		hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
   170  	}
   171  	return hosts
   172  }
   173  
   174  func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
   175  	if healthcheck == nil {
   176  		return nil, nil
   177  	}
   178  	var (
   179  		err               error
   180  		timeout, interval time.Duration
   181  		retries           int
   182  	)
   183  	if healthcheck.Disable {
   184  		if len(healthcheck.Test) != 0 {
   185  			return nil, fmt.Errorf("test and disable can't be set at the same time")
   186  		}
   187  		return &container.HealthConfig{
   188  			Test: []string{"NONE"},
   189  		}, nil
   190  
   191  	}
   192  	if healthcheck.Timeout != "" {
   193  		timeout, err = time.ParseDuration(healthcheck.Timeout)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  	}
   198  	if healthcheck.Interval != "" {
   199  		interval, err = time.ParseDuration(healthcheck.Interval)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  	}
   204  	if healthcheck.Retries != nil {
   205  		retries = int(*healthcheck.Retries)
   206  	}
   207  	return &container.HealthConfig{
   208  		Test:     healthcheck.Test,
   209  		Timeout:  timeout,
   210  		Interval: interval,
   211  		Retries:  retries,
   212  	}, nil
   213  }
   214  
   215  func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
   216  	// TODO: log if restart is being ignored
   217  	if source == nil {
   218  		policy, err := runconfigopts.ParseRestartPolicy(restart)
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		switch {
   223  		case policy.IsNone():
   224  			return nil, nil
   225  		case policy.IsAlways(), policy.IsUnlessStopped():
   226  			return &swarm.RestartPolicy{
   227  				Condition: swarm.RestartPolicyConditionAny,
   228  			}, nil
   229  		case policy.IsOnFailure():
   230  			attempts := uint64(policy.MaximumRetryCount)
   231  			return &swarm.RestartPolicy{
   232  				Condition:   swarm.RestartPolicyConditionOnFailure,
   233  				MaxAttempts: &attempts,
   234  			}, nil
   235  		default:
   236  			return nil, fmt.Errorf("unknown restart policy: %s", restart)
   237  		}
   238  	}
   239  	return &swarm.RestartPolicy{
   240  		Condition:   swarm.RestartPolicyCondition(source.Condition),
   241  		Delay:       source.Delay,
   242  		MaxAttempts: source.MaxAttempts,
   243  		Window:      source.Window,
   244  	}, nil
   245  }
   246  
   247  func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
   248  	if source == nil {
   249  		return nil
   250  	}
   251  	parallel := uint64(1)
   252  	if source.Parallelism != nil {
   253  		parallel = *source.Parallelism
   254  	}
   255  	return &swarm.UpdateConfig{
   256  		Parallelism:     parallel,
   257  		Delay:           source.Delay,
   258  		FailureAction:   source.FailureAction,
   259  		Monitor:         source.Monitor,
   260  		MaxFailureRatio: source.MaxFailureRatio,
   261  	}
   262  }
   263  
   264  func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
   265  	resources := &swarm.ResourceRequirements{}
   266  	var err error
   267  	if source.Limits != nil {
   268  		var cpus int64
   269  		if source.Limits.NanoCPUs != "" {
   270  			cpus, err = opts.ParseCPUs(source.Limits.NanoCPUs)
   271  			if err != nil {
   272  				return nil, err
   273  			}
   274  		}
   275  		resources.Limits = &swarm.Resources{
   276  			NanoCPUs:    cpus,
   277  			MemoryBytes: int64(source.Limits.MemoryBytes),
   278  		}
   279  	}
   280  	if source.Reservations != nil {
   281  		var cpus int64
   282  		if source.Reservations.NanoCPUs != "" {
   283  			cpus, err = opts.ParseCPUs(source.Reservations.NanoCPUs)
   284  			if err != nil {
   285  				return nil, err
   286  			}
   287  		}
   288  		resources.Reservations = &swarm.Resources{
   289  			NanoCPUs:    cpus,
   290  			MemoryBytes: int64(source.Reservations.MemoryBytes),
   291  		}
   292  	}
   293  	return resources, nil
   294  }
   295  
   296  func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
   297  	portConfigs := []swarm.PortConfig{}
   298  	ports, portBindings, err := nat.ParsePortSpecs(source)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	for port := range ports {
   304  		portConfig, err := opts.ConvertPortToPortConfig(port, portBindings)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  		portConfigs = append(portConfigs, portConfig...)
   309  	}
   310  
   311  	return &swarm.EndpointSpec{Ports: portConfigs}, nil
   312  }
   313  
   314  func convertEnvironment(source map[string]string) []string {
   315  	var output []string
   316  
   317  	for name, value := range source {
   318  		output = append(output, fmt.Sprintf("%s=%s", name, value))
   319  	}
   320  
   321  	return output
   322  }
   323  
   324  func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
   325  	serviceMode := swarm.ServiceMode{}
   326  
   327  	switch mode {
   328  	case "global":
   329  		if replicas != nil {
   330  			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
   331  		}
   332  		serviceMode.Global = &swarm.GlobalService{}
   333  	case "replicated", "":
   334  		serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
   335  	default:
   336  		return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
   337  	}
   338  	return serviceMode, nil
   339  }