create.go 27 KB

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