1/*
2   Copyright 2020 Docker Compose CLI authors
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package compose
18
19import (
20	"bytes"
21	"context"
22	"encoding/json"
23	"fmt"
24	"io/ioutil"
25	"path"
26	"path/filepath"
27	"strconv"
28	"strings"
29
30	"github.com/compose-spec/compose-go/types"
31	moby "github.com/docker/docker/api/types"
32	"github.com/docker/docker/api/types/blkiodev"
33	"github.com/docker/docker/api/types/container"
34	"github.com/docker/docker/api/types/mount"
35	"github.com/docker/docker/api/types/network"
36	"github.com/docker/docker/api/types/strslice"
37	volume_api "github.com/docker/docker/api/types/volume"
38	"github.com/docker/docker/errdefs"
39	"github.com/docker/go-connections/nat"
40	"github.com/docker/go-units"
41	"github.com/pkg/errors"
42	"github.com/sirupsen/logrus"
43
44	"github.com/docker/compose/v2/pkg/api"
45	"github.com/docker/compose/v2/pkg/progress"
46	"github.com/docker/compose/v2/pkg/utils"
47)
48
49func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
50	return progress.Run(ctx, func(ctx context.Context) error {
51		return s.create(ctx, project, options)
52	})
53}
54
55func (s *composeService) create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
56	if len(options.Services) == 0 {
57		options.Services = project.ServiceNames()
58	}
59
60	var observedState Containers
61	observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
62	if err != nil {
63		return err
64	}
65
66	err = s.ensureImagesExists(ctx, project, options.QuietPull)
67	if err != nil {
68		return err
69	}
70
71	prepareNetworks(project)
72
73	err = prepareVolumes(project)
74	if err != nil {
75		return err
76	}
77
78	if err := s.ensureNetworks(ctx, project.Networks); err != nil {
79		return err
80	}
81
82	if err := s.ensureProjectVolumes(ctx, project); err != nil {
83		return err
84	}
85
86	allServices := project.AllServices()
87	allServiceNames := []string{}
88	for _, service := range allServices {
89		allServiceNames = append(allServiceNames, service.Name)
90	}
91	orphans := observedState.filter(isNotService(allServiceNames...))
92	if len(orphans) > 0 && !options.IgnoreOrphans {
93		if options.RemoveOrphans {
94			w := progress.ContextWriter(ctx)
95			err := s.removeContainers(ctx, w, orphans, nil, false)
96			if err != nil {
97				return err
98			}
99		} else {
100			logrus.Warnf("Found orphan containers (%s) for this project. If "+
101				"you removed or renamed this service in your compose "+
102				"file, you can run this command with the "+
103				"--remove-orphans flag to clean it up.", orphans.names())
104		}
105	}
106
107	err = prepareServicesDependsOn(project)
108	if err != nil {
109		return err
110	}
111
112	return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
113}
114
115func prepareVolumes(p *types.Project) error {
116	for i := range p.Services {
117		volumesFrom, dependServices, err := getVolumesFrom(p, p.Services[i].VolumesFrom)
118		if err != nil {
119			return err
120		}
121		p.Services[i].VolumesFrom = volumesFrom
122		if len(dependServices) > 0 {
123			if p.Services[i].DependsOn == nil {
124				p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
125			}
126			for _, service := range p.Services {
127				if utils.StringContains(dependServices, service.Name) {
128					p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
129						Condition: types.ServiceConditionStarted,
130					}
131				}
132			}
133		}
134	}
135	return nil
136}
137
138func prepareNetworks(project *types.Project) {
139	for k, network := range project.Networks {
140		network.Labels = network.Labels.Add(api.NetworkLabel, k)
141		network.Labels = network.Labels.Add(api.ProjectLabel, project.Name)
142		network.Labels = network.Labels.Add(api.VersionLabel, api.ComposeVersion)
143		project.Networks[k] = network
144	}
145}
146
147func prepareServicesDependsOn(p *types.Project) error {
148	for i, service := range p.Services {
149		var dependencies []string
150		networkDependency := getDependentServiceFromMode(service.NetworkMode)
151		if networkDependency != "" {
152			dependencies = append(dependencies, networkDependency)
153		}
154
155		ipcDependency := getDependentServiceFromMode(service.Ipc)
156		if ipcDependency != "" {
157			dependencies = append(dependencies, ipcDependency)
158		}
159
160		pidDependency := getDependentServiceFromMode(service.Pid)
161		if pidDependency != "" {
162			dependencies = append(dependencies, pidDependency)
163		}
164
165		for _, vol := range service.VolumesFrom {
166			spec := strings.Split(vol, ":")
167			if len(spec) == 0 {
168				continue
169			}
170			if spec[0] == "container" {
171				continue
172			}
173			dependencies = append(dependencies, spec[0])
174		}
175
176		if len(dependencies) == 0 {
177			continue
178		}
179		if service.DependsOn == nil {
180			service.DependsOn = make(types.DependsOnConfig)
181		}
182		deps, err := p.GetServices(dependencies...)
183		if err != nil {
184			return err
185		}
186		for _, d := range deps {
187			if _, ok := service.DependsOn[d.Name]; !ok {
188				service.DependsOn[d.Name] = types.ServiceDependency{
189					Condition: types.ServiceConditionStarted,
190				}
191			}
192		}
193		p.Services[i] = service
194	}
195	return nil
196}
197
198func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
199	for _, network := range networks {
200		err := s.ensureNetwork(ctx, network)
201		if err != nil {
202			return err
203		}
204	}
205	return nil
206}
207
208func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
209	for k, volume := range project.Volumes {
210		volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
211		volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
212		volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
213		err := s.ensureVolume(ctx, volume)
214		if err != nil {
215			return err
216		}
217	}
218	return nil
219}
220
221func getImageName(service types.ServiceConfig, projectName string) string {
222	imageName := service.Image
223	if imageName == "" {
224		imageName = projectName + "_" + service.Name
225	}
226	return imageName
227}
228
229func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig,
230	number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
231
232	labels, err := s.prepareLabels(p, service, number)
233	if err != nil {
234		return nil, nil, nil, err
235	}
236
237	var (
238		runCmd     strslice.StrSlice
239		entrypoint strslice.StrSlice
240	)
241	if service.Command != nil {
242		runCmd = strslice.StrSlice(service.Command)
243	}
244	if service.Entrypoint != nil {
245		entrypoint = strslice.StrSlice(service.Entrypoint)
246	}
247
248	var (
249		tty       = service.Tty
250		stdinOpen = service.StdinOpen
251	)
252
253	volumeMounts, binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
254	if err != nil {
255		return nil, nil, nil, err
256	}
257
258	proxyConfig := types.MappingWithEquals(s.configFile.ParseProxyConfig(s.apiClient.DaemonHost(), nil))
259	env := proxyConfig.OverrideBy(service.Environment)
260
261	containerConfig := container.Config{
262		Hostname:        service.Hostname,
263		Domainname:      service.DomainName,
264		User:            service.User,
265		ExposedPorts:    buildContainerPorts(service),
266		Tty:             tty,
267		OpenStdin:       stdinOpen,
268		StdinOnce:       attachStdin && stdinOpen,
269		AttachStdin:     attachStdin,
270		AttachStderr:    true,
271		AttachStdout:    true,
272		Cmd:             runCmd,
273		Image:           getImageName(service, p.Name),
274		WorkingDir:      service.WorkingDir,
275		Entrypoint:      entrypoint,
276		NetworkDisabled: service.NetworkMode == "disabled",
277		MacAddress:      service.MacAddress,
278		Labels:          labels,
279		StopSignal:      service.StopSignal,
280		Env:             ToMobyEnv(env),
281		Healthcheck:     ToMobyHealthCheck(service.HealthCheck),
282		Volumes:         volumeMounts,
283		StopTimeout:     ToSeconds(service.StopGracePeriod),
284	}
285
286	portBindings := buildContainerPortBindingOptions(service)
287
288	resources := getDeployResources(service)
289
290	if service.NetworkMode == "" {
291		service.NetworkMode = getDefaultNetworkMode(p, service)
292	}
293
294	var networkConfig *network.NetworkingConfig
295
296	for _, id := range service.NetworksByPriority() {
297		net := p.Networks[id]
298		config := service.Networks[id]
299		var ipam *network.EndpointIPAMConfig
300		var (
301			ipv4Address string
302			ipv6Address string
303		)
304		if config != nil {
305			ipv4Address = config.Ipv4Address
306			ipv6Address = config.Ipv6Address
307			ipam = &network.EndpointIPAMConfig{
308				IPv4Address: ipv4Address,
309				IPv6Address: ipv6Address,
310			}
311		}
312		networkConfig = &network.NetworkingConfig{
313			EndpointsConfig: map[string]*network.EndpointSettings{
314				net.Name: {
315					Aliases:     getAliases(service, config),
316					IPAddress:   ipv4Address,
317					IPv6Gateway: ipv6Address,
318					IPAMConfig:  ipam,
319				},
320			},
321		}
322		break //nolint:staticcheck
323	}
324
325	tmpfs := map[string]string{}
326	for _, t := range service.Tmpfs {
327		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
328			tmpfs[arr[0]] = arr[1]
329		} else {
330			tmpfs[arr[0]] = ""
331		}
332	}
333
334	var logConfig container.LogConfig
335	if service.Logging != nil {
336		logConfig = container.LogConfig{
337			Type:   service.Logging.Driver,
338			Config: service.Logging.Options,
339		}
340	}
341
342	var volumesFrom []string
343	for _, v := range service.VolumesFrom {
344		if !strings.HasPrefix(v, "container:") {
345			return nil, nil, nil, fmt.Errorf("invalid volume_from: %s", v)
346		}
347		volumesFrom = append(volumesFrom, v[len("container:"):])
348	}
349
350	securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
351	if err != nil {
352		return nil, nil, nil, err
353	}
354	hostConfig := container.HostConfig{
355		AutoRemove:     autoRemove,
356		Binds:          binds,
357		Mounts:         mounts,
358		CapAdd:         strslice.StrSlice(service.CapAdd),
359		CapDrop:        strslice.StrSlice(service.CapDrop),
360		NetworkMode:    container.NetworkMode(service.NetworkMode),
361		Init:           service.Init,
362		IpcMode:        container.IpcMode(service.Ipc),
363		ReadonlyRootfs: service.ReadOnly,
364		RestartPolicy:  getRestartPolicy(service),
365		ShmSize:        int64(service.ShmSize),
366		Sysctls:        service.Sysctls,
367		PortBindings:   portBindings,
368		Resources:      resources,
369		VolumeDriver:   service.VolumeDriver,
370		VolumesFrom:    volumesFrom,
371		DNS:            service.DNS,
372		DNSSearch:      service.DNSSearch,
373		DNSOptions:     service.DNSOpts,
374		ExtraHosts:     service.ExtraHosts,
375		SecurityOpt:    securityOpts,
376		UsernsMode:     container.UsernsMode(service.UserNSMode),
377		Privileged:     service.Privileged,
378		PidMode:        container.PidMode(service.Pid),
379		Tmpfs:          tmpfs,
380		Isolation:      container.Isolation(service.Isolation),
381		LogConfig:      logConfig,
382	}
383
384	return &containerConfig, &hostConfig, networkConfig, nil
385}
386
387// copy/pasted from https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 + RelativePath
388// TODO find so way to share this code with docker/cli
389func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error) {
390	for key, opt := range securityOpts {
391		con := strings.SplitN(opt, "=", 2)
392		if len(con) == 1 && con[0] != "no-new-privileges" {
393			if strings.Contains(opt, ":") {
394				con = strings.SplitN(opt, ":", 2)
395			} else {
396				return securityOpts, errors.Errorf("Invalid security-opt: %q", opt)
397			}
398		}
399		if con[0] == "seccomp" && con[1] != "unconfined" {
400			f, err := ioutil.ReadFile(p.RelativePath(con[1]))
401			if err != nil {
402				return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
403			}
404			b := bytes.NewBuffer(nil)
405			if err := json.Compact(b, f); err != nil {
406				return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
407			}
408			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
409		}
410	}
411
412	return securityOpts, nil
413}
414
415func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
416	labels := map[string]string{}
417	for k, v := range service.Labels {
418		labels[k] = v
419	}
420
421	labels[api.ProjectLabel] = p.Name
422	labels[api.ServiceLabel] = service.Name
423	labels[api.VersionLabel] = api.ComposeVersion
424	if _, ok := service.Labels[api.OneoffLabel]; !ok {
425		labels[api.OneoffLabel] = "False"
426	}
427
428	hash, err := ServiceHash(service)
429	if err != nil {
430		return nil, err
431	}
432
433	labels[api.ConfigHashLabel] = hash
434	labels[api.WorkingDirLabel] = p.WorkingDir
435	labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
436	labels[api.ContainerNumberLabel] = strconv.Itoa(number)
437	var dependencies []string
438	for s := range service.DependsOn {
439		dependencies = append(dependencies, s)
440	}
441	labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
442	return labels, nil
443}
444
445func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
446	mode := "none"
447	if len(project.Networks) > 0 {
448		for name := range getNetworksForService(service) {
449			mode = project.Networks[name].Name
450			break
451		}
452	}
453	return mode
454}
455
456func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
457	var restart container.RestartPolicy
458	if service.Restart != "" {
459		split := strings.Split(service.Restart, ":")
460		var attempts int
461		if len(split) > 1 {
462			attempts, _ = strconv.Atoi(split[1])
463		}
464		restart = container.RestartPolicy{
465			Name:              split[0],
466			MaximumRetryCount: attempts,
467		}
468	}
469	if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
470		policy := *service.Deploy.RestartPolicy
471		var attempts int
472		if policy.MaxAttempts != nil {
473			attempts = int(*policy.MaxAttempts)
474		}
475		restart = container.RestartPolicy{
476			Name:              policy.Condition,
477			MaximumRetryCount: attempts,
478		}
479	}
480	return restart
481}
482
483func getDeployResources(s types.ServiceConfig) container.Resources {
484	var swappiness *int64
485	if s.MemSwappiness != 0 {
486		val := int64(s.MemSwappiness)
487		swappiness = &val
488	}
489	resources := container.Resources{
490		CgroupParent:       s.CgroupParent,
491		Memory:             int64(s.MemLimit),
492		MemorySwap:         int64(s.MemSwapLimit),
493		MemorySwappiness:   swappiness,
494		MemoryReservation:  int64(s.MemReservation),
495		CPUCount:           s.CPUCount,
496		CPUPeriod:          s.CPUPeriod,
497		CPUQuota:           s.CPUQuota,
498		CPURealtimePeriod:  s.CPURTPeriod,
499		CPURealtimeRuntime: s.CPURTRuntime,
500		CPUShares:          s.CPUShares,
501		CPUPercent:         int64(s.CPUS * 100),
502		CpusetCpus:         s.CPUSet,
503	}
504
505	setBlkio(s.BlkioConfig, &resources)
506
507	if s.Deploy != nil {
508		setLimits(s.Deploy.Resources.Limits, &resources)
509		setReservations(s.Deploy.Resources.Reservations, &resources)
510	}
511
512	for _, device := range s.Devices {
513		// FIXME should use docker/cli parseDevice, unfortunately private
514		src := ""
515		dst := ""
516		permissions := "rwm"
517		arr := strings.Split(device, ":")
518		switch len(arr) {
519		case 3:
520			permissions = arr[2]
521			fallthrough
522		case 2:
523			dst = arr[1]
524			fallthrough
525		case 1:
526			src = arr[0]
527		}
528		resources.Devices = append(resources.Devices, container.DeviceMapping{
529			PathOnHost:        src,
530			PathInContainer:   dst,
531			CgroupPermissions: permissions,
532		})
533	}
534
535	for name, u := range s.Ulimits {
536		soft := u.Single
537		if u.Soft != 0 {
538			soft = u.Soft
539		}
540		hard := u.Single
541		if u.Hard != 0 {
542			hard = u.Hard
543		}
544		resources.Ulimits = append(resources.Ulimits, &units.Ulimit{
545			Name: name,
546			Hard: int64(hard),
547			Soft: int64(soft),
548		})
549	}
550	return resources
551}
552
553func setReservations(reservations *types.Resource, resources *container.Resources) {
554	if reservations == nil {
555		return
556	}
557	for _, device := range reservations.Devices {
558		resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
559			Capabilities: [][]string{device.Capabilities},
560			Count:        int(device.Count),
561			DeviceIDs:    device.IDs,
562			Driver:       device.Driver,
563		})
564	}
565}
566
567func setLimits(limits *types.Resource, resources *container.Resources) {
568	if limits == nil {
569		return
570	}
571	if limits.MemoryBytes != 0 {
572		resources.Memory = int64(limits.MemoryBytes)
573	}
574	if limits.NanoCPUs != "" {
575		i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
576		resources.NanoCPUs = i
577	}
578}
579
580func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
581	if blkio == nil {
582		return
583	}
584	resources.BlkioWeight = blkio.Weight
585	for _, b := range blkio.WeightDevice {
586		resources.BlkioWeightDevice = append(resources.BlkioWeightDevice, &blkiodev.WeightDevice{
587			Path:   b.Path,
588			Weight: b.Weight,
589		})
590	}
591	for _, b := range blkio.DeviceReadBps {
592		resources.BlkioDeviceReadBps = append(resources.BlkioDeviceReadBps, &blkiodev.ThrottleDevice{
593			Path: b.Path,
594			Rate: b.Rate,
595		})
596	}
597	for _, b := range blkio.DeviceReadIOps {
598		resources.BlkioDeviceReadIOps = append(resources.BlkioDeviceReadIOps, &blkiodev.ThrottleDevice{
599			Path: b.Path,
600			Rate: b.Rate,
601		})
602	}
603	for _, b := range blkio.DeviceWriteBps {
604		resources.BlkioDeviceWriteBps = append(resources.BlkioDeviceWriteBps, &blkiodev.ThrottleDevice{
605			Path: b.Path,
606			Rate: b.Rate,
607		})
608	}
609	for _, b := range blkio.DeviceWriteIOps {
610		resources.BlkioDeviceWriteIOps = append(resources.BlkioDeviceWriteIOps, &blkiodev.ThrottleDevice{
611			Path: b.Path,
612			Rate: b.Rate,
613		})
614	}
615}
616
617func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
618	ports := nat.PortSet{}
619	for _, s := range s.Expose {
620		p := nat.Port(s)
621		ports[p] = struct{}{}
622	}
623	for _, p := range s.Ports {
624		p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
625		ports[p] = struct{}{}
626	}
627	return ports
628}
629
630func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
631	bindings := nat.PortMap{}
632	for _, port := range s.Ports {
633		p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
634		bind := bindings[p]
635		binding := nat.PortBinding{
636			HostIP: port.HostIP,
637		}
638		if port.Published > 0 {
639			binding.HostPort = fmt.Sprint(port.Published)
640		}
641		bind = append(bind, binding)
642		bindings[p] = bind
643	}
644	return bindings
645}
646
647func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
648	var volumes = []string{}
649	var services = []string{}
650	// parse volumes_from
651	if len(volumesFrom) == 0 {
652		return volumes, services, nil
653	}
654	for _, vol := range volumesFrom {
655		spec := strings.Split(vol, ":")
656		if len(spec) == 0 {
657			continue
658		}
659		if spec[0] == "container" {
660			volumes = append(volumes, strings.Join(spec[1:], ":"))
661			continue
662		}
663		serviceName := spec[0]
664		services = append(services, serviceName)
665		service, err := project.GetService(serviceName)
666		if err != nil {
667			return nil, nil, err
668		}
669
670		firstContainer := getContainerName(project.Name, service, 1)
671		v := fmt.Sprintf("container:%s", firstContainer)
672		if len(spec) > 2 {
673			v = fmt.Sprintf("container:%s:%s", firstContainer, strings.Join(spec[1:], ":"))
674		}
675		volumes = append(volumes, v)
676	}
677	return volumes, services, nil
678
679}
680
681func getDependentServiceFromMode(mode string) string {
682	if strings.HasPrefix(mode, types.NetworkModeServicePrefix) {
683		return mode[len(types.NetworkModeServicePrefix):]
684	}
685	return ""
686}
687
688func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
689	inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
690	var mounts = []mount.Mount{}
691
692	image := getImageName(service, p.Name)
693	imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
694	if err != nil {
695		return nil, nil, nil, err
696	}
697
698	mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
699	if err != nil {
700		return nil, nil, nil, err
701	}
702
703	volumeMounts := map[string]struct{}{}
704	binds := []string{}
705MOUNTS:
706	for _, m := range mountOptions {
707		volumeMounts[m.Target] = struct{}{}
708		// `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
709		if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
710			for _, v := range service.Volumes {
711				if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
712					mode := "rw"
713					if m.ReadOnly {
714						mode = "ro"
715					}
716					binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
717					continue MOUNTS
718				}
719			}
720		}
721		mounts = append(mounts, m)
722	}
723	return volumeMounts, binds, mounts, nil
724}
725
726func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
727	var mounts = map[string]mount.Mount{}
728	if inherit != nil {
729		for _, m := range inherit.Mounts {
730			if m.Type == "tmpfs" {
731				continue
732			}
733			src := m.Source
734			if m.Type == "volume" {
735				src = m.Name
736			}
737			m.Destination = path.Clean(m.Destination)
738
739			if img.Config != nil {
740				if _, ok := img.Config.Volumes[m.Destination]; ok {
741					// inherit previous container's anonymous volume
742					mounts[m.Destination] = mount.Mount{
743						Type:     m.Type,
744						Source:   src,
745						Target:   m.Destination,
746						ReadOnly: !m.RW,
747					}
748				}
749			}
750			for i, v := range s.Volumes {
751				if v.Target != m.Destination {
752					continue
753				}
754				if v.Source == "" {
755					// inherit previous container's anonymous volume
756					mounts[m.Destination] = mount.Mount{
757						Type:     m.Type,
758						Source:   src,
759						Target:   m.Destination,
760						ReadOnly: !m.RW,
761					}
762					// Avoid mount to be later re-defined
763					l := len(s.Volumes) - 1
764					s.Volumes[i] = s.Volumes[l]
765					s.Volumes = s.Volumes[:l]
766				}
767			}
768		}
769	}
770
771	mounts, err := fillBindMounts(p, s, mounts)
772	if err != nil {
773		return nil, err
774	}
775
776	values := make([]mount.Mount, 0, len(mounts))
777	for _, v := range mounts {
778		values = append(values, v)
779	}
780	return values, nil
781}
782
783func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
784	for _, v := range s.Volumes {
785		bindMount, err := buildMount(p, v)
786		if err != nil {
787			return nil, err
788		}
789		m[bindMount.Target] = bindMount
790	}
791
792	secrets, err := buildContainerSecretMounts(p, s)
793	if err != nil {
794		return nil, err
795	}
796	for _, s := range secrets {
797		if _, found := m[s.Target]; found {
798			continue
799		}
800		m[s.Target] = s
801	}
802
803	configs, err := buildContainerConfigMounts(p, s)
804	if err != nil {
805		return nil, err
806	}
807	for _, c := range configs {
808		if _, found := m[c.Target]; found {
809			continue
810		}
811		m[c.Target] = c
812	}
813	return m, nil
814}
815
816func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
817	var mounts = map[string]mount.Mount{}
818
819	configsBaseDir := "/"
820	for _, config := range s.Configs {
821		target := config.Target
822		if config.Target == "" {
823			target = configsBaseDir + config.Source
824		} else if !isUnixAbs(config.Target) {
825			target = configsBaseDir + config.Target
826		}
827
828		definedConfig := p.Configs[config.Source]
829		if definedConfig.External.External {
830			return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
831		}
832
833		bindMount, err := buildMount(p, types.ServiceVolumeConfig{
834			Type:     types.VolumeTypeBind,
835			Source:   definedConfig.File,
836			Target:   target,
837			ReadOnly: true,
838		})
839		if err != nil {
840			return nil, err
841		}
842		mounts[target] = bindMount
843	}
844	values := make([]mount.Mount, 0, len(mounts))
845	for _, v := range mounts {
846		values = append(values, v)
847	}
848	return values, nil
849}
850
851func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
852	var mounts = map[string]mount.Mount{}
853
854	secretsDir := "/run/secrets/"
855	for _, secret := range s.Secrets {
856		target := secret.Target
857		if secret.Target == "" {
858			target = secretsDir + secret.Source
859		} else if !isUnixAbs(secret.Target) {
860			target = secretsDir + secret.Target
861		}
862
863		definedSecret := p.Secrets[secret.Source]
864		if definedSecret.External.External {
865			return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
866		}
867
868		mount, err := buildMount(p, types.ServiceVolumeConfig{
869			Type:     types.VolumeTypeBind,
870			Source:   definedSecret.File,
871			Target:   target,
872			ReadOnly: true,
873		})
874		if err != nil {
875			return nil, err
876		}
877		mounts[target] = mount
878	}
879	values := make([]mount.Mount, 0, len(mounts))
880	for _, v := range mounts {
881		values = append(values, v)
882	}
883	return values, nil
884}
885
886func isUnixAbs(path string) bool {
887	return strings.HasPrefix(path, "/")
888}
889
890func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
891	source := volume.Source
892	// on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
893	// do not replace these with  filepath.Abs(source) that will include a default drive.
894	if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) && !strings.HasPrefix(source, "/") {
895		// volume source has already been prefixed with workdir if required, by compose-go project loader
896		var err error
897		source, err = filepath.Abs(source)
898		if err != nil {
899			return mount.Mount{}, err
900		}
901	}
902	if volume.Type == types.VolumeTypeVolume {
903		if volume.Source != "" {
904			pVolume, ok := project.Volumes[volume.Source]
905			if ok {
906				source = pVolume.Name
907			}
908		}
909	}
910
911	bind, vol, tmpfs := buildMountOptions(volume)
912
913	volume.Target = path.Clean(volume.Target)
914
915	return mount.Mount{
916		Type:          mount.Type(volume.Type),
917		Source:        source,
918		Target:        volume.Target,
919		ReadOnly:      volume.ReadOnly,
920		Consistency:   mount.Consistency(volume.Consistency),
921		BindOptions:   bind,
922		VolumeOptions: vol,
923		TmpfsOptions:  tmpfs,
924	}, nil
925}
926
927func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
928	switch volume.Type {
929	case "bind":
930		if volume.Volume != nil {
931			logrus.Warnf("mount of type `bind` should not define `volume` option")
932		}
933		if volume.Tmpfs != nil {
934			logrus.Warnf("mount of type `tmpfs` should not define `tmpfs` option")
935		}
936		return buildBindOption(volume.Bind), nil, nil
937	case "volume":
938		if volume.Bind != nil {
939			logrus.Warnf("mount of type `volume` should not define `bind` option")
940		}
941		if volume.Tmpfs != nil {
942			logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
943		}
944		return nil, buildVolumeOptions(volume.Volume), nil
945	case "tmpfs":
946		if volume.Bind != nil {
947			logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
948		}
949		if volume.Tmpfs != nil {
950			logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
951		}
952		return nil, nil, buildTmpfsOptions(volume.Tmpfs)
953	}
954	return nil, nil, nil
955}
956
957func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
958	if bind == nil {
959		return nil
960	}
961	return &mount.BindOptions{
962		Propagation: mount.Propagation(bind.Propagation),
963		// NonRecursive: false, FIXME missing from model ?
964	}
965}
966
967func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
968	if vol == nil {
969		return nil
970	}
971	return &mount.VolumeOptions{
972		NoCopy: vol.NoCopy,
973		// Labels:       , // FIXME missing from model ?
974		// DriverConfig: , // FIXME missing from model ?
975	}
976}
977
978func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
979	if tmpfs == nil {
980		return nil
981	}
982	return &mount.TmpfsOptions{
983		SizeBytes: int64(tmpfs.Size),
984		// Mode:      , // FIXME missing from model ?
985	}
986}
987
988func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
989	aliases := []string{s.Name}
990	if c != nil {
991		aliases = append(aliases, c.Aliases...)
992	}
993	return aliases
994}
995
996func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
997	if len(s.Networks) > 0 {
998		return s.Networks
999	}
1000	if s.NetworkMode != "" {
1001		return nil
1002	}
1003	return map[string]*types.ServiceNetworkConfig{"default": nil}
1004}
1005
1006func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
1007	_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
1008	if err != nil {
1009		if errdefs.IsNotFound(err) {
1010			if n.External.External {
1011				return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
1012			}
1013			var ipam *network.IPAM
1014			if n.Ipam.Config != nil {
1015				var config []network.IPAMConfig
1016				for _, pool := range n.Ipam.Config {
1017					config = append(config, network.IPAMConfig{
1018						Subnet:     pool.Subnet,
1019						IPRange:    pool.IPRange,
1020						Gateway:    pool.Gateway,
1021						AuxAddress: pool.AuxiliaryAddresses,
1022					})
1023				}
1024				ipam = &network.IPAM{
1025					Driver: n.Ipam.Driver,
1026					Config: config,
1027				}
1028			}
1029			createOpts := moby.NetworkCreate{
1030				// TODO NameSpace Labels
1031				Labels:     n.Labels,
1032				Driver:     n.Driver,
1033				Options:    n.DriverOpts,
1034				Internal:   n.Internal,
1035				Attachable: n.Attachable,
1036				IPAM:       ipam,
1037			}
1038
1039			if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
1040				createOpts.IPAM = &network.IPAM{}
1041			}
1042
1043			if n.Ipam.Driver != "" {
1044				createOpts.IPAM.Driver = n.Ipam.Driver
1045			}
1046
1047			for _, ipamConfig := range n.Ipam.Config {
1048				config := network.IPAMConfig{
1049					Subnet: ipamConfig.Subnet,
1050				}
1051				createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
1052			}
1053			networkEventName := fmt.Sprintf("Network %s", n.Name)
1054			w := progress.ContextWriter(ctx)
1055			w.Event(progress.CreatingEvent(networkEventName))
1056			if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
1057				w.Event(progress.ErrorEvent(networkEventName))
1058				return errors.Wrapf(err, "failed to create network %s", n.Name)
1059			}
1060			w.Event(progress.CreatedEvent(networkEventName))
1061			return nil
1062		}
1063		return err
1064	}
1065	return nil
1066}
1067
1068func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error {
1069	w := progress.ContextWriter(ctx)
1070	eventName := fmt.Sprintf("Network %s", networkName)
1071	w.Event(progress.RemovingEvent(eventName))
1072
1073	if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
1074		w.Event(progress.ErrorEvent(eventName))
1075		return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
1076	}
1077
1078	w.Event(progress.RemovedEvent(eventName))
1079	return nil
1080}
1081
1082func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
1083	// TODO could identify volume by label vs name
1084	_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
1085	if err != nil {
1086		if !errdefs.IsNotFound(err) {
1087			return err
1088		}
1089		eventName := fmt.Sprintf("Volume %q", volume.Name)
1090		w := progress.ContextWriter(ctx)
1091		w.Event(progress.CreatingEvent(eventName))
1092		_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
1093			Labels:     volume.Labels,
1094			Name:       volume.Name,
1095			Driver:     volume.Driver,
1096			DriverOpts: volume.DriverOpts,
1097		})
1098		if err != nil {
1099			w.Event(progress.ErrorEvent(eventName))
1100			return err
1101		}
1102		w.Event(progress.CreatedEvent(eventName))
1103	}
1104	return nil
1105}
1106