create.go 27 KB

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