create.go 24 KB

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