create.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  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/pkg/errors"
  31. "github.com/sirupsen/logrus"
  32. "github.com/docker/compose-cli/api/compose"
  33. "github.com/docker/compose-cli/api/progress"
  34. convert "github.com/docker/compose-cli/local/moby"
  35. )
  36. func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error {
  37. if len(opts.Services) == 0 {
  38. opts.Services = project.ServiceNames()
  39. }
  40. err := s.ensureImagesExists(ctx, project)
  41. if err != nil {
  42. return err
  43. }
  44. prepareNetworks(project)
  45. err = prepareVolumes(project)
  46. if err != nil {
  47. return err
  48. }
  49. if err := s.ensureNetworks(ctx, project.Networks); err != nil {
  50. return err
  51. }
  52. if err := s.ensureProjectVolumes(ctx, project); err != nil {
  53. return err
  54. }
  55. var observedState Containers
  56. observedState, err = s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
  57. Filters: filters.NewArgs(projectFilter(project.Name)),
  58. All: true,
  59. })
  60. if err != nil {
  61. return err
  62. }
  63. containerState := NewContainersState(observedState)
  64. ctx = context.WithValue(ctx, ContainersKey{}, containerState)
  65. allServices := project.AllServices()
  66. allServiceNames := []string{}
  67. for _, service := range allServices {
  68. allServiceNames = append(allServiceNames, service.Name)
  69. }
  70. orphans := observedState.filter(isNotService(allServiceNames...))
  71. if len(orphans) > 0 {
  72. if opts.RemoveOrphans {
  73. w := progress.ContextWriter(ctx)
  74. err := s.removeContainers(ctx, w, orphans, nil)
  75. if err != nil {
  76. return err
  77. }
  78. } else {
  79. logrus.Warnf("Found orphan containers (%s) for this project. If "+
  80. "you removed or renamed this service in your compose "+
  81. "file, you can run this command with the "+
  82. "--remove-orphans flag to clean it up.", orphans.names())
  83. }
  84. }
  85. prepareNetworkMode(project)
  86. return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
  87. if contains(opts.Services, service.Name) {
  88. return s.ensureService(c, project, service, opts.Recreate, opts.Inherit, opts.Timeout)
  89. }
  90. return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit, opts.Timeout)
  91. })
  92. }
  93. func prepareVolumes(p *types.Project) error {
  94. for i := range p.Services {
  95. volumesFrom, dependServices, err := getVolumesFrom(p, p.Services[i].VolumesFrom)
  96. if err != nil {
  97. return err
  98. }
  99. p.Services[i].VolumesFrom = volumesFrom
  100. if len(dependServices) > 0 {
  101. if p.Services[i].DependsOn == nil {
  102. p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
  103. }
  104. for _, service := range p.Services {
  105. if contains(dependServices, service.Name) {
  106. p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
  107. Condition: types.ServiceConditionStarted,
  108. }
  109. }
  110. }
  111. }
  112. }
  113. return nil
  114. }
  115. func prepareNetworks(project *types.Project) {
  116. for k, network := range project.Networks {
  117. network.Labels = network.Labels.Add(networkLabel, k)
  118. network.Labels = network.Labels.Add(projectLabel, project.Name)
  119. network.Labels = network.Labels.Add(versionLabel, ComposeVersion)
  120. project.Networks[k] = network
  121. }
  122. }
  123. func prepareNetworkMode(p *types.Project) {
  124. outLoop:
  125. for i := range p.Services {
  126. dependency := getDependentServiceByNetwork(p.Services[i].NetworkMode)
  127. if dependency == "" {
  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 == dependency {
  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 := jsonHash(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: true,
  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 := getNetworkMode(ctx, p, service)
  237. if err != nil {
  238. return nil, nil, nil, err
  239. }
  240. hostConfig := container.HostConfig{
  241. AutoRemove: autoRemove,
  242. Binds: binds,
  243. Mounts: mounts,
  244. CapAdd: strslice.StrSlice(service.CapAdd),
  245. CapDrop: strslice.StrSlice(service.CapDrop),
  246. NetworkMode: networkMode,
  247. Init: service.Init,
  248. ReadonlyRootfs: service.ReadOnly,
  249. RestartPolicy: getRestartPolicy(service),
  250. // ShmSize: , TODO
  251. Sysctls: service.Sysctls,
  252. PortBindings: portBindings,
  253. Resources: resources,
  254. VolumeDriver: service.VolumeDriver,
  255. VolumesFrom: service.VolumesFrom,
  256. }
  257. networkConfig := buildDefaultNetworkConfig(service, networkMode, getContainerName(p.Name, service, number))
  258. return &containerConfig, &hostConfig, networkConfig, nil
  259. }
  260. func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
  261. var restart container.RestartPolicy
  262. if service.Restart != "" {
  263. split := strings.Split(service.Restart, ":")
  264. var attempts int
  265. if len(split) > 1 {
  266. attempts, _ = strconv.Atoi(split[1])
  267. }
  268. restart = container.RestartPolicy{
  269. Name: split[0],
  270. MaximumRetryCount: attempts,
  271. }
  272. }
  273. if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
  274. policy := *service.Deploy.RestartPolicy
  275. var attempts int
  276. if policy.MaxAttempts != nil {
  277. attempts = int(*policy.MaxAttempts)
  278. }
  279. restart = container.RestartPolicy{
  280. Name: policy.Condition,
  281. MaximumRetryCount: attempts,
  282. }
  283. }
  284. return restart
  285. }
  286. func getDeployResources(s types.ServiceConfig) container.Resources {
  287. resources := container.Resources{}
  288. if s.Deploy == nil {
  289. return resources
  290. }
  291. reservations := s.Deploy.Resources.Reservations
  292. if reservations == nil || len(reservations.Devices) == 0 {
  293. return resources
  294. }
  295. for _, device := range reservations.Devices {
  296. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  297. Capabilities: [][]string{device.Capabilities},
  298. Count: int(device.Count),
  299. DeviceIDs: device.IDs,
  300. Driver: device.Driver,
  301. })
  302. }
  303. return resources
  304. }
  305. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  306. ports := nat.PortSet{}
  307. for _, p := range s.Ports {
  308. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  309. ports[p] = struct{}{}
  310. }
  311. return ports
  312. }
  313. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  314. bindings := nat.PortMap{}
  315. for _, port := range s.Ports {
  316. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  317. bind := []nat.PortBinding{}
  318. binding := nat.PortBinding{}
  319. if port.Published > 0 {
  320. binding.HostPort = fmt.Sprint(port.Published)
  321. }
  322. bind = append(bind, binding)
  323. bindings[p] = bind
  324. }
  325. return bindings
  326. }
  327. func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
  328. var volumes = []string{}
  329. var services = []string{}
  330. // parse volumes_from
  331. if len(volumesFrom) == 0 {
  332. return volumes, services, nil
  333. }
  334. for _, vol := range volumesFrom {
  335. spec := strings.Split(vol, ":")
  336. if len(spec) == 0 {
  337. continue
  338. }
  339. if spec[0] == "container" {
  340. volumes = append(volumes, strings.Join(spec[1:], ":"))
  341. continue
  342. }
  343. serviceName := spec[0]
  344. services = append(services, serviceName)
  345. service, err := project.GetService(serviceName)
  346. if err != nil {
  347. return nil, nil, err
  348. }
  349. firstContainer := getContainerName(project.Name, service, 1)
  350. v := fmt.Sprintf("%s:%s", firstContainer, strings.Join(spec[1:], ":"))
  351. volumes = append(volumes, v)
  352. }
  353. return volumes, services, nil
  354. }
  355. func getDependentServiceByNetwork(networkMode string) string {
  356. baseService := ""
  357. if strings.HasPrefix(networkMode, types.NetworkModeServicePrefix) {
  358. return networkMode[len(types.NetworkModeServicePrefix):]
  359. }
  360. return baseService
  361. }
  362. func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
  363. inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
  364. var mounts = []mount.Mount{}
  365. image := getImageName(service, p.Name)
  366. imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
  367. if err != nil {
  368. return nil, nil, nil, err
  369. }
  370. mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
  371. if err != nil {
  372. return nil, nil, nil, err
  373. }
  374. // filter binds and volumes mount targets
  375. volumeMounts := map[string]struct{}{}
  376. binds := []string{}
  377. for _, m := range mountOptions {
  378. if m.Type == mount.TypeVolume {
  379. volumeMounts[m.Target] = struct{}{}
  380. if m.Source != "" {
  381. binds = append(binds, fmt.Sprintf("%s:%s:rw", m.Source, m.Target))
  382. }
  383. }
  384. }
  385. for _, m := range mountOptions {
  386. if m.Type == mount.TypeBind || m.Type == mount.TypeTmpfs {
  387. mounts = append(mounts, m)
  388. }
  389. }
  390. return volumeMounts, binds, mounts, nil
  391. }
  392. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  393. var mounts = map[string]mount.Mount{}
  394. if inherit != nil {
  395. for _, m := range inherit.Mounts {
  396. if m.Type == "tmpfs" {
  397. continue
  398. }
  399. src := m.Source
  400. if m.Type == "volume" {
  401. src = m.Name
  402. }
  403. mounts[m.Destination] = mount.Mount{
  404. Type: m.Type,
  405. Source: src,
  406. Target: m.Destination,
  407. ReadOnly: !m.RW,
  408. }
  409. }
  410. }
  411. if img.ContainerConfig != nil {
  412. for k := range img.ContainerConfig.Volumes {
  413. m, err := buildMount(p, types.ServiceVolumeConfig{
  414. Type: types.VolumeTypeVolume,
  415. Target: k,
  416. })
  417. if err != nil {
  418. return nil, err
  419. }
  420. mounts[k] = m
  421. }
  422. }
  423. mounts, err := fillBindMounts(p, s, mounts)
  424. if err != nil {
  425. return nil, err
  426. }
  427. values := make([]mount.Mount, 0, len(mounts))
  428. for _, v := range mounts {
  429. values = append(values, v)
  430. }
  431. return values, nil
  432. }
  433. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  434. for _, v := range s.Volumes {
  435. bindMount, err := buildMount(p, v)
  436. if err != nil {
  437. return nil, err
  438. }
  439. m[bindMount.Target] = bindMount
  440. }
  441. secrets, err := buildContainerSecretMounts(p, s)
  442. if err != nil {
  443. return nil, err
  444. }
  445. for _, s := range secrets {
  446. if _, found := m[s.Target]; found {
  447. continue
  448. }
  449. m[s.Target] = s
  450. }
  451. configs, err := buildContainerConfigMounts(p, s)
  452. if err != nil {
  453. return nil, err
  454. }
  455. for _, c := range configs {
  456. if _, found := m[c.Target]; found {
  457. continue
  458. }
  459. m[c.Target] = c
  460. }
  461. return m, nil
  462. }
  463. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  464. var mounts = map[string]mount.Mount{}
  465. configsBaseDir := "/"
  466. for _, config := range s.Configs {
  467. target := config.Target
  468. if config.Target == "" {
  469. target = filepath.Join(configsBaseDir, config.Source)
  470. } else if !filepath.IsAbs(config.Target) {
  471. target = filepath.Join(configsBaseDir, config.Target)
  472. }
  473. definedConfig := p.Configs[config.Source]
  474. if definedConfig.External.External {
  475. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  476. }
  477. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  478. Type: types.VolumeTypeBind,
  479. Source: definedConfig.File,
  480. Target: target,
  481. ReadOnly: true,
  482. })
  483. if err != nil {
  484. return nil, err
  485. }
  486. mounts[target] = bindMount
  487. }
  488. values := make([]mount.Mount, 0, len(mounts))
  489. for _, v := range mounts {
  490. values = append(values, v)
  491. }
  492. return values, nil
  493. }
  494. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  495. var mounts = map[string]mount.Mount{}
  496. secretsDir := "/run/secrets"
  497. for _, secret := range s.Secrets {
  498. target := secret.Target
  499. if secret.Target == "" {
  500. target = filepath.Join(secretsDir, secret.Source)
  501. } else if !filepath.IsAbs(secret.Target) {
  502. target = filepath.Join(secretsDir, secret.Target)
  503. }
  504. definedSecret := p.Secrets[secret.Source]
  505. if definedSecret.External.External {
  506. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  507. }
  508. mount, err := buildMount(p, types.ServiceVolumeConfig{
  509. Type: types.VolumeTypeBind,
  510. Source: definedSecret.File,
  511. Target: target,
  512. ReadOnly: true,
  513. })
  514. if err != nil {
  515. return nil, err
  516. }
  517. mounts[target] = mount
  518. }
  519. values := make([]mount.Mount, 0, len(mounts))
  520. for _, v := range mounts {
  521. values = append(values, v)
  522. }
  523. return values, nil
  524. }
  525. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  526. source := volume.Source
  527. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) {
  528. // volume source has already been prefixed with workdir if required, by compose-go project loader
  529. var err error
  530. source, err = filepath.Abs(source)
  531. if err != nil {
  532. return mount.Mount{}, err
  533. }
  534. }
  535. if volume.Type == types.VolumeTypeVolume {
  536. if volume.Source != "" {
  537. pVolume, ok := project.Volumes[volume.Source]
  538. if ok {
  539. source = pVolume.Name
  540. }
  541. }
  542. }
  543. return mount.Mount{
  544. Type: mount.Type(volume.Type),
  545. Source: source,
  546. Target: volume.Target,
  547. ReadOnly: volume.ReadOnly,
  548. Consistency: mount.Consistency(volume.Consistency),
  549. BindOptions: buildBindOption(volume.Bind),
  550. VolumeOptions: buildVolumeOptions(volume.Volume),
  551. TmpfsOptions: buildTmpfsOptions(volume.Tmpfs),
  552. }, nil
  553. }
  554. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  555. if bind == nil {
  556. return nil
  557. }
  558. return &mount.BindOptions{
  559. Propagation: mount.Propagation(bind.Propagation),
  560. // NonRecursive: false, FIXME missing from model ?
  561. }
  562. }
  563. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  564. if vol == nil {
  565. return nil
  566. }
  567. return &mount.VolumeOptions{
  568. NoCopy: vol.NoCopy,
  569. // Labels: , // FIXME missing from model ?
  570. // DriverConfig: , // FIXME missing from model ?
  571. }
  572. }
  573. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  574. if tmpfs == nil {
  575. return nil
  576. }
  577. return &mount.TmpfsOptions{
  578. SizeBytes: tmpfs.Size,
  579. // Mode: , // FIXME missing from model ?
  580. }
  581. }
  582. func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode, containerName string) *network.NetworkingConfig {
  583. config := map[string]*network.EndpointSettings{}
  584. net := string(networkMode)
  585. config[net] = &network.EndpointSettings{
  586. Aliases: append(getAliases(s, s.Networks[net]), containerName),
  587. }
  588. return &network.NetworkingConfig{
  589. EndpointsConfig: config,
  590. }
  591. }
  592. func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
  593. aliases := []string{s.Name}
  594. if c != nil {
  595. aliases = append(aliases, c.Aliases...)
  596. }
  597. return aliases
  598. }
  599. func getNetworkMode(ctx context.Context, p *types.Project, service types.ServiceConfig) (container.NetworkMode, error) {
  600. cState, err := GetContextContainerState(ctx)
  601. if err != nil {
  602. return container.NetworkMode("none"), nil
  603. }
  604. observedState := cState.GetContainers()
  605. mode := service.NetworkMode
  606. if mode == "" {
  607. if len(p.Networks) > 0 {
  608. for name := range getNetworksForService(service) {
  609. return container.NetworkMode(p.Networks[name].Name), nil
  610. }
  611. }
  612. return container.NetworkMode("none"), nil
  613. }
  614. depServiceNetworkMode := getDependentServiceByNetwork(service.NetworkMode)
  615. if depServiceNetworkMode != "" {
  616. depServiceContainers := observedState.filter(isService(depServiceNetworkMode))
  617. if len(depServiceContainers) > 0 {
  618. return container.NetworkMode(types.NetworkModeContainerPrefix + depServiceContainers[0].ID), nil
  619. }
  620. return container.NetworkMode("none"),
  621. fmt.Errorf(`no containers started for network_mode %q in service %q -> %v`,
  622. mode, service.Name, observedState)
  623. }
  624. return container.NetworkMode(mode), nil
  625. }
  626. func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
  627. if len(s.Networks) > 0 {
  628. return s.Networks
  629. }
  630. if s.NetworkMode != "" {
  631. return nil
  632. }
  633. return map[string]*types.ServiceNetworkConfig{"default": nil}
  634. }
  635. func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
  636. _, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  637. if err != nil {
  638. if errdefs.IsNotFound(err) {
  639. if n.External.External {
  640. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  641. }
  642. createOpts := moby.NetworkCreate{
  643. // TODO NameSpace Labels
  644. Labels: n.Labels,
  645. Driver: n.Driver,
  646. Options: n.DriverOpts,
  647. Internal: n.Internal,
  648. Attachable: n.Attachable,
  649. }
  650. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  651. createOpts.IPAM = &network.IPAM{}
  652. }
  653. if n.Ipam.Driver != "" {
  654. createOpts.IPAM.Driver = n.Ipam.Driver
  655. }
  656. for _, ipamConfig := range n.Ipam.Config {
  657. config := network.IPAMConfig{
  658. Subnet: ipamConfig.Subnet,
  659. }
  660. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  661. }
  662. networkEventName := fmt.Sprintf("Network %q", n.Name)
  663. w := progress.ContextWriter(ctx)
  664. w.Event(progress.CreatingEvent(networkEventName))
  665. if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
  666. w.Event(progress.ErrorEvent(networkEventName))
  667. return errors.Wrapf(err, "failed to create network %s", n.Name)
  668. }
  669. w.Event(progress.CreatedEvent(networkEventName))
  670. return nil
  671. }
  672. return err
  673. }
  674. return nil
  675. }
  676. func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
  677. w := progress.ContextWriter(ctx)
  678. eventName := fmt.Sprintf("Network %q", networkName)
  679. w.Event(progress.RemovingEvent(eventName))
  680. if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
  681. w.Event(progress.ErrorEvent(eventName))
  682. return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
  683. }
  684. w.Event(progress.RemovedEvent(eventName))
  685. return nil
  686. }
  687. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
  688. // TODO could identify volume by label vs name
  689. _, err := s.apiClient.VolumeInspect(ctx, volume.Name)
  690. if err != nil {
  691. if !errdefs.IsNotFound(err) {
  692. return err
  693. }
  694. eventName := fmt.Sprintf("Volume %q", volume.Name)
  695. w := progress.ContextWriter(ctx)
  696. w.Event(progress.CreatingEvent(eventName))
  697. _, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
  698. Labels: volume.Labels,
  699. Name: volume.Name,
  700. Driver: volume.Driver,
  701. DriverOpts: volume.DriverOpts,
  702. })
  703. if err != nil {
  704. w.Event(progress.ErrorEvent(eventName))
  705. return err
  706. }
  707. w.Event(progress.CreatedEvent(eventName))
  708. }
  709. return nil
  710. }