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 }