create.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  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, opts.QuietPull)
  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, opts.Timeout)
  89. }
  90. return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit, opts.Timeout)
  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. RestartPolicy: getRestartPolicy(service),
  250. // ShmSize: , TODO
  251. Sysctls: service.Sysctls,
  252. PortBindings: portBindings,
  253. Resources: resources,
  254. VolumeDriver: service.VolumeDriver,
  255. VolumesFrom: service.VolumesFrom,
  256. DNS: service.DNS,
  257. DNSSearch: service.DNSSearch,
  258. DNSOptions: service.DNSOpts,
  259. ExtraHosts: service.ExtraHosts,
  260. SecurityOpt: service.SecurityOpt,
  261. UsernsMode: container.UsernsMode(service.UserNSMode),
  262. Privileged: service.Privileged,
  263. Isolation: container.Isolation(service.Isolation),
  264. }
  265. networkConfig := buildDefaultNetworkConfig(service, networkMode, getContainerName(p.Name, service, number))
  266. return &containerConfig, &hostConfig, networkConfig, nil
  267. }
  268. func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
  269. var restart container.RestartPolicy
  270. if service.Restart != "" {
  271. split := strings.Split(service.Restart, ":")
  272. var attempts int
  273. if len(split) > 1 {
  274. attempts, _ = strconv.Atoi(split[1])
  275. }
  276. restart = container.RestartPolicy{
  277. Name: split[0],
  278. MaximumRetryCount: attempts,
  279. }
  280. }
  281. if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
  282. policy := *service.Deploy.RestartPolicy
  283. var attempts int
  284. if policy.MaxAttempts != nil {
  285. attempts = int(*policy.MaxAttempts)
  286. }
  287. restart = container.RestartPolicy{
  288. Name: policy.Condition,
  289. MaximumRetryCount: attempts,
  290. }
  291. }
  292. return restart
  293. }
  294. func getDeployResources(s types.ServiceConfig) container.Resources {
  295. resources := container.Resources{}
  296. if s.Deploy == nil {
  297. return resources
  298. }
  299. reservations := s.Deploy.Resources.Reservations
  300. if reservations == nil || len(reservations.Devices) == 0 {
  301. return resources
  302. }
  303. for _, device := range reservations.Devices {
  304. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  305. Capabilities: [][]string{device.Capabilities},
  306. Count: int(device.Count),
  307. DeviceIDs: device.IDs,
  308. Driver: device.Driver,
  309. })
  310. }
  311. return resources
  312. }
  313. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  314. ports := nat.PortSet{}
  315. for _, p := range s.Ports {
  316. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  317. ports[p] = struct{}{}
  318. }
  319. return ports
  320. }
  321. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  322. bindings := nat.PortMap{}
  323. for _, port := range s.Ports {
  324. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  325. bind := []nat.PortBinding{}
  326. binding := nat.PortBinding{}
  327. if port.Published > 0 {
  328. binding.HostPort = fmt.Sprint(port.Published)
  329. }
  330. bind = append(bind, binding)
  331. bindings[p] = bind
  332. }
  333. return bindings
  334. }
  335. func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
  336. var volumes = []string{}
  337. var services = []string{}
  338. // parse volumes_from
  339. if len(volumesFrom) == 0 {
  340. return volumes, services, nil
  341. }
  342. for _, vol := range volumesFrom {
  343. spec := strings.Split(vol, ":")
  344. if len(spec) == 0 {
  345. continue
  346. }
  347. if spec[0] == "container" {
  348. volumes = append(volumes, strings.Join(spec[1:], ":"))
  349. continue
  350. }
  351. serviceName := spec[0]
  352. services = append(services, serviceName)
  353. service, err := project.GetService(serviceName)
  354. if err != nil {
  355. return nil, nil, err
  356. }
  357. firstContainer := getContainerName(project.Name, service, 1)
  358. v := fmt.Sprintf("%s:%s", firstContainer, strings.Join(spec[1:], ":"))
  359. volumes = append(volumes, v)
  360. }
  361. return volumes, services, nil
  362. }
  363. func getDependentServiceByNetwork(networkMode string) string {
  364. baseService := ""
  365. if strings.HasPrefix(networkMode, types.NetworkModeServicePrefix) {
  366. return networkMode[len(types.NetworkModeServicePrefix):]
  367. }
  368. return baseService
  369. }
  370. func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
  371. inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
  372. var mounts = []mount.Mount{}
  373. image := getImageName(service, p.Name)
  374. imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
  375. if err != nil {
  376. return nil, nil, nil, err
  377. }
  378. mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
  379. if err != nil {
  380. return nil, nil, nil, err
  381. }
  382. // filter binds and volumes mount targets
  383. volumeMounts := map[string]struct{}{}
  384. binds := []string{}
  385. for _, m := range mountOptions {
  386. if m.Type == mount.TypeVolume {
  387. volumeMounts[m.Target] = struct{}{}
  388. if m.Source != "" {
  389. binds = append(binds, fmt.Sprintf("%s:%s:rw", m.Source, m.Target))
  390. }
  391. }
  392. }
  393. for _, m := range mountOptions {
  394. if m.Type == mount.TypeBind || m.Type == mount.TypeTmpfs {
  395. mounts = append(mounts, m)
  396. }
  397. }
  398. return volumeMounts, binds, mounts, nil
  399. }
  400. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  401. var mounts = map[string]mount.Mount{}
  402. if inherit != nil {
  403. for _, m := range inherit.Mounts {
  404. if m.Type == "tmpfs" {
  405. continue
  406. }
  407. src := m.Source
  408. if m.Type == "volume" {
  409. src = m.Name
  410. }
  411. mounts[m.Destination] = mount.Mount{
  412. Type: m.Type,
  413. Source: src,
  414. Target: m.Destination,
  415. ReadOnly: !m.RW,
  416. }
  417. }
  418. }
  419. if img.ContainerConfig != nil {
  420. for k := range img.ContainerConfig.Volumes {
  421. m, err := buildMount(p, types.ServiceVolumeConfig{
  422. Type: types.VolumeTypeVolume,
  423. Target: k,
  424. })
  425. if err != nil {
  426. return nil, err
  427. }
  428. mounts[k] = m
  429. }
  430. }
  431. mounts, err := fillBindMounts(p, s, mounts)
  432. if err != nil {
  433. return nil, err
  434. }
  435. values := make([]mount.Mount, 0, len(mounts))
  436. for _, v := range mounts {
  437. values = append(values, v)
  438. }
  439. return values, nil
  440. }
  441. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  442. for _, v := range s.Volumes {
  443. bindMount, err := buildMount(p, v)
  444. if err != nil {
  445. return nil, err
  446. }
  447. m[bindMount.Target] = bindMount
  448. }
  449. secrets, err := buildContainerSecretMounts(p, s)
  450. if err != nil {
  451. return nil, err
  452. }
  453. for _, s := range secrets {
  454. if _, found := m[s.Target]; found {
  455. continue
  456. }
  457. m[s.Target] = s
  458. }
  459. configs, err := buildContainerConfigMounts(p, s)
  460. if err != nil {
  461. return nil, err
  462. }
  463. for _, c := range configs {
  464. if _, found := m[c.Target]; found {
  465. continue
  466. }
  467. m[c.Target] = c
  468. }
  469. return m, nil
  470. }
  471. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  472. var mounts = map[string]mount.Mount{}
  473. configsBaseDir := "/"
  474. for _, config := range s.Configs {
  475. target := config.Target
  476. if config.Target == "" {
  477. target = filepath.Join(configsBaseDir, config.Source)
  478. } else if !filepath.IsAbs(config.Target) {
  479. target = filepath.Join(configsBaseDir, config.Target)
  480. }
  481. definedConfig := p.Configs[config.Source]
  482. if definedConfig.External.External {
  483. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  484. }
  485. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  486. Type: types.VolumeTypeBind,
  487. Source: definedConfig.File,
  488. Target: target,
  489. ReadOnly: true,
  490. })
  491. if err != nil {
  492. return nil, err
  493. }
  494. mounts[target] = bindMount
  495. }
  496. values := make([]mount.Mount, 0, len(mounts))
  497. for _, v := range mounts {
  498. values = append(values, v)
  499. }
  500. return values, nil
  501. }
  502. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  503. var mounts = map[string]mount.Mount{}
  504. secretsDir := "/run/secrets"
  505. for _, secret := range s.Secrets {
  506. target := secret.Target
  507. if secret.Target == "" {
  508. target = filepath.Join(secretsDir, secret.Source)
  509. } else if !filepath.IsAbs(secret.Target) {
  510. target = filepath.Join(secretsDir, secret.Target)
  511. }
  512. definedSecret := p.Secrets[secret.Source]
  513. if definedSecret.External.External {
  514. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  515. }
  516. mount, err := buildMount(p, types.ServiceVolumeConfig{
  517. Type: types.VolumeTypeBind,
  518. Source: definedSecret.File,
  519. Target: target,
  520. ReadOnly: true,
  521. })
  522. if err != nil {
  523. return nil, err
  524. }
  525. mounts[target] = mount
  526. }
  527. values := make([]mount.Mount, 0, len(mounts))
  528. for _, v := range mounts {
  529. values = append(values, v)
  530. }
  531. return values, nil
  532. }
  533. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  534. source := volume.Source
  535. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) {
  536. // volume source has already been prefixed with workdir if required, by compose-go project loader
  537. var err error
  538. source, err = filepath.Abs(source)
  539. if err != nil {
  540. return mount.Mount{}, err
  541. }
  542. }
  543. if volume.Type == types.VolumeTypeVolume {
  544. if volume.Source != "" {
  545. pVolume, ok := project.Volumes[volume.Source]
  546. if ok {
  547. source = pVolume.Name
  548. }
  549. }
  550. }
  551. return mount.Mount{
  552. Type: mount.Type(volume.Type),
  553. Source: source,
  554. Target: volume.Target,
  555. ReadOnly: volume.ReadOnly,
  556. Consistency: mount.Consistency(volume.Consistency),
  557. BindOptions: buildBindOption(volume.Bind),
  558. VolumeOptions: buildVolumeOptions(volume.Volume),
  559. TmpfsOptions: buildTmpfsOptions(volume.Tmpfs),
  560. }, nil
  561. }
  562. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  563. if bind == nil {
  564. return nil
  565. }
  566. return &mount.BindOptions{
  567. Propagation: mount.Propagation(bind.Propagation),
  568. // NonRecursive: false, FIXME missing from model ?
  569. }
  570. }
  571. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  572. if vol == nil {
  573. return nil
  574. }
  575. return &mount.VolumeOptions{
  576. NoCopy: vol.NoCopy,
  577. // Labels: , // FIXME missing from model ?
  578. // DriverConfig: , // FIXME missing from model ?
  579. }
  580. }
  581. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  582. if tmpfs == nil {
  583. return nil
  584. }
  585. return &mount.TmpfsOptions{
  586. SizeBytes: tmpfs.Size,
  587. // Mode: , // FIXME missing from model ?
  588. }
  589. }
  590. func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode, containerName string) *network.NetworkingConfig {
  591. config := map[string]*network.EndpointSettings{}
  592. net := string(networkMode)
  593. config[net] = &network.EndpointSettings{
  594. Aliases: append(getAliases(s, s.Networks[net]), containerName),
  595. }
  596. return &network.NetworkingConfig{
  597. EndpointsConfig: config,
  598. }
  599. }
  600. func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
  601. aliases := []string{s.Name}
  602. if c != nil {
  603. aliases = append(aliases, c.Aliases...)
  604. }
  605. return aliases
  606. }
  607. func getNetworkMode(ctx context.Context, p *types.Project, service types.ServiceConfig) (container.NetworkMode, error) {
  608. cState, err := GetContextContainerState(ctx)
  609. if err != nil {
  610. return container.NetworkMode("none"), nil
  611. }
  612. observedState := cState.GetContainers()
  613. mode := service.NetworkMode
  614. if mode == "" {
  615. if len(p.Networks) > 0 {
  616. for name := range getNetworksForService(service) {
  617. return container.NetworkMode(p.Networks[name].Name), nil
  618. }
  619. }
  620. return container.NetworkMode("none"), nil
  621. }
  622. depServiceNetworkMode := getDependentServiceByNetwork(service.NetworkMode)
  623. if depServiceNetworkMode != "" {
  624. depServiceContainers := observedState.filter(isService(depServiceNetworkMode))
  625. if len(depServiceContainers) > 0 {
  626. return container.NetworkMode(types.NetworkModeContainerPrefix + depServiceContainers[0].ID), nil
  627. }
  628. return container.NetworkMode("none"),
  629. fmt.Errorf(`no containers started for network_mode %q in service %q -> %v`,
  630. mode, service.Name, observedState)
  631. }
  632. return container.NetworkMode(mode), nil
  633. }
  634. func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
  635. if len(s.Networks) > 0 {
  636. return s.Networks
  637. }
  638. if s.NetworkMode != "" {
  639. return nil
  640. }
  641. return map[string]*types.ServiceNetworkConfig{"default": nil}
  642. }
  643. func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
  644. _, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  645. if err != nil {
  646. if errdefs.IsNotFound(err) {
  647. if n.External.External {
  648. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  649. }
  650. createOpts := moby.NetworkCreate{
  651. // TODO NameSpace Labels
  652. Labels: n.Labels,
  653. Driver: n.Driver,
  654. Options: n.DriverOpts,
  655. Internal: n.Internal,
  656. Attachable: n.Attachable,
  657. }
  658. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  659. createOpts.IPAM = &network.IPAM{}
  660. }
  661. if n.Ipam.Driver != "" {
  662. createOpts.IPAM.Driver = n.Ipam.Driver
  663. }
  664. for _, ipamConfig := range n.Ipam.Config {
  665. config := network.IPAMConfig{
  666. Subnet: ipamConfig.Subnet,
  667. }
  668. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  669. }
  670. networkEventName := fmt.Sprintf("Network %q", n.Name)
  671. w := progress.ContextWriter(ctx)
  672. w.Event(progress.CreatingEvent(networkEventName))
  673. if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
  674. w.Event(progress.ErrorEvent(networkEventName))
  675. return errors.Wrapf(err, "failed to create network %s", n.Name)
  676. }
  677. w.Event(progress.CreatedEvent(networkEventName))
  678. return nil
  679. }
  680. return err
  681. }
  682. return nil
  683. }
  684. func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
  685. w := progress.ContextWriter(ctx)
  686. eventName := fmt.Sprintf("Network %q", networkName)
  687. w.Event(progress.RemovingEvent(eventName))
  688. if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
  689. w.Event(progress.ErrorEvent(eventName))
  690. return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
  691. }
  692. w.Event(progress.RemovedEvent(eventName))
  693. return nil
  694. }
  695. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
  696. // TODO could identify volume by label vs name
  697. _, err := s.apiClient.VolumeInspect(ctx, volume.Name)
  698. if err != nil {
  699. if !errdefs.IsNotFound(err) {
  700. return err
  701. }
  702. eventName := fmt.Sprintf("Volume %q", volume.Name)
  703. w := progress.ContextWriter(ctx)
  704. w.Event(progress.CreatingEvent(eventName))
  705. _, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
  706. Labels: volume.Labels,
  707. Name: volume.Name,
  708. Driver: volume.Driver,
  709. DriverOpts: volume.DriverOpts,
  710. })
  711. if err != nil {
  712. w.Event(progress.ErrorEvent(eventName))
  713. return err
  714. }
  715. w.Event(progress.CreatedEvent(eventName))
  716. }
  717. return nil
  718. }