create.go 27 KB

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