create.go 28 KB

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