github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/docker/service/convert.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/docker/docker/runconfig/opts"
    10  	"github.com/docker/engine-api/types/container"
    11  	"github.com/docker/engine-api/types/network"
    12  	"github.com/docker/engine-api/types/strslice"
    13  	"github.com/docker/go-connections/nat"
    14  	"github.com/docker/go-units"
    15  	"github.com/docker/libcompose/config"
    16  	composeclient "github.com/docker/libcompose/docker/client"
    17  	composecontainer "github.com/docker/libcompose/docker/container"
    18  	"github.com/docker/libcompose/project"
    19  	"github.com/docker/libcompose/utils"
    20  )
    21  
    22  // ConfigWrapper wraps Config, HostConfig and NetworkingConfig for a container.
    23  type ConfigWrapper struct {
    24  	Config           *container.Config
    25  	HostConfig       *container.HostConfig
    26  	NetworkingConfig *network.NetworkingConfig
    27  }
    28  
    29  // Filter filters the specified string slice with the specified function.
    30  func Filter(vs []string, f func(string) bool) []string {
    31  	r := make([]string, 0, len(vs))
    32  	for _, v := range vs {
    33  		if f(v) {
    34  			r = append(r, v)
    35  		}
    36  	}
    37  	return r
    38  }
    39  
    40  func toMap(vs []string) map[string]struct{} {
    41  	m := map[string]struct{}{}
    42  	for _, v := range vs {
    43  		if v != "" {
    44  			m[v] = struct{}{}
    45  		}
    46  	}
    47  	return m
    48  }
    49  
    50  func isBind(s string) bool {
    51  	return strings.ContainsRune(s, ':')
    52  }
    53  
    54  func isVolume(s string) bool {
    55  	return !isBind(s)
    56  }
    57  
    58  // ConvertToAPI converts a service configuration to a docker API container configuration.
    59  func ConvertToAPI(serviceConfig *config.ServiceConfig, ctx project.Context, clientFactory composeclient.Factory) (*ConfigWrapper, error) {
    60  	config, hostConfig, err := Convert(serviceConfig, ctx, clientFactory)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	result := ConfigWrapper{
    66  		Config:     config,
    67  		HostConfig: hostConfig,
    68  	}
    69  	return &result, nil
    70  }
    71  
    72  func volumes(c *config.ServiceConfig, ctx project.Context) []string {
    73  	if c.Volumes == nil {
    74  		return []string{}
    75  	}
    76  	volumes := make([]string, len(c.Volumes.Volumes))
    77  	for _, v := range c.Volumes.Volumes {
    78  		vol := v
    79  		if len(ctx.ComposeFiles) > 0 && !project.IsNamedVolume(v.Source) {
    80  			sourceVol := ctx.ResourceLookup.ResolvePath(v.String(), ctx.ComposeFiles[0])
    81  			vol.Source = strings.SplitN(sourceVol, ":", 2)[0]
    82  		}
    83  		volumes = append(volumes, vol.String())
    84  	}
    85  	return volumes
    86  }
    87  
    88  func restartPolicy(c *config.ServiceConfig) (*container.RestartPolicy, error) {
    89  	restart, err := opts.ParseRestartPolicy(c.Restart)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	return &container.RestartPolicy{Name: restart.Name, MaximumRetryCount: restart.MaximumRetryCount}, nil
    94  }
    95  
    96  func ports(c *config.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) {
    97  	ports, binding, err := nat.ParsePortSpecs(c.Ports)
    98  	if err != nil {
    99  		return nil, nil, err
   100  	}
   101  
   102  	exPorts, _, err := nat.ParsePortSpecs(c.Expose)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  
   107  	for k, v := range exPorts {
   108  		ports[k] = v
   109  	}
   110  
   111  	exposedPorts := map[nat.Port]struct{}{}
   112  	for k, v := range ports {
   113  		exposedPorts[nat.Port(k)] = v
   114  	}
   115  
   116  	portBindings := nat.PortMap{}
   117  	for k, bv := range binding {
   118  		dcbs := make([]nat.PortBinding, len(bv))
   119  		for k, v := range bv {
   120  			dcbs[k] = nat.PortBinding{HostIP: v.HostIP, HostPort: v.HostPort}
   121  		}
   122  		portBindings[nat.Port(k)] = dcbs
   123  	}
   124  	return exposedPorts, portBindings, nil
   125  }
   126  
   127  // Convert converts a service configuration to an docker API structures (Config and HostConfig)
   128  func Convert(c *config.ServiceConfig, ctx project.Context, clientFactory composeclient.Factory) (*container.Config, *container.HostConfig, error) {
   129  	restartPolicy, err := restartPolicy(c)
   130  	if err != nil {
   131  		return nil, nil, err
   132  	}
   133  
   134  	exposedPorts, portBindings, err := ports(c)
   135  	if err != nil {
   136  		return nil, nil, err
   137  	}
   138  
   139  	deviceMappings, err := parseDevices(c.Devices)
   140  	if err != nil {
   141  		return nil, nil, err
   142  	}
   143  
   144  	var volumesFrom []string
   145  	if c.VolumesFrom != nil {
   146  		volumesFrom, err = getVolumesFrom(c.VolumesFrom, ctx.Project.ServiceConfigs, ctx.ProjectName)
   147  		if err != nil {
   148  			return nil, nil, err
   149  		}
   150  	}
   151  
   152  	vols := volumes(c, ctx)
   153  
   154  	config := &container.Config{
   155  		Entrypoint:   strslice.StrSlice(utils.CopySlice(c.Entrypoint)),
   156  		Hostname:     c.Hostname,
   157  		Domainname:   c.DomainName,
   158  		User:         c.User,
   159  		Env:          utils.CopySlice(c.Environment),
   160  		Cmd:          strslice.StrSlice(utils.CopySlice(c.Command)),
   161  		Image:        c.Image,
   162  		Labels:       utils.CopyMap(c.Labels),
   163  		ExposedPorts: exposedPorts,
   164  		Tty:          c.Tty,
   165  		OpenStdin:    c.StdinOpen,
   166  		WorkingDir:   c.WorkingDir,
   167  		Volumes:      toMap(Filter(vols, isVolume)),
   168  		MacAddress:   c.MacAddress,
   169  	}
   170  
   171  	ulimits := []*units.Ulimit{}
   172  	if c.Ulimits.Elements != nil {
   173  		for _, ulimit := range c.Ulimits.Elements {
   174  			ulimits = append(ulimits, &units.Ulimit{
   175  				Name: ulimit.Name,
   176  				Soft: ulimit.Soft,
   177  				Hard: ulimit.Hard,
   178  			})
   179  		}
   180  	}
   181  
   182  	resources := container.Resources{
   183  		CgroupParent: c.CgroupParent,
   184  		Memory:       int64(c.MemLimit),
   185  		MemorySwap:   int64(c.MemSwapLimit),
   186  		CPUShares:    int64(c.CPUShares),
   187  		CPUQuota:     int64(c.CPUQuota),
   188  		CpusetCpus:   c.CPUSet,
   189  		Ulimits:      ulimits,
   190  		Devices:      deviceMappings,
   191  	}
   192  
   193  	networkMode := c.NetworkMode
   194  	if c.NetworkMode == "" {
   195  		if c.Networks != nil && len(c.Networks.Networks) > 0 {
   196  			networkMode = c.Networks.Networks[0].RealName
   197  		}
   198  	} else {
   199  		switch {
   200  		case strings.HasPrefix(c.NetworkMode, "service:"):
   201  			serviceName := c.NetworkMode[8:]
   202  			if serviceConfig, ok := ctx.Project.ServiceConfigs.Get(serviceName); ok {
   203  				// FIXME(vdemeester) this is actually not right, should be fixed but not there
   204  				service, err := ctx.ServiceFactory.Create(ctx.Project, serviceName, serviceConfig)
   205  				if err != nil {
   206  					return nil, nil, err
   207  				}
   208  				containers, err := service.Containers(context.Background())
   209  				if err != nil {
   210  					return nil, nil, err
   211  				}
   212  				if len(containers) != 0 {
   213  					container := containers[0]
   214  					containerID, err := container.ID()
   215  					if err != nil {
   216  						return nil, nil, err
   217  					}
   218  					networkMode = "container:" + containerID
   219  				}
   220  				// FIXME(vdemeester) log/warn in case of len(containers) == 0
   221  			}
   222  		case strings.HasPrefix(c.NetworkMode, "container:"):
   223  			containerName := c.NetworkMode[10:]
   224  			client := clientFactory.Create(nil)
   225  			container, err := composecontainer.Get(context.Background(), client, containerName)
   226  			if err != nil {
   227  				return nil, nil, err
   228  			}
   229  			networkMode = "container:" + container.ID
   230  		default:
   231  			// do nothing :)
   232  		}
   233  	}
   234  
   235  	hostConfig := &container.HostConfig{
   236  		VolumesFrom: volumesFrom,
   237  		CapAdd:      strslice.StrSlice(utils.CopySlice(c.CapAdd)),
   238  		CapDrop:     strslice.StrSlice(utils.CopySlice(c.CapDrop)),
   239  		ExtraHosts:  utils.CopySlice(c.ExtraHosts),
   240  		Privileged:  c.Privileged,
   241  		Binds:       Filter(vols, isBind),
   242  		DNS:         utils.CopySlice(c.DNS),
   243  		DNSSearch:   utils.CopySlice(c.DNSSearch),
   244  		LogConfig: container.LogConfig{
   245  			Type:   c.Logging.Driver,
   246  			Config: utils.CopyMap(c.Logging.Options),
   247  		},
   248  		NetworkMode:    container.NetworkMode(networkMode),
   249  		ReadonlyRootfs: c.ReadOnly,
   250  		PidMode:        container.PidMode(c.Pid),
   251  		UTSMode:        container.UTSMode(c.Uts),
   252  		IpcMode:        container.IpcMode(c.Ipc),
   253  		PortBindings:   portBindings,
   254  		RestartPolicy:  *restartPolicy,
   255  		ShmSize:        int64(c.ShmSize),
   256  		SecurityOpt:    utils.CopySlice(c.SecurityOpt),
   257  		VolumeDriver:   c.VolumeDriver,
   258  		Resources:      resources,
   259  	}
   260  
   261  	if config.Labels == nil {
   262  		config.Labels = map[string]string{}
   263  	}
   264  
   265  	return config, hostConfig, nil
   266  }
   267  
   268  func getVolumesFrom(volumesFrom []string, serviceConfigs *config.ServiceConfigs, projectName string) ([]string, error) {
   269  	volumes := []string{}
   270  	for _, volumeFrom := range volumesFrom {
   271  		if serviceConfig, ok := serviceConfigs.Get(volumeFrom); ok {
   272  			// It's a service - Use the first one
   273  			name := fmt.Sprintf("%s_%s_1", projectName, volumeFrom)
   274  			// If a container name is specified, use that instead
   275  			if serviceConfig.ContainerName != "" {
   276  				name = serviceConfig.ContainerName
   277  			}
   278  			volumes = append(volumes, name)
   279  		} else {
   280  			volumes = append(volumes, volumeFrom)
   281  		}
   282  	}
   283  	return volumes, nil
   284  }
   285  
   286  func parseDevices(devices []string) ([]container.DeviceMapping, error) {
   287  	// parse device mappings
   288  	deviceMappings := []container.DeviceMapping{}
   289  	for _, device := range devices {
   290  		v, err := opts.ParseDevice(device)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		deviceMappings = append(deviceMappings, container.DeviceMapping{
   295  			PathOnHost:        v.PathOnHost,
   296  			PathInContainer:   v.PathInContainer,
   297  			CgroupPermissions: v.CgroupPermissions,
   298  		})
   299  	}
   300  
   301  	return deviceMappings, nil
   302  }