create.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  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. volumeMounts := map[string]struct{}{}
  519. binds := []string{}
  520. MOUNTS:
  521. for _, m := range mountOptions {
  522. volumeMounts[m.Target] = struct{}{}
  523. // `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
  524. if m.Type == mount.TypeBind {
  525. for _, v := range service.Volumes {
  526. if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
  527. mode := "rw"
  528. if m.ReadOnly {
  529. mode = "ro"
  530. }
  531. binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
  532. continue MOUNTS
  533. }
  534. }
  535. }
  536. mounts = append(mounts, m)
  537. }
  538. return volumeMounts, binds, mounts, nil
  539. }
  540. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  541. var mounts = map[string]mount.Mount{}
  542. if inherit != nil {
  543. for _, m := range inherit.Mounts {
  544. if m.Type == "tmpfs" {
  545. continue
  546. }
  547. src := m.Source
  548. if m.Type == "volume" {
  549. src = m.Name
  550. }
  551. mounts[m.Destination] = mount.Mount{
  552. Type: m.Type,
  553. Source: src,
  554. Target: m.Destination,
  555. ReadOnly: !m.RW,
  556. }
  557. }
  558. }
  559. if img.ContainerConfig != nil {
  560. for k := range img.ContainerConfig.Volumes {
  561. m, err := buildMount(p, types.ServiceVolumeConfig{
  562. Type: types.VolumeTypeVolume,
  563. Target: k,
  564. })
  565. if err != nil {
  566. return nil, err
  567. }
  568. mounts[k] = m
  569. }
  570. }
  571. mounts, err := fillBindMounts(p, s, mounts)
  572. if err != nil {
  573. return nil, err
  574. }
  575. values := make([]mount.Mount, 0, len(mounts))
  576. for _, v := range mounts {
  577. values = append(values, v)
  578. }
  579. return values, nil
  580. }
  581. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  582. for _, v := range s.Volumes {
  583. bindMount, err := buildMount(p, v)
  584. if err != nil {
  585. return nil, err
  586. }
  587. m[bindMount.Target] = bindMount
  588. }
  589. secrets, err := buildContainerSecretMounts(p, s)
  590. if err != nil {
  591. return nil, err
  592. }
  593. for _, s := range secrets {
  594. if _, found := m[s.Target]; found {
  595. continue
  596. }
  597. m[s.Target] = s
  598. }
  599. configs, err := buildContainerConfigMounts(p, s)
  600. if err != nil {
  601. return nil, err
  602. }
  603. for _, c := range configs {
  604. if _, found := m[c.Target]; found {
  605. continue
  606. }
  607. m[c.Target] = c
  608. }
  609. return m, nil
  610. }
  611. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  612. var mounts = map[string]mount.Mount{}
  613. configsBaseDir := "/"
  614. for _, config := range s.Configs {
  615. target := config.Target
  616. if config.Target == "" {
  617. target = configsBaseDir + config.Source
  618. } else if !isUnixAbs(config.Target) {
  619. target = configsBaseDir + config.Target
  620. }
  621. definedConfig := p.Configs[config.Source]
  622. if definedConfig.External.External {
  623. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  624. }
  625. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  626. Type: types.VolumeTypeBind,
  627. Source: definedConfig.File,
  628. Target: target,
  629. ReadOnly: true,
  630. })
  631. if err != nil {
  632. return nil, err
  633. }
  634. mounts[target] = bindMount
  635. }
  636. values := make([]mount.Mount, 0, len(mounts))
  637. for _, v := range mounts {
  638. values = append(values, v)
  639. }
  640. return values, nil
  641. }
  642. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  643. var mounts = map[string]mount.Mount{}
  644. secretsDir := "/run/secrets/"
  645. for _, secret := range s.Secrets {
  646. target := secret.Target
  647. if secret.Target == "" {
  648. target = secretsDir + secret.Source
  649. } else if !isUnixAbs(secret.Target) {
  650. target = secretsDir + secret.Target
  651. }
  652. definedSecret := p.Secrets[secret.Source]
  653. if definedSecret.External.External {
  654. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  655. }
  656. mount, err := buildMount(p, types.ServiceVolumeConfig{
  657. Type: types.VolumeTypeBind,
  658. Source: definedSecret.File,
  659. Target: target,
  660. ReadOnly: true,
  661. })
  662. if err != nil {
  663. return nil, err
  664. }
  665. mounts[target] = mount
  666. }
  667. values := make([]mount.Mount, 0, len(mounts))
  668. for _, v := range mounts {
  669. values = append(values, v)
  670. }
  671. return values, nil
  672. }
  673. func isUnixAbs(path string) bool {
  674. return strings.HasPrefix(path, "/")
  675. }
  676. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  677. source := volume.Source
  678. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) {
  679. // volume source has already been prefixed with workdir if required, by compose-go project loader
  680. var err error
  681. source, err = filepath.Abs(source)
  682. if err != nil {
  683. return mount.Mount{}, err
  684. }
  685. }
  686. if volume.Type == types.VolumeTypeVolume {
  687. if volume.Source != "" {
  688. pVolume, ok := project.Volumes[volume.Source]
  689. if ok {
  690. source = pVolume.Name
  691. }
  692. }
  693. }
  694. bind, vol, tmpfs := buildMountOptions(volume)
  695. return mount.Mount{
  696. Type: mount.Type(volume.Type),
  697. Source: source,
  698. Target: volume.Target,
  699. ReadOnly: volume.ReadOnly,
  700. Consistency: mount.Consistency(volume.Consistency),
  701. BindOptions: bind,
  702. VolumeOptions: vol,
  703. TmpfsOptions: tmpfs,
  704. }, nil
  705. }
  706. func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
  707. switch volume.Type {
  708. case "bind":
  709. if volume.Volume != nil {
  710. logrus.Warnf("mount of type `bind` should not define `volume` option")
  711. }
  712. if volume.Tmpfs != nil {
  713. logrus.Warnf("mount of type `tmpfs` should not define `tmpfs` option")
  714. }
  715. return buildBindOption(volume.Bind), nil, nil
  716. case "volume":
  717. if volume.Bind != nil {
  718. logrus.Warnf("mount of type `volume` should not define `bind` option")
  719. }
  720. if volume.Tmpfs != nil {
  721. logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
  722. }
  723. return nil, buildVolumeOptions(volume.Volume), nil
  724. case "tmpfs":
  725. if volume.Bind != nil {
  726. logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
  727. }
  728. if volume.Tmpfs != nil {
  729. logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
  730. }
  731. return nil, nil, buildTmpfsOptions(volume.Tmpfs)
  732. }
  733. return nil, nil, nil
  734. }
  735. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  736. if bind == nil {
  737. return nil
  738. }
  739. return &mount.BindOptions{
  740. Propagation: mount.Propagation(bind.Propagation),
  741. // NonRecursive: false, FIXME missing from model ?
  742. }
  743. }
  744. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  745. if vol == nil {
  746. return nil
  747. }
  748. return &mount.VolumeOptions{
  749. NoCopy: vol.NoCopy,
  750. // Labels: , // FIXME missing from model ?
  751. // DriverConfig: , // FIXME missing from model ?
  752. }
  753. }
  754. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  755. if tmpfs == nil {
  756. return nil
  757. }
  758. return &mount.TmpfsOptions{
  759. SizeBytes: tmpfs.Size,
  760. // Mode: , // FIXME missing from model ?
  761. }
  762. }
  763. func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode, containerName string) *network.NetworkingConfig {
  764. if len(s.Networks) == 0 {
  765. return nil
  766. }
  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 %s", 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 %s", 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. }