create.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  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: attachStdin && stdinOpen,
  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 = configsBaseDir + config.Source
  554. } else if !isUnixAbs(config.Target) {
  555. target = 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 = secretsDir + secret.Source
  585. } else if !isUnixAbs(secret.Target) {
  586. target = 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 isUnixAbs(path string) bool {
  610. return strings.HasPrefix(path, "/")
  611. }
  612. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  613. source := volume.Source
  614. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) {
  615. // volume source has already been prefixed with workdir if required, by compose-go project loader
  616. var err error
  617. source, err = filepath.Abs(source)
  618. if err != nil {
  619. return mount.Mount{}, err
  620. }
  621. }
  622. if volume.Type == types.VolumeTypeVolume {
  623. if volume.Source != "" {
  624. pVolume, ok := project.Volumes[volume.Source]
  625. if ok {
  626. source = pVolume.Name
  627. }
  628. }
  629. }
  630. return mount.Mount{
  631. Type: mount.Type(volume.Type),
  632. Source: source,
  633. Target: volume.Target,
  634. ReadOnly: volume.ReadOnly,
  635. Consistency: mount.Consistency(volume.Consistency),
  636. BindOptions: buildBindOption(volume.Bind),
  637. VolumeOptions: buildVolumeOptions(volume.Volume),
  638. TmpfsOptions: buildTmpfsOptions(volume.Tmpfs),
  639. }, nil
  640. }
  641. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  642. if bind == nil {
  643. return nil
  644. }
  645. return &mount.BindOptions{
  646. Propagation: mount.Propagation(bind.Propagation),
  647. // NonRecursive: false, FIXME missing from model ?
  648. }
  649. }
  650. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  651. if vol == nil {
  652. return nil
  653. }
  654. return &mount.VolumeOptions{
  655. NoCopy: vol.NoCopy,
  656. // Labels: , // FIXME missing from model ?
  657. // DriverConfig: , // FIXME missing from model ?
  658. }
  659. }
  660. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  661. if tmpfs == nil {
  662. return nil
  663. }
  664. return &mount.TmpfsOptions{
  665. SizeBytes: tmpfs.Size,
  666. // Mode: , // FIXME missing from model ?
  667. }
  668. }
  669. func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode, containerName string) *network.NetworkingConfig {
  670. config := map[string]*network.EndpointSettings{}
  671. net := string(networkMode)
  672. config[net] = &network.EndpointSettings{
  673. Aliases: append(getAliases(s, s.Networks[net]), containerName),
  674. }
  675. return &network.NetworkingConfig{
  676. EndpointsConfig: config,
  677. }
  678. }
  679. func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
  680. aliases := []string{s.Name}
  681. if c != nil {
  682. aliases = append(aliases, c.Aliases...)
  683. }
  684. return aliases
  685. }
  686. func getMode(ctx context.Context, serviceName string, mode string) (string, error) {
  687. cState, err := GetContextContainerState(ctx)
  688. if err != nil {
  689. return "", nil
  690. }
  691. observedState := cState.GetContainers()
  692. depService := getDependentServiceFromMode(mode)
  693. if depService != "" {
  694. depServiceContainers := observedState.filter(isService(depService))
  695. if len(depServiceContainers) > 0 {
  696. return types.NetworkModeContainerPrefix + depServiceContainers[0].ID, nil
  697. }
  698. return "", fmt.Errorf(`no containers started for %q in service %q -> %v`,
  699. mode, serviceName, observedState)
  700. }
  701. return mode, nil
  702. }
  703. func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
  704. if len(s.Networks) > 0 {
  705. return s.Networks
  706. }
  707. if s.NetworkMode != "" {
  708. return nil
  709. }
  710. return map[string]*types.ServiceNetworkConfig{"default": nil}
  711. }
  712. func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
  713. _, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  714. if err != nil {
  715. if errdefs.IsNotFound(err) {
  716. if n.External.External {
  717. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  718. }
  719. createOpts := moby.NetworkCreate{
  720. // TODO NameSpace Labels
  721. Labels: n.Labels,
  722. Driver: n.Driver,
  723. Options: n.DriverOpts,
  724. Internal: n.Internal,
  725. Attachable: n.Attachable,
  726. }
  727. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  728. createOpts.IPAM = &network.IPAM{}
  729. }
  730. if n.Ipam.Driver != "" {
  731. createOpts.IPAM.Driver = n.Ipam.Driver
  732. }
  733. for _, ipamConfig := range n.Ipam.Config {
  734. config := network.IPAMConfig{
  735. Subnet: ipamConfig.Subnet,
  736. }
  737. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  738. }
  739. networkEventName := fmt.Sprintf("Network %q", n.Name)
  740. w := progress.ContextWriter(ctx)
  741. w.Event(progress.CreatingEvent(networkEventName))
  742. if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
  743. w.Event(progress.ErrorEvent(networkEventName))
  744. return errors.Wrapf(err, "failed to create network %s", n.Name)
  745. }
  746. w.Event(progress.CreatedEvent(networkEventName))
  747. return nil
  748. }
  749. return err
  750. }
  751. return nil
  752. }
  753. func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
  754. w := progress.ContextWriter(ctx)
  755. eventName := fmt.Sprintf("Network %q", networkName)
  756. w.Event(progress.RemovingEvent(eventName))
  757. if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
  758. w.Event(progress.ErrorEvent(eventName))
  759. return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
  760. }
  761. w.Event(progress.RemovedEvent(eventName))
  762. return nil
  763. }
  764. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
  765. // TODO could identify volume by label vs name
  766. _, err := s.apiClient.VolumeInspect(ctx, volume.Name)
  767. if err != nil {
  768. if !errdefs.IsNotFound(err) {
  769. return err
  770. }
  771. eventName := fmt.Sprintf("Volume %q", volume.Name)
  772. w := progress.ContextWriter(ctx)
  773. w.Event(progress.CreatingEvent(eventName))
  774. _, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
  775. Labels: volume.Labels,
  776. Name: volume.Name,
  777. Driver: volume.Driver,
  778. DriverOpts: volume.DriverOpts,
  779. })
  780. if err != nil {
  781. w.Event(progress.ErrorEvent(eventName))
  782. return err
  783. }
  784. w.Event(progress.CreatedEvent(eventName))
  785. }
  786. return nil
  787. }