create.go 24 KB

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