github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/compose/convert/service.go (about)

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