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