create.go 21 KB

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