create.go 24 KB

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