compose.go 22 KB

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