create.go 22 KB

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