create.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  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. labels, err := s.prepareLabels(p, service, number)
  200. if err != nil {
  201. return nil, nil, nil, err
  202. }
  203. var (
  204. runCmd strslice.StrSlice
  205. entrypoint strslice.StrSlice
  206. )
  207. if service.Command != nil {
  208. runCmd = strslice.StrSlice(service.Command)
  209. }
  210. if service.Entrypoint != nil {
  211. entrypoint = strslice.StrSlice(service.Entrypoint)
  212. }
  213. var (
  214. tty = service.Tty
  215. stdinOpen = service.StdinOpen
  216. attachStdin = false
  217. )
  218. volumeMounts, binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
  219. if err != nil {
  220. return nil, nil, nil, err
  221. }
  222. proxyConfig := types.MappingWithEquals(s.configFile.ParseProxyConfig(s.apiClient.DaemonHost(), nil))
  223. env := proxyConfig.OverrideBy(service.Environment)
  224. containerConfig := container.Config{
  225. Hostname: service.Hostname,
  226. Domainname: service.DomainName,
  227. User: service.User,
  228. ExposedPorts: buildContainerPorts(service),
  229. Tty: tty,
  230. OpenStdin: stdinOpen,
  231. StdinOnce: attachStdin && stdinOpen,
  232. AttachStdin: attachStdin,
  233. AttachStderr: true,
  234. AttachStdout: true,
  235. Cmd: runCmd,
  236. Image: getImageName(service, p.Name),
  237. WorkingDir: service.WorkingDir,
  238. Entrypoint: entrypoint,
  239. NetworkDisabled: service.NetworkMode == "disabled",
  240. MacAddress: service.MacAddress,
  241. Labels: labels,
  242. StopSignal: service.StopSignal,
  243. Env: ToMobyEnv(env),
  244. Healthcheck: ToMobyHealthCheck(service.HealthCheck),
  245. Volumes: volumeMounts,
  246. StopTimeout: ToSeconds(service.StopGracePeriod),
  247. }
  248. portBindings := buildContainerPortBindingOptions(service)
  249. resources := getDeployResources(service)
  250. if service.NetworkMode == "" {
  251. service.NetworkMode = getDefaultNetworkMode(p, service)
  252. }
  253. var networkConfig *network.NetworkingConfig
  254. for _, id := range service.NetworksByPriority() {
  255. net := p.Networks[id]
  256. config := service.Networks[id]
  257. var ipam *network.EndpointIPAMConfig
  258. var (
  259. ipv4Address string
  260. ipv6Address string
  261. )
  262. if config != nil {
  263. ipv4Address = config.Ipv4Address
  264. ipv6Address = config.Ipv6Address
  265. ipam = &network.EndpointIPAMConfig{
  266. IPv4Address: ipv4Address,
  267. IPv6Address: ipv6Address,
  268. }
  269. }
  270. networkConfig = &network.NetworkingConfig{
  271. EndpointsConfig: map[string]*network.EndpointSettings{
  272. net.Name: {
  273. Aliases: getAliases(service, config),
  274. IPAddress: ipv4Address,
  275. IPv6Gateway: ipv6Address,
  276. IPAMConfig: ipam,
  277. },
  278. },
  279. }
  280. break //nolint:staticcheck
  281. }
  282. tmpfs := map[string]string{}
  283. for _, t := range service.Tmpfs {
  284. if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
  285. tmpfs[arr[0]] = arr[1]
  286. } else {
  287. tmpfs[arr[0]] = ""
  288. }
  289. }
  290. var logConfig container.LogConfig
  291. if service.Logging != nil {
  292. logConfig = container.LogConfig{
  293. Type: service.Logging.Driver,
  294. Config: service.Logging.Options,
  295. }
  296. }
  297. var volumesFrom []string
  298. for _, v := range service.VolumesFrom {
  299. if !strings.HasPrefix(v, "container:") {
  300. return nil, nil, nil, fmt.Errorf("invalid volume_from: %s", v)
  301. }
  302. volumesFrom = append(volumesFrom, v[len("container:"):])
  303. }
  304. hostConfig := container.HostConfig{
  305. AutoRemove: autoRemove,
  306. Binds: binds,
  307. Mounts: mounts,
  308. CapAdd: strslice.StrSlice(service.CapAdd),
  309. CapDrop: strslice.StrSlice(service.CapDrop),
  310. NetworkMode: container.NetworkMode(service.NetworkMode),
  311. Init: service.Init,
  312. IpcMode: container.IpcMode(service.Ipc),
  313. ReadonlyRootfs: service.ReadOnly,
  314. RestartPolicy: getRestartPolicy(service),
  315. ShmSize: int64(service.ShmSize),
  316. Sysctls: service.Sysctls,
  317. PortBindings: portBindings,
  318. Resources: resources,
  319. VolumeDriver: service.VolumeDriver,
  320. VolumesFrom: volumesFrom,
  321. DNS: service.DNS,
  322. DNSSearch: service.DNSSearch,
  323. DNSOptions: service.DNSOpts,
  324. ExtraHosts: service.ExtraHosts,
  325. SecurityOpt: service.SecurityOpt,
  326. UsernsMode: container.UsernsMode(service.UserNSMode),
  327. Privileged: service.Privileged,
  328. PidMode: container.PidMode(service.Pid),
  329. Tmpfs: tmpfs,
  330. Isolation: container.Isolation(service.Isolation),
  331. LogConfig: logConfig,
  332. }
  333. return &containerConfig, &hostConfig, networkConfig, nil
  334. }
  335. func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
  336. labels := map[string]string{}
  337. for k, v := range service.Labels {
  338. labels[k] = v
  339. }
  340. labels[api.ProjectLabel] = p.Name
  341. labels[api.ServiceLabel] = service.Name
  342. labels[api.VersionLabel] = api.ComposeVersion
  343. if _, ok := service.Labels[api.OneoffLabel]; !ok {
  344. labels[api.OneoffLabel] = "False"
  345. }
  346. hash, err := ServiceHash(service)
  347. if err != nil {
  348. return nil, err
  349. }
  350. labels[api.ConfigHashLabel] = hash
  351. labels[api.WorkingDirLabel] = p.WorkingDir
  352. labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
  353. labels[api.ContainerNumberLabel] = strconv.Itoa(number)
  354. var dependencies []string
  355. for s := range service.DependsOn {
  356. dependencies = append(dependencies, s)
  357. }
  358. labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
  359. return labels, nil
  360. }
  361. func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
  362. mode := "none"
  363. if len(project.Networks) > 0 {
  364. for name := range getNetworksForService(service) {
  365. mode = project.Networks[name].Name
  366. break
  367. }
  368. }
  369. return mode
  370. }
  371. func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
  372. var restart container.RestartPolicy
  373. if service.Restart != "" {
  374. split := strings.Split(service.Restart, ":")
  375. var attempts int
  376. if len(split) > 1 {
  377. attempts, _ = strconv.Atoi(split[1])
  378. }
  379. restart = container.RestartPolicy{
  380. Name: split[0],
  381. MaximumRetryCount: attempts,
  382. }
  383. }
  384. if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
  385. policy := *service.Deploy.RestartPolicy
  386. var attempts int
  387. if policy.MaxAttempts != nil {
  388. attempts = int(*policy.MaxAttempts)
  389. }
  390. restart = container.RestartPolicy{
  391. Name: policy.Condition,
  392. MaximumRetryCount: attempts,
  393. }
  394. }
  395. return restart
  396. }
  397. func getDeployResources(s types.ServiceConfig) container.Resources {
  398. var swappiness *int64
  399. if s.MemSwappiness != 0 {
  400. val := int64(s.MemSwappiness)
  401. swappiness = &val
  402. }
  403. resources := container.Resources{
  404. CgroupParent: s.CgroupParent,
  405. Memory: int64(s.MemLimit),
  406. MemorySwap: int64(s.MemSwapLimit),
  407. MemorySwappiness: swappiness,
  408. MemoryReservation: int64(s.MemReservation),
  409. CPUCount: s.CPUCount,
  410. CPUPeriod: s.CPUPeriod,
  411. CPUQuota: s.CPUQuota,
  412. CPURealtimePeriod: s.CPURTPeriod,
  413. CPURealtimeRuntime: s.CPURTRuntime,
  414. CPUShares: s.CPUShares,
  415. CPUPercent: int64(s.CPUS * 100),
  416. CpusetCpus: s.CPUSet,
  417. }
  418. setBlkio(s.BlkioConfig, &resources)
  419. if s.Deploy != nil {
  420. setLimits(s.Deploy.Resources.Limits, &resources)
  421. setReservations(s.Deploy.Resources.Reservations, &resources)
  422. }
  423. for _, device := range s.Devices {
  424. // FIXME should use docker/cli parseDevice, unfortunately private
  425. src := ""
  426. dst := ""
  427. permissions := "rwm"
  428. arr := strings.Split(device, ":")
  429. switch len(arr) {
  430. case 3:
  431. permissions = arr[2]
  432. fallthrough
  433. case 2:
  434. dst = arr[1]
  435. fallthrough
  436. case 1:
  437. src = arr[0]
  438. }
  439. resources.Devices = append(resources.Devices, container.DeviceMapping{
  440. PathOnHost: src,
  441. PathInContainer: dst,
  442. CgroupPermissions: permissions,
  443. })
  444. }
  445. for name, u := range s.Ulimits {
  446. soft := u.Single
  447. if u.Soft != 0 {
  448. soft = u.Soft
  449. }
  450. hard := u.Single
  451. if u.Hard != 0 {
  452. hard = u.Hard
  453. }
  454. resources.Ulimits = append(resources.Ulimits, &units.Ulimit{
  455. Name: name,
  456. Hard: int64(hard),
  457. Soft: int64(soft),
  458. })
  459. }
  460. return resources
  461. }
  462. func setReservations(reservations *types.Resource, resources *container.Resources) {
  463. if reservations == nil {
  464. return
  465. }
  466. for _, device := range reservations.Devices {
  467. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  468. Capabilities: [][]string{device.Capabilities},
  469. Count: int(device.Count),
  470. DeviceIDs: device.IDs,
  471. Driver: device.Driver,
  472. })
  473. }
  474. }
  475. func setLimits(limits *types.Resource, resources *container.Resources) {
  476. if limits == nil {
  477. return
  478. }
  479. if limits.MemoryBytes != 0 {
  480. resources.Memory = int64(limits.MemoryBytes)
  481. }
  482. if limits.NanoCPUs != "" {
  483. i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
  484. resources.NanoCPUs = i
  485. }
  486. }
  487. func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
  488. if blkio == nil {
  489. return
  490. }
  491. resources.BlkioWeight = blkio.Weight
  492. for _, b := range blkio.WeightDevice {
  493. resources.BlkioWeightDevice = append(resources.BlkioWeightDevice, &blkiodev.WeightDevice{
  494. Path: b.Path,
  495. Weight: b.Weight,
  496. })
  497. }
  498. for _, b := range blkio.DeviceReadBps {
  499. resources.BlkioDeviceReadBps = append(resources.BlkioDeviceReadBps, &blkiodev.ThrottleDevice{
  500. Path: b.Path,
  501. Rate: b.Rate,
  502. })
  503. }
  504. for _, b := range blkio.DeviceReadIOps {
  505. resources.BlkioDeviceReadIOps = append(resources.BlkioDeviceReadIOps, &blkiodev.ThrottleDevice{
  506. Path: b.Path,
  507. Rate: b.Rate,
  508. })
  509. }
  510. for _, b := range blkio.DeviceWriteBps {
  511. resources.BlkioDeviceWriteBps = append(resources.BlkioDeviceWriteBps, &blkiodev.ThrottleDevice{
  512. Path: b.Path,
  513. Rate: b.Rate,
  514. })
  515. }
  516. for _, b := range blkio.DeviceWriteIOps {
  517. resources.BlkioDeviceWriteIOps = append(resources.BlkioDeviceWriteIOps, &blkiodev.ThrottleDevice{
  518. Path: b.Path,
  519. Rate: b.Rate,
  520. })
  521. }
  522. }
  523. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  524. ports := nat.PortSet{}
  525. for _, s := range s.Expose {
  526. p := nat.Port(s)
  527. ports[p] = struct{}{}
  528. }
  529. for _, p := range s.Ports {
  530. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  531. ports[p] = struct{}{}
  532. }
  533. return ports
  534. }
  535. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  536. bindings := nat.PortMap{}
  537. for _, port := range s.Ports {
  538. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  539. bind := bindings[p]
  540. binding := nat.PortBinding{
  541. HostIP: port.HostIP,
  542. }
  543. if port.Published > 0 {
  544. binding.HostPort = fmt.Sprint(port.Published)
  545. }
  546. bind = append(bind, binding)
  547. bindings[p] = bind
  548. }
  549. return bindings
  550. }
  551. func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
  552. var volumes = []string{}
  553. var services = []string{}
  554. // parse volumes_from
  555. if len(volumesFrom) == 0 {
  556. return volumes, services, nil
  557. }
  558. for _, vol := range volumesFrom {
  559. spec := strings.Split(vol, ":")
  560. if len(spec) == 0 {
  561. continue
  562. }
  563. if spec[0] == "container" {
  564. volumes = append(volumes, strings.Join(spec[1:], ":"))
  565. continue
  566. }
  567. serviceName := spec[0]
  568. services = append(services, serviceName)
  569. service, err := project.GetService(serviceName)
  570. if err != nil {
  571. return nil, nil, err
  572. }
  573. firstContainer := getContainerName(project.Name, service, 1)
  574. v := fmt.Sprintf("container:%s", firstContainer)
  575. if len(spec) > 2 {
  576. v = fmt.Sprintf("container:%s:%s", firstContainer, strings.Join(spec[1:], ":"))
  577. }
  578. volumes = append(volumes, v)
  579. }
  580. return volumes, services, nil
  581. }
  582. func getDependentServiceFromMode(mode string) string {
  583. if strings.HasPrefix(mode, types.NetworkModeServicePrefix) {
  584. return mode[len(types.NetworkModeServicePrefix):]
  585. }
  586. return ""
  587. }
  588. func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
  589. inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
  590. var mounts = []mount.Mount{}
  591. image := getImageName(service, p.Name)
  592. imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
  593. if err != nil {
  594. return nil, nil, nil, err
  595. }
  596. mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
  597. if err != nil {
  598. return nil, nil, nil, err
  599. }
  600. volumeMounts := map[string]struct{}{}
  601. binds := []string{}
  602. MOUNTS:
  603. for _, m := range mountOptions {
  604. volumeMounts[m.Target] = struct{}{}
  605. // `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
  606. if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
  607. for _, v := range service.Volumes {
  608. if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
  609. mode := "rw"
  610. if m.ReadOnly {
  611. mode = "ro"
  612. }
  613. binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
  614. continue MOUNTS
  615. }
  616. }
  617. }
  618. mounts = append(mounts, m)
  619. }
  620. return volumeMounts, binds, mounts, nil
  621. }
  622. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  623. var mounts = map[string]mount.Mount{}
  624. if inherit != nil {
  625. for _, m := range inherit.Mounts {
  626. if m.Type == "tmpfs" {
  627. continue
  628. }
  629. src := m.Source
  630. if m.Type == "volume" {
  631. src = m.Name
  632. }
  633. m.Destination = path.Clean(m.Destination)
  634. if img.Config != nil {
  635. if _, ok := img.Config.Volumes[m.Destination]; ok {
  636. // inherit previous container's anonymous volume
  637. mounts[m.Destination] = mount.Mount{
  638. Type: m.Type,
  639. Source: src,
  640. Target: m.Destination,
  641. ReadOnly: !m.RW,
  642. }
  643. }
  644. }
  645. for i, v := range s.Volumes {
  646. if v.Target != m.Destination {
  647. continue
  648. }
  649. if v.Source == "" {
  650. // inherit previous container's anonymous volume
  651. mounts[m.Destination] = mount.Mount{
  652. Type: m.Type,
  653. Source: src,
  654. Target: m.Destination,
  655. ReadOnly: !m.RW,
  656. }
  657. // Avoid mount to be later re-defined
  658. l := len(s.Volumes) - 1
  659. s.Volumes[i] = s.Volumes[l]
  660. s.Volumes = s.Volumes[:l]
  661. }
  662. }
  663. }
  664. }
  665. mounts, err := fillBindMounts(p, s, mounts)
  666. if err != nil {
  667. return nil, err
  668. }
  669. values := make([]mount.Mount, 0, len(mounts))
  670. for _, v := range mounts {
  671. values = append(values, v)
  672. }
  673. return values, nil
  674. }
  675. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  676. for _, v := range s.Volumes {
  677. bindMount, err := buildMount(p, v)
  678. if err != nil {
  679. return nil, err
  680. }
  681. m[bindMount.Target] = bindMount
  682. }
  683. secrets, err := buildContainerSecretMounts(p, s)
  684. if err != nil {
  685. return nil, err
  686. }
  687. for _, s := range secrets {
  688. if _, found := m[s.Target]; found {
  689. continue
  690. }
  691. m[s.Target] = s
  692. }
  693. configs, err := buildContainerConfigMounts(p, s)
  694. if err != nil {
  695. return nil, err
  696. }
  697. for _, c := range configs {
  698. if _, found := m[c.Target]; found {
  699. continue
  700. }
  701. m[c.Target] = c
  702. }
  703. return m, nil
  704. }
  705. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  706. var mounts = map[string]mount.Mount{}
  707. configsBaseDir := "/"
  708. for _, config := range s.Configs {
  709. target := config.Target
  710. if config.Target == "" {
  711. target = configsBaseDir + config.Source
  712. } else if !isUnixAbs(config.Target) {
  713. target = configsBaseDir + config.Target
  714. }
  715. definedConfig := p.Configs[config.Source]
  716. if definedConfig.External.External {
  717. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  718. }
  719. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  720. Type: types.VolumeTypeBind,
  721. Source: definedConfig.File,
  722. Target: target,
  723. ReadOnly: true,
  724. })
  725. if err != nil {
  726. return nil, err
  727. }
  728. mounts[target] = bindMount
  729. }
  730. values := make([]mount.Mount, 0, len(mounts))
  731. for _, v := range mounts {
  732. values = append(values, v)
  733. }
  734. return values, nil
  735. }
  736. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  737. var mounts = map[string]mount.Mount{}
  738. secretsDir := "/run/secrets/"
  739. for _, secret := range s.Secrets {
  740. target := secret.Target
  741. if secret.Target == "" {
  742. target = secretsDir + secret.Source
  743. } else if !isUnixAbs(secret.Target) {
  744. target = secretsDir + secret.Target
  745. }
  746. definedSecret := p.Secrets[secret.Source]
  747. if definedSecret.External.External {
  748. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  749. }
  750. mount, err := buildMount(p, types.ServiceVolumeConfig{
  751. Type: types.VolumeTypeBind,
  752. Source: definedSecret.File,
  753. Target: target,
  754. ReadOnly: true,
  755. })
  756. if err != nil {
  757. return nil, err
  758. }
  759. mounts[target] = mount
  760. }
  761. values := make([]mount.Mount, 0, len(mounts))
  762. for _, v := range mounts {
  763. values = append(values, v)
  764. }
  765. return values, nil
  766. }
  767. func isUnixAbs(path string) bool {
  768. return strings.HasPrefix(path, "/")
  769. }
  770. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  771. source := volume.Source
  772. // on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
  773. // do not replace these with filepath.Abs(source) that will include a default drive.
  774. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) && !strings.HasPrefix(source, "/") {
  775. // volume source has already been prefixed with workdir if required, by compose-go project loader
  776. var err error
  777. source, err = filepath.Abs(source)
  778. if err != nil {
  779. return mount.Mount{}, err
  780. }
  781. }
  782. if volume.Type == types.VolumeTypeVolume {
  783. if volume.Source != "" {
  784. pVolume, ok := project.Volumes[volume.Source]
  785. if ok {
  786. source = pVolume.Name
  787. }
  788. }
  789. }
  790. bind, vol, tmpfs := buildMountOptions(volume)
  791. volume.Target = path.Clean(volume.Target)
  792. return mount.Mount{
  793. Type: mount.Type(volume.Type),
  794. Source: source,
  795. Target: volume.Target,
  796. ReadOnly: volume.ReadOnly,
  797. Consistency: mount.Consistency(volume.Consistency),
  798. BindOptions: bind,
  799. VolumeOptions: vol,
  800. TmpfsOptions: tmpfs,
  801. }, nil
  802. }
  803. func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
  804. switch volume.Type {
  805. case "bind":
  806. if volume.Volume != nil {
  807. logrus.Warnf("mount of type `bind` should not define `volume` option")
  808. }
  809. if volume.Tmpfs != nil {
  810. logrus.Warnf("mount of type `tmpfs` should not define `tmpfs` option")
  811. }
  812. return buildBindOption(volume.Bind), nil, nil
  813. case "volume":
  814. if volume.Bind != nil {
  815. logrus.Warnf("mount of type `volume` should not define `bind` option")
  816. }
  817. if volume.Tmpfs != nil {
  818. logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
  819. }
  820. return nil, buildVolumeOptions(volume.Volume), nil
  821. case "tmpfs":
  822. if volume.Bind != nil {
  823. logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
  824. }
  825. if volume.Tmpfs != nil {
  826. logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
  827. }
  828. return nil, nil, buildTmpfsOptions(volume.Tmpfs)
  829. }
  830. return nil, nil, nil
  831. }
  832. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  833. if bind == nil {
  834. return nil
  835. }
  836. return &mount.BindOptions{
  837. Propagation: mount.Propagation(bind.Propagation),
  838. // NonRecursive: false, FIXME missing from model ?
  839. }
  840. }
  841. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  842. if vol == nil {
  843. return nil
  844. }
  845. return &mount.VolumeOptions{
  846. NoCopy: vol.NoCopy,
  847. // Labels: , // FIXME missing from model ?
  848. // DriverConfig: , // FIXME missing from model ?
  849. }
  850. }
  851. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  852. if tmpfs == nil {
  853. return nil
  854. }
  855. return &mount.TmpfsOptions{
  856. SizeBytes: tmpfs.Size,
  857. // Mode: , // FIXME missing from model ?
  858. }
  859. }
  860. func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
  861. aliases := []string{s.Name}
  862. if c != nil {
  863. aliases = append(aliases, c.Aliases...)
  864. }
  865. return aliases
  866. }
  867. func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
  868. if len(s.Networks) > 0 {
  869. return s.Networks
  870. }
  871. if s.NetworkMode != "" {
  872. return nil
  873. }
  874. return map[string]*types.ServiceNetworkConfig{"default": nil}
  875. }
  876. func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
  877. _, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  878. if err != nil {
  879. if errdefs.IsNotFound(err) {
  880. if n.External.External {
  881. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  882. }
  883. var ipam *network.IPAM
  884. if n.Ipam.Config != nil {
  885. var config []network.IPAMConfig
  886. for _, pool := range n.Ipam.Config {
  887. config = append(config, network.IPAMConfig{
  888. Subnet: pool.Subnet,
  889. IPRange: pool.IPRange,
  890. Gateway: pool.Gateway,
  891. AuxAddress: pool.AuxiliaryAddresses,
  892. })
  893. }
  894. ipam = &network.IPAM{
  895. Driver: n.Ipam.Driver,
  896. Config: config,
  897. }
  898. }
  899. createOpts := moby.NetworkCreate{
  900. // TODO NameSpace Labels
  901. Labels: n.Labels,
  902. Driver: n.Driver,
  903. Options: n.DriverOpts,
  904. Internal: n.Internal,
  905. Attachable: n.Attachable,
  906. IPAM: ipam,
  907. }
  908. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  909. createOpts.IPAM = &network.IPAM{}
  910. }
  911. if n.Ipam.Driver != "" {
  912. createOpts.IPAM.Driver = n.Ipam.Driver
  913. }
  914. for _, ipamConfig := range n.Ipam.Config {
  915. config := network.IPAMConfig{
  916. Subnet: ipamConfig.Subnet,
  917. }
  918. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  919. }
  920. networkEventName := fmt.Sprintf("Network %s", n.Name)
  921. w := progress.ContextWriter(ctx)
  922. w.Event(progress.CreatingEvent(networkEventName))
  923. if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
  924. w.Event(progress.ErrorEvent(networkEventName))
  925. return errors.Wrapf(err, "failed to create network %s", n.Name)
  926. }
  927. w.Event(progress.CreatedEvent(networkEventName))
  928. return nil
  929. }
  930. return err
  931. }
  932. return nil
  933. }
  934. func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error {
  935. w := progress.ContextWriter(ctx)
  936. eventName := fmt.Sprintf("Network %s", networkName)
  937. w.Event(progress.RemovingEvent(eventName))
  938. if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
  939. w.Event(progress.ErrorEvent(eventName))
  940. return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
  941. }
  942. w.Event(progress.RemovedEvent(eventName))
  943. return nil
  944. }
  945. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
  946. // TODO could identify volume by label vs name
  947. _, err := s.apiClient.VolumeInspect(ctx, volume.Name)
  948. if err != nil {
  949. if !errdefs.IsNotFound(err) {
  950. return err
  951. }
  952. eventName := fmt.Sprintf("Volume %q", volume.Name)
  953. w := progress.ContextWriter(ctx)
  954. w.Event(progress.CreatingEvent(eventName))
  955. _, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
  956. Labels: volume.Labels,
  957. Name: volume.Name,
  958. Driver: volume.Driver,
  959. DriverOpts: volume.DriverOpts,
  960. })
  961. if err != nil {
  962. w.Event(progress.ErrorEvent(eventName))
  963. return err
  964. }
  965. w.Event(progress.CreatedEvent(eventName))
  966. }
  967. return nil
  968. }