compose.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. // +build local
  2. /*
  3. Copyright 2020 Docker Compose CLI authors
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package local
  15. import (
  16. "context"
  17. "encoding/base64"
  18. "encoding/json"
  19. "fmt"
  20. "io"
  21. "path/filepath"
  22. "sort"
  23. "strconv"
  24. "strings"
  25. "github.com/compose-spec/compose-go/types"
  26. "github.com/docker/buildx/build"
  27. "github.com/docker/cli/cli/config"
  28. "github.com/docker/distribution/reference"
  29. moby "github.com/docker/docker/api/types"
  30. "github.com/docker/docker/api/types/container"
  31. "github.com/docker/docker/api/types/filters"
  32. "github.com/docker/docker/api/types/mount"
  33. "github.com/docker/docker/api/types/network"
  34. "github.com/docker/docker/api/types/strslice"
  35. mobyvolume "github.com/docker/docker/api/types/volume"
  36. "github.com/docker/docker/client"
  37. "github.com/docker/docker/errdefs"
  38. "github.com/docker/docker/pkg/jsonmessage"
  39. "github.com/docker/docker/pkg/stdcopy"
  40. "github.com/docker/docker/registry"
  41. "github.com/docker/go-connections/nat"
  42. "github.com/pkg/errors"
  43. "github.com/sanathkr/go-yaml"
  44. "golang.org/x/sync/errgroup"
  45. "github.com/docker/compose-cli/api/compose"
  46. "github.com/docker/compose-cli/formatter"
  47. "github.com/docker/compose-cli/progress"
  48. )
  49. type composeService struct {
  50. apiClient *client.Client
  51. }
  52. func (s *composeService) Build(ctx context.Context, project *types.Project) error {
  53. opts := map[string]build.Options{}
  54. for _, service := range project.Services {
  55. if service.Build != nil {
  56. opts[service.Name] = s.toBuildOptions(service, project.WorkingDir)
  57. }
  58. }
  59. return s.build(ctx, project, opts)
  60. }
  61. func (s *composeService) Push(ctx context.Context, project *types.Project) error {
  62. configFile, err := config.Load(config.Dir())
  63. if err != nil {
  64. return err
  65. }
  66. eg, ctx := errgroup.WithContext(ctx)
  67. info, err := s.apiClient.Info(ctx)
  68. if err != nil {
  69. return err
  70. }
  71. if info.IndexServerAddress == "" {
  72. info.IndexServerAddress = registry.IndexServer
  73. }
  74. for _, service := range project.Services {
  75. if service.Build == nil {
  76. continue
  77. }
  78. service := service
  79. eg.Go(func() error {
  80. w := progress.ContextWriter(ctx)
  81. ref, err := reference.ParseNormalizedNamed(service.Image)
  82. if err != nil {
  83. return err
  84. }
  85. repoInfo, err := registry.ParseRepositoryInfo(ref)
  86. if err != nil {
  87. return err
  88. }
  89. key := repoInfo.Index.Name
  90. if repoInfo.Index.Official {
  91. key = info.IndexServerAddress
  92. }
  93. authConfig, err := configFile.GetAuthConfig(key)
  94. if err != nil {
  95. return err
  96. }
  97. buf, err := json.Marshal(authConfig)
  98. if err != nil {
  99. return err
  100. }
  101. stream, err := s.apiClient.ImagePush(ctx, service.Image, moby.ImagePushOptions{
  102. RegistryAuth: base64.URLEncoding.EncodeToString(buf),
  103. })
  104. if err != nil {
  105. return err
  106. }
  107. dec := json.NewDecoder(stream)
  108. for {
  109. var jm jsonmessage.JSONMessage
  110. if err := dec.Decode(&jm); err != nil {
  111. if err == io.EOF {
  112. break
  113. }
  114. return err
  115. }
  116. toProgressEvent(service.Name, jm, w)
  117. }
  118. return nil
  119. })
  120. }
  121. return eg.Wait()
  122. }
  123. func toProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.Writer) {
  124. if jm.ID == "" {
  125. return
  126. }
  127. var (
  128. text string
  129. status = progress.Working
  130. )
  131. if jm.Status == "Pull complete" || jm.Status == "Already exists" {
  132. status = progress.Done
  133. }
  134. if jm.Error != nil {
  135. status = progress.Error
  136. text = jm.Error.Message
  137. }
  138. if jm.Progress != nil {
  139. text = jm.Progress.String()
  140. }
  141. w.Event(progress.Event{
  142. ID: fmt.Sprintf("%s: %s", prefix, jm.ID),
  143. Text: jm.Status,
  144. Status: status,
  145. StatusText: text,
  146. })
  147. }
  148. func (s *composeService) Up(ctx context.Context, project *types.Project, detach bool) error {
  149. err := s.ensureImagesExists(ctx, project)
  150. if err != nil {
  151. return err
  152. }
  153. for k, network := range project.Networks {
  154. if !network.External.External && network.Name != "" {
  155. network.Name = fmt.Sprintf("%s_%s", project.Name, k)
  156. project.Networks[k] = network
  157. }
  158. network.Labels = network.Labels.Add(networkLabel, k)
  159. network.Labels = network.Labels.Add(projectLabel, project.Name)
  160. network.Labels = network.Labels.Add(versionLabel, ComposeVersion)
  161. err := s.ensureNetwork(ctx, network)
  162. if err != nil {
  163. return err
  164. }
  165. }
  166. for k, volume := range project.Volumes {
  167. if !volume.External.External && volume.Name != "" {
  168. volume.Name = fmt.Sprintf("%s_%s", project.Name, k)
  169. project.Volumes[k] = volume
  170. }
  171. volume.Labels = volume.Labels.Add(volumeLabel, k)
  172. volume.Labels = volume.Labels.Add(projectLabel, project.Name)
  173. volume.Labels = volume.Labels.Add(versionLabel, ComposeVersion)
  174. err := s.ensureVolume(ctx, volume)
  175. if err != nil {
  176. return err
  177. }
  178. }
  179. err = inDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
  180. return s.ensureService(c, project, service)
  181. })
  182. return err
  183. }
  184. func getContainerName(c moby.Container) string {
  185. // Names return container canonical name /foo + link aliases /linked_by/foo
  186. for _, name := range c.Names {
  187. if strings.LastIndex(name, "/") == 0 {
  188. return name[1:]
  189. }
  190. }
  191. return c.Names[0][1:]
  192. }
  193. func (s *composeService) Down(ctx context.Context, projectName string) error {
  194. list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
  195. Filters: filters.NewArgs(
  196. projectFilter(projectName),
  197. ),
  198. })
  199. if err != nil {
  200. return err
  201. }
  202. eg, _ := errgroup.WithContext(ctx)
  203. w := progress.ContextWriter(ctx)
  204. for _, c := range list {
  205. container := c
  206. eg.Go(func() error {
  207. w.Event(progress.NewEvent(getContainerName(container), progress.Working, "Stopping"))
  208. err := s.apiClient.ContainerStop(ctx, container.ID, nil)
  209. if err != nil {
  210. w.Event(progress.ErrorMessageEvent(getContainerName(container), "Error while Stopping"))
  211. return err
  212. }
  213. w.Event(progress.RemovingEvent(getContainerName(container)))
  214. err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
  215. if err != nil {
  216. w.Event(progress.ErrorMessageEvent(getContainerName(container), "Error while Removing"))
  217. return err
  218. }
  219. w.Event(progress.RemovedEvent(getContainerName(container)))
  220. return nil
  221. })
  222. }
  223. err = eg.Wait()
  224. if err != nil {
  225. return err
  226. }
  227. networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
  228. Filters: filters.NewArgs(
  229. projectFilter(projectName),
  230. ),
  231. })
  232. if err != nil {
  233. return err
  234. }
  235. for _, network := range networks {
  236. networkID := network.ID
  237. networkName := network.Name
  238. eg.Go(func() error {
  239. return s.ensureNetworkDown(ctx, networkID, networkName)
  240. })
  241. }
  242. return eg.Wait()
  243. }
  244. func (s *composeService) Logs(ctx context.Context, projectName string, w io.Writer) error {
  245. list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
  246. Filters: filters.NewArgs(
  247. projectFilter(projectName),
  248. ),
  249. })
  250. if err != nil {
  251. return err
  252. }
  253. consumer := formatter.NewLogConsumer(w)
  254. eg, ctx := errgroup.WithContext(ctx)
  255. for _, c := range list {
  256. service := c.Labels[serviceLabel]
  257. container, err := s.apiClient.ContainerInspect(ctx, c.ID)
  258. if err != nil {
  259. return err
  260. }
  261. eg.Go(func() error {
  262. r, err := s.apiClient.ContainerLogs(ctx, container.ID, moby.ContainerLogsOptions{
  263. ShowStdout: true,
  264. ShowStderr: true,
  265. Follow: true,
  266. })
  267. defer r.Close() // nolint errcheck
  268. if err != nil {
  269. return err
  270. }
  271. w := consumer.GetWriter(service, container.ID)
  272. if container.Config.Tty {
  273. _, err = io.Copy(w, r)
  274. } else {
  275. _, err = stdcopy.StdCopy(w, w, r)
  276. }
  277. return err
  278. })
  279. }
  280. return eg.Wait()
  281. }
  282. func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
  283. list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
  284. Filters: filters.NewArgs(
  285. projectFilter(projectName),
  286. ),
  287. })
  288. if err != nil {
  289. return nil, err
  290. }
  291. return containersToServiceStatus(list)
  292. }
  293. func containersToServiceStatus(containers []moby.Container) ([]compose.ServiceStatus, error) {
  294. containersByLabel, keys, err := groupContainerByLabel(containers, serviceLabel)
  295. if err != nil {
  296. return nil, err
  297. }
  298. var services []compose.ServiceStatus
  299. for _, service := range keys {
  300. containers := containersByLabel[service]
  301. runnningContainers := []moby.Container{}
  302. for _, container := range containers {
  303. if container.State == "running" {
  304. runnningContainers = append(runnningContainers, container)
  305. }
  306. }
  307. services = append(services, compose.ServiceStatus{
  308. ID: service,
  309. Name: service,
  310. Desired: len(containers),
  311. Replicas: len(runnningContainers),
  312. })
  313. }
  314. return services, nil
  315. }
  316. func groupContainerByLabel(containers []moby.Container, labelName string) (map[string][]moby.Container, []string, error) {
  317. containersByLabel := map[string][]moby.Container{}
  318. keys := []string{}
  319. for _, c := range containers {
  320. label, ok := c.Labels[labelName]
  321. if !ok {
  322. return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
  323. }
  324. labelContainers, ok := containersByLabel[label]
  325. if !ok {
  326. labelContainers = []moby.Container{}
  327. keys = append(keys, label)
  328. }
  329. labelContainers = append(labelContainers, c)
  330. containersByLabel[label] = labelContainers
  331. }
  332. sort.Strings(keys)
  333. return containersByLabel, keys, nil
  334. }
  335. func (s *composeService) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
  336. list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
  337. Filters: filters.NewArgs(hasProjectLabelFilter()),
  338. })
  339. if err != nil {
  340. return nil, err
  341. }
  342. return containersToStacks(list)
  343. }
  344. func containersToStacks(containers []moby.Container) ([]compose.Stack, error) {
  345. containersByLabel, keys, err := groupContainerByLabel(containers, projectLabel)
  346. if err != nil {
  347. return nil, err
  348. }
  349. var projects []compose.Stack
  350. for _, project := range keys {
  351. projects = append(projects, compose.Stack{
  352. ID: project,
  353. Name: project,
  354. Status: combinedStatus(containerToState(containersByLabel[project])),
  355. })
  356. }
  357. return projects, nil
  358. }
  359. func containerToState(containers []moby.Container) []string {
  360. statuses := []string{}
  361. for _, c := range containers {
  362. statuses = append(statuses, c.State)
  363. }
  364. return statuses
  365. }
  366. func combinedStatus(statuses []string) string {
  367. nbByStatus := map[string]int{}
  368. keys := []string{}
  369. for _, status := range statuses {
  370. nb, ok := nbByStatus[status]
  371. if !ok {
  372. nb = 0
  373. keys = append(keys, status)
  374. }
  375. nbByStatus[status] = nb + 1
  376. }
  377. sort.Strings(keys)
  378. result := ""
  379. for _, status := range keys {
  380. nb := nbByStatus[status]
  381. if result != "" {
  382. result = result + ", "
  383. }
  384. result = result + fmt.Sprintf("%s(%d)", status, nb)
  385. }
  386. return result
  387. }
  388. func (s *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
  389. switch format {
  390. case "json":
  391. return json.MarshalIndent(project, "", " ")
  392. case "yaml":
  393. return yaml.Marshal(project)
  394. default:
  395. return nil, fmt.Errorf("unsupported format %q", format)
  396. }
  397. }
  398. func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
  399. hash, err := jsonHash(s)
  400. if err != nil {
  401. return nil, nil, nil, err
  402. }
  403. //TODO: change oneoffLabel value for containers started with `docker compose run`
  404. labels := map[string]string{
  405. projectLabel: p.Name,
  406. serviceLabel: s.Name,
  407. versionLabel: ComposeVersion,
  408. oneoffLabel: "False",
  409. configHashLabel: hash,
  410. workingDirLabel: p.WorkingDir,
  411. configFilesLabel: strings.Join(p.ComposeFiles, ","),
  412. containerNumberLabel: strconv.Itoa(number),
  413. }
  414. var (
  415. runCmd strslice.StrSlice
  416. entrypoint strslice.StrSlice
  417. )
  418. if len(s.Command) > 0 {
  419. runCmd = strslice.StrSlice(s.Command)
  420. }
  421. if len(s.Entrypoint) > 0 {
  422. entrypoint = strslice.StrSlice(s.Entrypoint)
  423. }
  424. image := s.Image
  425. if s.Image == "" {
  426. image = fmt.Sprintf("%s_%s", p.Name, s.Name)
  427. }
  428. var (
  429. tty = s.Tty
  430. stdinOpen = s.StdinOpen
  431. attachStdin = false
  432. )
  433. containerConfig := container.Config{
  434. Hostname: s.Hostname,
  435. Domainname: s.DomainName,
  436. User: s.User,
  437. ExposedPorts: buildContainerPorts(s),
  438. Tty: tty,
  439. OpenStdin: stdinOpen,
  440. StdinOnce: true,
  441. AttachStdin: attachStdin,
  442. AttachStderr: true,
  443. AttachStdout: true,
  444. Cmd: runCmd,
  445. Image: image,
  446. WorkingDir: s.WorkingDir,
  447. Entrypoint: entrypoint,
  448. NetworkDisabled: s.NetworkMode == "disabled",
  449. MacAddress: s.MacAddress,
  450. Labels: labels,
  451. StopSignal: s.StopSignal,
  452. Env: toMobyEnv(s.Environment),
  453. Healthcheck: toMobyHealthCheck(s.HealthCheck),
  454. // Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts
  455. StopTimeout: toSeconds(s.StopGracePeriod),
  456. }
  457. mountOptions := buildContainerMountOptions(p, s, inherit)
  458. bindings := buildContainerBindingOptions(s)
  459. networkMode := getNetworkMode(p, s)
  460. hostConfig := container.HostConfig{
  461. Mounts: mountOptions,
  462. CapAdd: strslice.StrSlice(s.CapAdd),
  463. CapDrop: strslice.StrSlice(s.CapDrop),
  464. NetworkMode: networkMode,
  465. Init: s.Init,
  466. ReadonlyRootfs: s.ReadOnly,
  467. // ShmSize: , TODO
  468. Sysctls: s.Sysctls,
  469. PortBindings: bindings,
  470. }
  471. networkConfig := buildDefaultNetworkConfig(s, networkMode)
  472. return &containerConfig, &hostConfig, networkConfig, nil
  473. }
  474. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  475. ports := nat.PortSet{}
  476. for _, p := range s.Ports {
  477. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  478. ports[p] = struct{}{}
  479. }
  480. return ports
  481. }
  482. func buildContainerBindingOptions(s types.ServiceConfig) nat.PortMap {
  483. bindings := nat.PortMap{}
  484. for _, port := range s.Ports {
  485. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  486. bind := []nat.PortBinding{}
  487. binding := nat.PortBinding{}
  488. if port.Published > 0 {
  489. binding.HostPort = fmt.Sprint(port.Published)
  490. }
  491. bind = append(bind, binding)
  492. bindings[p] = bind
  493. }
  494. return bindings
  495. }
  496. func buildContainerMountOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) []mount.Mount {
  497. mounts := []mount.Mount{}
  498. var inherited []string
  499. if inherit != nil {
  500. for _, m := range inherit.Mounts {
  501. if m.Type == "tmpfs" {
  502. continue
  503. }
  504. src := m.Source
  505. if m.Type == "volume" {
  506. src = m.Name
  507. }
  508. mounts = append(mounts, mount.Mount{
  509. Type: m.Type,
  510. Source: src,
  511. Target: m.Destination,
  512. ReadOnly: !m.RW,
  513. })
  514. inherited = append(inherited, m.Destination)
  515. }
  516. }
  517. for _, v := range s.Volumes {
  518. if contains(inherited, v.Target) {
  519. continue
  520. }
  521. source := v.Source
  522. if v.Type == "bind" && !filepath.IsAbs(source) {
  523. // FIXME handle ~/
  524. source = filepath.Join(p.WorkingDir, source)
  525. }
  526. mounts = append(mounts, mount.Mount{
  527. Type: mount.Type(v.Type),
  528. Source: source,
  529. Target: v.Target,
  530. ReadOnly: v.ReadOnly,
  531. Consistency: mount.Consistency(v.Consistency),
  532. BindOptions: buildBindOption(v.Bind),
  533. VolumeOptions: buildVolumeOptions(v.Volume),
  534. TmpfsOptions: buildTmpfsOptions(v.Tmpfs),
  535. })
  536. }
  537. return mounts
  538. }
  539. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  540. if bind == nil {
  541. return nil
  542. }
  543. return &mount.BindOptions{
  544. Propagation: mount.Propagation(bind.Propagation),
  545. // NonRecursive: false, FIXME missing from model ?
  546. }
  547. }
  548. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  549. if vol == nil {
  550. return nil
  551. }
  552. return &mount.VolumeOptions{
  553. NoCopy: vol.NoCopy,
  554. // Labels: , // FIXME missing from model ?
  555. // DriverConfig: , // FIXME missing from model ?
  556. }
  557. }
  558. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  559. if tmpfs == nil {
  560. return nil
  561. }
  562. return &mount.TmpfsOptions{
  563. SizeBytes: tmpfs.Size,
  564. // Mode: , // FIXME missing from model ?
  565. }
  566. }
  567. func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode) *network.NetworkingConfig {
  568. config := map[string]*network.EndpointSettings{}
  569. net := string(networkMode)
  570. config[net] = &network.EndpointSettings{
  571. Aliases: getAliases(s, s.Networks[net]),
  572. }
  573. return &network.NetworkingConfig{
  574. EndpointsConfig: config,
  575. }
  576. }
  577. func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
  578. aliases := []string{s.Name}
  579. if c != nil {
  580. aliases = append(aliases, c.Aliases...)
  581. }
  582. return aliases
  583. }
  584. func getNetworkMode(p *types.Project, service types.ServiceConfig) container.NetworkMode {
  585. mode := service.NetworkMode
  586. if mode == "" {
  587. if len(p.Networks) > 0 {
  588. for name := range getNetworksForService(service) {
  589. return container.NetworkMode(p.Networks[name].Name)
  590. }
  591. }
  592. return container.NetworkMode("none")
  593. }
  594. /// FIXME incomplete implementation
  595. if strings.HasPrefix(mode, "service:") {
  596. panic("Not yet implemented")
  597. }
  598. if strings.HasPrefix(mode, "container:") {
  599. panic("Not yet implemented")
  600. }
  601. return container.NetworkMode(mode)
  602. }
  603. func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
  604. if len(s.Networks) > 0 {
  605. return s.Networks
  606. }
  607. return map[string]*types.ServiceNetworkConfig{"default": nil}
  608. }
  609. func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
  610. _, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  611. if err != nil {
  612. if errdefs.IsNotFound(err) {
  613. createOpts := moby.NetworkCreate{
  614. // TODO NameSpace Labels
  615. Labels: n.Labels,
  616. Driver: n.Driver,
  617. Options: n.DriverOpts,
  618. Internal: n.Internal,
  619. Attachable: n.Attachable,
  620. }
  621. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  622. createOpts.IPAM = &network.IPAM{}
  623. }
  624. if n.Ipam.Driver != "" {
  625. createOpts.IPAM.Driver = n.Ipam.Driver
  626. }
  627. for _, ipamConfig := range n.Ipam.Config {
  628. config := network.IPAMConfig{
  629. Subnet: ipamConfig.Subnet,
  630. }
  631. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  632. }
  633. networkEventName := fmt.Sprintf("Network %q", n.Name)
  634. w := progress.ContextWriter(ctx)
  635. w.Event(progress.CreatingEvent(networkEventName))
  636. if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
  637. w.Event(progress.ErrorEvent(networkEventName))
  638. return errors.Wrapf(err, "failed to create network %s", n.Name)
  639. }
  640. w.Event(progress.CreatedEvent(networkEventName))
  641. return nil
  642. }
  643. return err
  644. }
  645. return nil
  646. }
  647. func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
  648. w := progress.ContextWriter(ctx)
  649. eventName := fmt.Sprintf("Network %q", networkName)
  650. w.Event(progress.RemovingEvent(eventName))
  651. if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
  652. w.Event(progress.ErrorEvent(eventName))
  653. return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
  654. }
  655. w.Event(progress.RemovedEvent(eventName))
  656. return nil
  657. }
  658. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
  659. // TODO could identify volume by label vs name
  660. _, err := s.apiClient.VolumeInspect(ctx, volume.Name)
  661. if err != nil {
  662. if errdefs.IsNotFound(err) {
  663. eventName := fmt.Sprintf("Volume %q", volume.Name)
  664. w := progress.ContextWriter(ctx)
  665. w.Event(progress.CreatingEvent(eventName))
  666. // TODO we miss support for driver_opts and labels
  667. _, err := s.apiClient.VolumeCreate(ctx, mobyvolume.VolumeCreateBody{
  668. Labels: volume.Labels,
  669. Name: volume.Name,
  670. Driver: volume.Driver,
  671. DriverOpts: volume.DriverOpts,
  672. })
  673. if err != nil {
  674. w.Event(progress.ErrorEvent(eventName))
  675. return err
  676. }
  677. w.Event(progress.CreatedEvent(eventName))
  678. }
  679. return err
  680. }
  681. return nil
  682. }