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 }