create.go 21 KB

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