create.go 27 KB

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