create.go 19 KB

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