create.go 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  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. "bytes"
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "os"
  20. "path"
  21. "path/filepath"
  22. "strconv"
  23. "strings"
  24. moby "github.com/docker/docker/api/types"
  25. "github.com/docker/docker/api/types/blkiodev"
  26. "github.com/docker/docker/api/types/container"
  27. "github.com/docker/docker/api/types/filters"
  28. "github.com/docker/docker/api/types/mount"
  29. "github.com/docker/docker/api/types/network"
  30. "github.com/docker/docker/api/types/strslice"
  31. volume_api "github.com/docker/docker/api/types/volume"
  32. "github.com/docker/docker/errdefs"
  33. "github.com/docker/go-connections/nat"
  34. "github.com/docker/go-units"
  35. "github.com/sirupsen/logrus"
  36. "github.com/compose-spec/compose-go/types"
  37. "github.com/docker/compose/v2/pkg/api"
  38. "github.com/docker/compose/v2/pkg/progress"
  39. "github.com/docker/compose/v2/pkg/utils"
  40. )
  41. type createOptions struct {
  42. AutoRemove bool
  43. AttachStdin bool
  44. UseNetworkAliases bool
  45. Labels types.Labels
  46. }
  47. type createConfigs struct {
  48. Container *container.Config
  49. Host *container.HostConfig
  50. Network *network.NetworkingConfig
  51. Links []string
  52. }
  53. func (s *composeService) Create(ctx context.Context, project *types.Project, createOpts api.CreateOptions) error {
  54. return progress.RunWithTitle(ctx, func(ctx context.Context) error {
  55. return s.create(ctx, project, createOpts)
  56. }, s.stdinfo(), "Creating")
  57. }
  58. func (s *composeService) create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
  59. if len(options.Services) == 0 {
  60. options.Services = project.ServiceNames()
  61. }
  62. var observedState Containers
  63. observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
  64. if err != nil {
  65. return err
  66. }
  67. err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
  68. if err != nil {
  69. return err
  70. }
  71. prepareNetworks(project)
  72. if err := s.ensureNetworks(ctx, project.Networks); err != nil {
  73. return err
  74. }
  75. if err := s.ensureProjectVolumes(ctx, project); err != nil {
  76. return err
  77. }
  78. allServices := project.AllServices()
  79. allServiceNames := []string{}
  80. for _, service := range allServices {
  81. allServiceNames = append(allServiceNames, service.Name)
  82. }
  83. orphans := observedState.filter(isNotService(allServiceNames...))
  84. if len(orphans) > 0 && !options.IgnoreOrphans {
  85. if options.RemoveOrphans {
  86. w := progress.ContextWriter(ctx)
  87. err := s.removeContainers(ctx, w, orphans, nil, false)
  88. if err != nil {
  89. return err
  90. }
  91. } else {
  92. logrus.Warnf("Found orphan containers (%s) for this project. If "+
  93. "you removed or renamed this service in your compose "+
  94. "file, you can run this command with the "+
  95. "--remove-orphans flag to clean it up.", orphans.names())
  96. }
  97. }
  98. return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
  99. }
  100. func prepareNetworks(project *types.Project) {
  101. for k, network := range project.Networks {
  102. network.Labels = network.Labels.Add(api.NetworkLabel, k)
  103. network.Labels = network.Labels.Add(api.ProjectLabel, project.Name)
  104. network.Labels = network.Labels.Add(api.VersionLabel, api.ComposeVersion)
  105. project.Networks[k] = network
  106. }
  107. }
  108. func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
  109. for i, network := range networks {
  110. err := s.ensureNetwork(ctx, &network)
  111. if err != nil {
  112. return err
  113. }
  114. networks[i] = network
  115. }
  116. return nil
  117. }
  118. func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
  119. for k, volume := range project.Volumes {
  120. volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
  121. volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
  122. volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
  123. err := s.ensureVolume(ctx, volume, project.Name)
  124. if err != nil {
  125. return err
  126. }
  127. }
  128. return nil
  129. }
  130. func (s *composeService) getCreateConfigs(ctx context.Context,
  131. p *types.Project,
  132. service types.ServiceConfig,
  133. number int,
  134. inherit *moby.Container,
  135. opts createOptions,
  136. ) (createConfigs, error) {
  137. labels, err := s.prepareLabels(opts.Labels, service, number)
  138. if err != nil {
  139. return createConfigs{}, err
  140. }
  141. var (
  142. runCmd strslice.StrSlice
  143. entrypoint strslice.StrSlice
  144. )
  145. if service.Command != nil {
  146. runCmd = strslice.StrSlice(service.Command)
  147. }
  148. if service.Entrypoint != nil {
  149. entrypoint = strslice.StrSlice(service.Entrypoint)
  150. }
  151. var (
  152. tty = service.Tty
  153. stdinOpen = service.StdinOpen
  154. )
  155. proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
  156. env := proxyConfig.OverrideBy(service.Environment)
  157. containerConfig := container.Config{
  158. Hostname: service.Hostname,
  159. Domainname: service.DomainName,
  160. User: service.User,
  161. ExposedPorts: buildContainerPorts(service),
  162. Tty: tty,
  163. OpenStdin: stdinOpen,
  164. StdinOnce: opts.AttachStdin && stdinOpen,
  165. AttachStdin: opts.AttachStdin,
  166. AttachStderr: true,
  167. AttachStdout: true,
  168. Cmd: runCmd,
  169. Image: api.GetImageNameOrDefault(service, p.Name),
  170. WorkingDir: service.WorkingDir,
  171. Entrypoint: entrypoint,
  172. NetworkDisabled: service.NetworkMode == "disabled",
  173. MacAddress: service.MacAddress,
  174. Labels: labels,
  175. StopSignal: service.StopSignal,
  176. Env: ToMobyEnv(env),
  177. Healthcheck: ToMobyHealthCheck(service.HealthCheck),
  178. StopTimeout: ToSeconds(service.StopGracePeriod),
  179. }
  180. // VOLUMES/MOUNTS/FILESYSTEMS
  181. tmpfs := map[string]string{}
  182. for _, t := range service.Tmpfs {
  183. if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
  184. tmpfs[arr[0]] = arr[1]
  185. } else {
  186. tmpfs[arr[0]] = ""
  187. }
  188. }
  189. binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
  190. if err != nil {
  191. return createConfigs{}, err
  192. }
  193. // NETWORKING
  194. links, err := s.getLinks(ctx, p.Name, service, number)
  195. if err != nil {
  196. return createConfigs{}, err
  197. }
  198. networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases)
  199. portBindings := buildContainerPortBindingOptions(service)
  200. // MISC
  201. resources := getDeployResources(service)
  202. var logConfig container.LogConfig
  203. if service.Logging != nil {
  204. logConfig = container.LogConfig{
  205. Type: service.Logging.Driver,
  206. Config: service.Logging.Options,
  207. }
  208. }
  209. securityOpts, unconfined, err := parseSecurityOpts(p, service.SecurityOpt)
  210. if err != nil {
  211. return createConfigs{}, err
  212. }
  213. hostConfig := container.HostConfig{
  214. AutoRemove: opts.AutoRemove,
  215. Binds: binds,
  216. Mounts: mounts,
  217. CapAdd: strslice.StrSlice(service.CapAdd),
  218. CapDrop: strslice.StrSlice(service.CapDrop),
  219. NetworkMode: networkMode,
  220. Init: service.Init,
  221. IpcMode: container.IpcMode(service.Ipc),
  222. CgroupnsMode: container.CgroupnsMode(service.Cgroup),
  223. ReadonlyRootfs: service.ReadOnly,
  224. RestartPolicy: getRestartPolicy(service),
  225. ShmSize: int64(service.ShmSize),
  226. Sysctls: service.Sysctls,
  227. PortBindings: portBindings,
  228. Resources: resources,
  229. VolumeDriver: service.VolumeDriver,
  230. VolumesFrom: service.VolumesFrom,
  231. DNS: service.DNS,
  232. DNSSearch: service.DNSSearch,
  233. DNSOptions: service.DNSOpts,
  234. ExtraHosts: service.ExtraHosts.AsList(),
  235. SecurityOpt: securityOpts,
  236. UsernsMode: container.UsernsMode(service.UserNSMode),
  237. UTSMode: container.UTSMode(service.Uts),
  238. Privileged: service.Privileged,
  239. PidMode: container.PidMode(service.Pid),
  240. Tmpfs: tmpfs,
  241. Isolation: container.Isolation(service.Isolation),
  242. Runtime: service.Runtime,
  243. LogConfig: logConfig,
  244. GroupAdd: service.GroupAdd,
  245. Links: links,
  246. OomScoreAdj: int(service.OomScoreAdj),
  247. }
  248. if unconfined {
  249. hostConfig.MaskedPaths = []string{}
  250. hostConfig.ReadonlyPaths = []string{}
  251. }
  252. cfgs := createConfigs{
  253. Container: &containerConfig,
  254. Host: &hostConfig,
  255. Network: networkingConfig,
  256. Links: links,
  257. }
  258. return cfgs, nil
  259. }
  260. func getAliases(project *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, useNetworkAliases bool) []string {
  261. aliases := []string{getContainerName(project.Name, service, serviceIndex)}
  262. if useNetworkAliases {
  263. aliases = append(aliases, service.Name)
  264. if cfg := service.Networks[networkKey]; cfg != nil {
  265. aliases = append(aliases, cfg.Aliases...)
  266. }
  267. }
  268. return aliases
  269. }
  270. func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings {
  271. config := service.Networks[networkKey]
  272. var ipam *network.EndpointIPAMConfig
  273. var (
  274. ipv4Address string
  275. ipv6Address string
  276. )
  277. if config != nil {
  278. ipv4Address = config.Ipv4Address
  279. ipv6Address = config.Ipv6Address
  280. ipam = &network.EndpointIPAMConfig{
  281. IPv4Address: ipv4Address,
  282. IPv6Address: ipv6Address,
  283. LinkLocalIPs: config.LinkLocalIPs,
  284. }
  285. }
  286. return &network.EndpointSettings{
  287. Aliases: getAliases(p, service, serviceIndex, networkKey, useNetworkAliases),
  288. Links: links,
  289. IPAddress: ipv4Address,
  290. IPv6Gateway: ipv6Address,
  291. IPAMConfig: ipam,
  292. }
  293. }
  294. // copy/pasted from https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 + RelativePath
  295. // TODO find so way to share this code with docker/cli
  296. func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool, error) {
  297. var (
  298. unconfined bool
  299. parsed []string
  300. )
  301. for _, opt := range securityOpts {
  302. if opt == "systempaths=unconfined" {
  303. unconfined = true
  304. continue
  305. }
  306. con := strings.SplitN(opt, "=", 2)
  307. if len(con) == 1 && con[0] != "no-new-privileges" {
  308. if strings.Contains(opt, ":") {
  309. con = strings.SplitN(opt, ":", 2)
  310. } else {
  311. return securityOpts, false, fmt.Errorf("Invalid security-opt: %q", opt)
  312. }
  313. }
  314. if con[0] == "seccomp" && con[1] != "unconfined" {
  315. f, err := os.ReadFile(p.RelativePath(con[1]))
  316. if err != nil {
  317. return securityOpts, false, fmt.Errorf("opening seccomp profile (%s) failed: %w", con[1], err)
  318. }
  319. b := bytes.NewBuffer(nil)
  320. if err := json.Compact(b, f); err != nil {
  321. return securityOpts, false, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", con[1], err)
  322. }
  323. parsed = append(parsed, fmt.Sprintf("seccomp=%s", b.Bytes()))
  324. } else {
  325. parsed = append(parsed, opt)
  326. }
  327. }
  328. return parsed, unconfined, nil
  329. }
  330. func (s *composeService) prepareLabels(labels types.Labels, service types.ServiceConfig, number int) (map[string]string, error) {
  331. hash, err := ServiceHash(service)
  332. if err != nil {
  333. return nil, err
  334. }
  335. labels[api.ConfigHashLabel] = hash
  336. labels[api.ContainerNumberLabel] = strconv.Itoa(number)
  337. var dependencies []string
  338. for s, d := range service.DependsOn {
  339. dependencies = append(dependencies, fmt.Sprintf("%s:%s:%t", s, d.Condition, d.Restart))
  340. }
  341. labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
  342. return labels, nil
  343. }
  344. // defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable).
  345. func defaultNetworkSettings(
  346. project *types.Project,
  347. service types.ServiceConfig,
  348. serviceIndex int,
  349. links []string,
  350. useNetworkAliases bool,
  351. ) (container.NetworkMode, *network.NetworkingConfig) {
  352. if service.NetworkMode != "" {
  353. return container.NetworkMode(service.NetworkMode), nil
  354. }
  355. if len(project.Networks) == 0 {
  356. return "none", nil
  357. }
  358. var networkKey string
  359. if len(service.Networks) > 0 {
  360. networkKey = service.NetworksByPriority()[0]
  361. } else {
  362. networkKey = "default"
  363. }
  364. mobyNetworkName := project.Networks[networkKey].Name
  365. epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
  366. networkConfig := &network.NetworkingConfig{
  367. EndpointsConfig: map[string]*network.EndpointSettings{
  368. mobyNetworkName: epSettings,
  369. },
  370. }
  371. // From the Engine API docs:
  372. // > Supported standard values are: bridge, host, none, and container:<name|id>.
  373. // > Any other value is taken as a custom network's name to which this container should connect to.
  374. return container.NetworkMode(mobyNetworkName), networkConfig
  375. }
  376. func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
  377. var restart container.RestartPolicy
  378. if service.Restart != "" {
  379. split := strings.Split(service.Restart, ":")
  380. var attempts int
  381. if len(split) > 1 {
  382. attempts, _ = strconv.Atoi(split[1])
  383. }
  384. restart = container.RestartPolicy{
  385. Name: split[0],
  386. MaximumRetryCount: attempts,
  387. }
  388. }
  389. if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
  390. policy := *service.Deploy.RestartPolicy
  391. var attempts int
  392. if policy.MaxAttempts != nil {
  393. attempts = int(*policy.MaxAttempts)
  394. }
  395. restart = container.RestartPolicy{
  396. Name: mapRestartPolicyCondition(policy.Condition),
  397. MaximumRetryCount: attempts,
  398. }
  399. }
  400. return restart
  401. }
  402. func mapRestartPolicyCondition(condition string) string {
  403. // map definitions of deploy.restart_policy to engine definitions
  404. switch condition {
  405. case "none", "no":
  406. return "no"
  407. case "on-failure", "unless-stopped":
  408. return condition
  409. case "any", "always":
  410. return "always"
  411. default:
  412. return condition
  413. }
  414. }
  415. func getDeployResources(s types.ServiceConfig) container.Resources {
  416. var swappiness *int64
  417. if s.MemSwappiness != 0 {
  418. val := int64(s.MemSwappiness)
  419. swappiness = &val
  420. }
  421. resources := container.Resources{
  422. CgroupParent: s.CgroupParent,
  423. Memory: int64(s.MemLimit),
  424. MemorySwap: int64(s.MemSwapLimit),
  425. MemorySwappiness: swappiness,
  426. MemoryReservation: int64(s.MemReservation),
  427. OomKillDisable: &s.OomKillDisable,
  428. CPUCount: s.CPUCount,
  429. CPUPeriod: s.CPUPeriod,
  430. CPUQuota: s.CPUQuota,
  431. CPURealtimePeriod: s.CPURTPeriod,
  432. CPURealtimeRuntime: s.CPURTRuntime,
  433. CPUShares: s.CPUShares,
  434. NanoCPUs: int64(s.CPUS * 1e9),
  435. CPUPercent: int64(s.CPUPercent * 100),
  436. CpusetCpus: s.CPUSet,
  437. DeviceCgroupRules: s.DeviceCgroupRules,
  438. }
  439. if s.PidsLimit != 0 {
  440. resources.PidsLimit = &s.PidsLimit
  441. }
  442. setBlkio(s.BlkioConfig, &resources)
  443. if s.Deploy != nil {
  444. setLimits(s.Deploy.Resources.Limits, &resources)
  445. setReservations(s.Deploy.Resources.Reservations, &resources)
  446. }
  447. for _, device := range s.Devices {
  448. // FIXME should use docker/cli parseDevice, unfortunately private
  449. src := ""
  450. dst := ""
  451. permissions := "rwm"
  452. arr := strings.Split(device, ":")
  453. switch len(arr) {
  454. case 3:
  455. permissions = arr[2]
  456. fallthrough
  457. case 2:
  458. dst = arr[1]
  459. fallthrough
  460. case 1:
  461. src = arr[0]
  462. }
  463. if dst == "" {
  464. dst = src
  465. }
  466. resources.Devices = append(resources.Devices, container.DeviceMapping{
  467. PathOnHost: src,
  468. PathInContainer: dst,
  469. CgroupPermissions: permissions,
  470. })
  471. }
  472. for name, u := range s.Ulimits {
  473. soft := u.Single
  474. if u.Soft != 0 {
  475. soft = u.Soft
  476. }
  477. hard := u.Single
  478. if u.Hard != 0 {
  479. hard = u.Hard
  480. }
  481. resources.Ulimits = append(resources.Ulimits, &units.Ulimit{
  482. Name: name,
  483. Hard: int64(hard),
  484. Soft: int64(soft),
  485. })
  486. }
  487. return resources
  488. }
  489. func setReservations(reservations *types.Resource, resources *container.Resources) {
  490. if reservations == nil {
  491. return
  492. }
  493. // Cpu reservation is a swarm option and PIDs is only a limit
  494. // So we only need to map memory reservation and devices
  495. if reservations.MemoryBytes != 0 {
  496. resources.MemoryReservation = int64(reservations.MemoryBytes)
  497. }
  498. for _, device := range reservations.Devices {
  499. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  500. Capabilities: [][]string{device.Capabilities},
  501. Count: int(device.Count),
  502. DeviceIDs: device.IDs,
  503. Driver: device.Driver,
  504. })
  505. }
  506. }
  507. func setLimits(limits *types.Resource, resources *container.Resources) {
  508. if limits == nil {
  509. return
  510. }
  511. if limits.MemoryBytes != 0 {
  512. resources.Memory = int64(limits.MemoryBytes)
  513. }
  514. if limits.NanoCPUs != "" {
  515. if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
  516. resources.NanoCPUs = int64(f * 1e9)
  517. }
  518. }
  519. if limits.Pids > 0 {
  520. resources.PidsLimit = &limits.Pids
  521. }
  522. }
  523. func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
  524. if blkio == nil {
  525. return
  526. }
  527. resources.BlkioWeight = blkio.Weight
  528. for _, b := range blkio.WeightDevice {
  529. resources.BlkioWeightDevice = append(resources.BlkioWeightDevice, &blkiodev.WeightDevice{
  530. Path: b.Path,
  531. Weight: b.Weight,
  532. })
  533. }
  534. for _, b := range blkio.DeviceReadBps {
  535. resources.BlkioDeviceReadBps = append(resources.BlkioDeviceReadBps, &blkiodev.ThrottleDevice{
  536. Path: b.Path,
  537. Rate: uint64(b.Rate),
  538. })
  539. }
  540. for _, b := range blkio.DeviceReadIOps {
  541. resources.BlkioDeviceReadIOps = append(resources.BlkioDeviceReadIOps, &blkiodev.ThrottleDevice{
  542. Path: b.Path,
  543. Rate: uint64(b.Rate),
  544. })
  545. }
  546. for _, b := range blkio.DeviceWriteBps {
  547. resources.BlkioDeviceWriteBps = append(resources.BlkioDeviceWriteBps, &blkiodev.ThrottleDevice{
  548. Path: b.Path,
  549. Rate: uint64(b.Rate),
  550. })
  551. }
  552. for _, b := range blkio.DeviceWriteIOps {
  553. resources.BlkioDeviceWriteIOps = append(resources.BlkioDeviceWriteIOps, &blkiodev.ThrottleDevice{
  554. Path: b.Path,
  555. Rate: uint64(b.Rate),
  556. })
  557. }
  558. }
  559. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  560. ports := nat.PortSet{}
  561. for _, s := range s.Expose {
  562. p := nat.Port(s)
  563. ports[p] = struct{}{}
  564. }
  565. for _, p := range s.Ports {
  566. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  567. ports[p] = struct{}{}
  568. }
  569. return ports
  570. }
  571. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  572. bindings := nat.PortMap{}
  573. for _, port := range s.Ports {
  574. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  575. binding := nat.PortBinding{
  576. HostIP: port.HostIP,
  577. HostPort: port.Published,
  578. }
  579. bindings[p] = append(bindings[p], binding)
  580. }
  581. return bindings
  582. }
  583. func getDependentServiceFromMode(mode string) string {
  584. if strings.HasPrefix(
  585. mode,
  586. types.NetworkModeServicePrefix,
  587. ) {
  588. return mode[len(types.NetworkModeServicePrefix):]
  589. }
  590. return ""
  591. }
  592. func (s *composeService) buildContainerVolumes(
  593. ctx context.Context,
  594. p types.Project,
  595. service types.ServiceConfig,
  596. inherit *moby.Container,
  597. ) ([]string, []mount.Mount, error) {
  598. var mounts []mount.Mount
  599. var binds []string
  600. image := api.GetImageNameOrDefault(service, p.Name)
  601. imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
  602. if err != nil {
  603. return nil, nil, err
  604. }
  605. mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
  606. if err != nil {
  607. return nil, nil, err
  608. }
  609. MOUNTS:
  610. for _, m := range mountOptions {
  611. if m.Type == mount.TypeNamedPipe {
  612. mounts = append(mounts, m)
  613. continue
  614. }
  615. if m.Type == mount.TypeBind {
  616. // `Mount` is preferred but does not offer option to created host path if missing
  617. // so `Bind` API is used here with raw volume string
  618. // see https://github.com/moby/moby/issues/43483
  619. for _, v := range service.Volumes {
  620. if v.Target == m.Target {
  621. switch {
  622. case string(m.Type) != v.Type:
  623. v.Source = m.Source
  624. fallthrough
  625. case v.Bind != nil && v.Bind.CreateHostPath:
  626. binds = append(binds, v.String())
  627. continue MOUNTS
  628. }
  629. }
  630. }
  631. }
  632. mounts = append(mounts, m)
  633. }
  634. return binds, mounts, nil
  635. }
  636. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  637. var mounts = map[string]mount.Mount{}
  638. if inherit != nil {
  639. for _, m := range inherit.Mounts {
  640. if m.Type == "tmpfs" {
  641. continue
  642. }
  643. src := m.Source
  644. if m.Type == "volume" {
  645. src = m.Name
  646. }
  647. m.Destination = path.Clean(m.Destination)
  648. if img.Config != nil {
  649. if _, ok := img.Config.Volumes[m.Destination]; ok {
  650. // inherit previous container's anonymous volume
  651. mounts[m.Destination] = mount.Mount{
  652. Type: m.Type,
  653. Source: src,
  654. Target: m.Destination,
  655. ReadOnly: !m.RW,
  656. }
  657. }
  658. }
  659. volumes := []types.ServiceVolumeConfig{}
  660. for _, v := range s.Volumes {
  661. if v.Target != m.Destination || v.Source != "" {
  662. volumes = append(volumes, v)
  663. continue
  664. }
  665. // inherit previous container's anonymous volume
  666. mounts[m.Destination] = mount.Mount{
  667. Type: m.Type,
  668. Source: src,
  669. Target: m.Destination,
  670. ReadOnly: !m.RW,
  671. }
  672. }
  673. s.Volumes = volumes
  674. }
  675. }
  676. mounts, err := fillBindMounts(p, s, mounts)
  677. if err != nil {
  678. return nil, err
  679. }
  680. values := make([]mount.Mount, 0, len(mounts))
  681. for _, v := range mounts {
  682. values = append(values, v)
  683. }
  684. return values, nil
  685. }
  686. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  687. for _, v := range s.Volumes {
  688. bindMount, err := buildMount(p, v)
  689. if err != nil {
  690. return nil, err
  691. }
  692. m[bindMount.Target] = bindMount
  693. }
  694. secrets, err := buildContainerSecretMounts(p, s)
  695. if err != nil {
  696. return nil, err
  697. }
  698. for _, s := range secrets {
  699. if _, found := m[s.Target]; found {
  700. continue
  701. }
  702. m[s.Target] = s
  703. }
  704. configs, err := buildContainerConfigMounts(p, s)
  705. if err != nil {
  706. return nil, err
  707. }
  708. for _, c := range configs {
  709. if _, found := m[c.Target]; found {
  710. continue
  711. }
  712. m[c.Target] = c
  713. }
  714. return m, nil
  715. }
  716. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  717. var mounts = map[string]mount.Mount{}
  718. configsBaseDir := "/"
  719. for _, config := range s.Configs {
  720. target := config.Target
  721. if config.Target == "" {
  722. target = configsBaseDir + config.Source
  723. } else if !isAbsTarget(config.Target) {
  724. target = configsBaseDir + config.Target
  725. }
  726. if config.UID != "" || config.GID != "" || config.Mode != nil {
  727. logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
  728. }
  729. definedConfig := p.Configs[config.Source]
  730. if definedConfig.External.External {
  731. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  732. }
  733. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  734. Type: types.VolumeTypeBind,
  735. Source: definedConfig.File,
  736. Target: target,
  737. ReadOnly: true,
  738. })
  739. if err != nil {
  740. return nil, err
  741. }
  742. mounts[target] = bindMount
  743. }
  744. values := make([]mount.Mount, 0, len(mounts))
  745. for _, v := range mounts {
  746. values = append(values, v)
  747. }
  748. return values, nil
  749. }
  750. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  751. var mounts = map[string]mount.Mount{}
  752. secretsDir := "/run/secrets/"
  753. for _, secret := range s.Secrets {
  754. target := secret.Target
  755. if secret.Target == "" {
  756. target = secretsDir + secret.Source
  757. } else if !isAbsTarget(secret.Target) {
  758. target = secretsDir + secret.Target
  759. }
  760. if secret.UID != "" || secret.GID != "" || secret.Mode != nil {
  761. logrus.Warn("secrets `uid`, `gid` and `mode` are not supported, they will be ignored")
  762. }
  763. definedSecret := p.Secrets[secret.Source]
  764. if definedSecret.External.External {
  765. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  766. }
  767. if definedSecret.Environment != "" {
  768. continue
  769. }
  770. mnt, err := buildMount(p, types.ServiceVolumeConfig{
  771. Type: types.VolumeTypeBind,
  772. Source: definedSecret.File,
  773. Target: target,
  774. ReadOnly: true,
  775. })
  776. if err != nil {
  777. return nil, err
  778. }
  779. mounts[target] = mnt
  780. }
  781. values := make([]mount.Mount, 0, len(mounts))
  782. for _, v := range mounts {
  783. values = append(values, v)
  784. }
  785. return values, nil
  786. }
  787. func isAbsTarget(p string) bool {
  788. return isUnixAbs(p) || isWindowsAbs(p)
  789. }
  790. func isUnixAbs(p string) bool {
  791. return strings.HasPrefix(p, "/")
  792. }
  793. func isWindowsAbs(p string) bool {
  794. if strings.HasPrefix(p, "\\\\") {
  795. return true
  796. }
  797. if len(p) > 2 && p[1] == ':' {
  798. return p[2] == '\\'
  799. }
  800. return false
  801. }
  802. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  803. source := volume.Source
  804. // on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
  805. // do not replace these with filepath.Abs(source) that will include a default drive.
  806. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) && !strings.HasPrefix(source, "/") {
  807. // volume source has already been prefixed with workdir if required, by compose-go project loader
  808. var err error
  809. source, err = filepath.Abs(source)
  810. if err != nil {
  811. return mount.Mount{}, err
  812. }
  813. }
  814. if volume.Type == types.VolumeTypeVolume {
  815. if volume.Source != "" {
  816. pVolume, ok := project.Volumes[volume.Source]
  817. if ok {
  818. source = pVolume.Name
  819. }
  820. }
  821. }
  822. bind, vol, tmpfs := buildMountOptions(project, volume)
  823. volume.Target = path.Clean(volume.Target)
  824. if bind != nil {
  825. volume.Type = types.VolumeTypeBind
  826. }
  827. return mount.Mount{
  828. Type: mount.Type(volume.Type),
  829. Source: source,
  830. Target: volume.Target,
  831. ReadOnly: volume.ReadOnly,
  832. Consistency: mount.Consistency(volume.Consistency),
  833. BindOptions: bind,
  834. VolumeOptions: vol,
  835. TmpfsOptions: tmpfs,
  836. }, nil
  837. }
  838. func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
  839. switch volume.Type {
  840. case "bind":
  841. if volume.Volume != nil {
  842. logrus.Warnf("mount of type `bind` should not define `volume` option")
  843. }
  844. if volume.Tmpfs != nil {
  845. logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
  846. }
  847. return buildBindOption(volume.Bind), nil, nil
  848. case "volume":
  849. if volume.Bind != nil {
  850. logrus.Warnf("mount of type `volume` should not define `bind` option")
  851. }
  852. if volume.Tmpfs != nil {
  853. logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
  854. }
  855. if v, ok := project.Volumes[volume.Source]; ok && v.DriverOpts["o"] == types.VolumeTypeBind {
  856. return buildBindOption(&types.ServiceVolumeBind{
  857. CreateHostPath: true,
  858. }), nil, nil
  859. }
  860. return nil, buildVolumeOptions(volume.Volume), nil
  861. case "tmpfs":
  862. if volume.Bind != nil {
  863. logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
  864. }
  865. if volume.Volume != nil {
  866. logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
  867. }
  868. return nil, nil, buildTmpfsOptions(volume.Tmpfs)
  869. }
  870. return nil, nil, nil
  871. }
  872. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  873. if bind == nil {
  874. return nil
  875. }
  876. return &mount.BindOptions{
  877. Propagation: mount.Propagation(bind.Propagation),
  878. // NonRecursive: false, FIXME missing from model ?
  879. }
  880. }
  881. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  882. if vol == nil {
  883. return nil
  884. }
  885. return &mount.VolumeOptions{
  886. NoCopy: vol.NoCopy,
  887. // Labels: , // FIXME missing from model ?
  888. // DriverConfig: , // FIXME missing from model ?
  889. }
  890. }
  891. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  892. if tmpfs == nil {
  893. return nil
  894. }
  895. return &mount.TmpfsOptions{
  896. SizeBytes: int64(tmpfs.Size),
  897. Mode: os.FileMode(tmpfs.Mode),
  898. }
  899. }
  900. func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
  901. if n.External.External {
  902. return s.resolveExternalNetwork(ctx, n)
  903. }
  904. err := s.resolveOrCreateNetwork(ctx, n)
  905. if errdefs.IsConflict(err) {
  906. // Maybe another execution of `docker compose up|run` created same network
  907. // let's retry once
  908. return s.resolveOrCreateNetwork(ctx, n)
  909. }
  910. return err
  911. }
  912. func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
  913. expectedNetworkLabel := n.Labels[api.NetworkLabel]
  914. expectedProjectLabel := n.Labels[api.ProjectLabel]
  915. // First, try to find a unique network matching by name or ID
  916. inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  917. if err == nil {
  918. // NetworkInspect will match on ID prefix, so double check we get the expected one
  919. // as looking for network named `db` we could erroneously matched network ID `db9086999caf`
  920. if inspect.Name == n.Name || inspect.ID == n.Name {
  921. p, ok := inspect.Labels[api.ProjectLabel]
  922. if !ok {
  923. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  924. "Set `external: true` to use an existing network", n.Name)
  925. } else if p != expectedProjectLabel {
  926. logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
  927. "Set `external: true` to use an existing network", n.Name, expectedProjectLabel)
  928. }
  929. if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
  930. return fmt.Errorf("network %s was found but has incorrect label %s set to %q", n.Name, api.NetworkLabel, inspect.Labels[api.NetworkLabel])
  931. }
  932. return nil
  933. }
  934. }
  935. // ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
  936. // Either not found, or name is ambiguous - use NetworkList to list by name
  937. networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
  938. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  939. })
  940. if err != nil {
  941. return err
  942. }
  943. // NetworkList Matches all or part of a network name, so we have to filter for a strict match
  944. networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
  945. return net.Name == n.Name
  946. })
  947. for _, net := range networks {
  948. if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
  949. net.Labels[api.NetworkLabel] == expectedNetworkLabel {
  950. return nil
  951. }
  952. }
  953. // we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
  954. // scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
  955. // prevents to create another one.
  956. if len(networks) > 0 {
  957. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  958. "Set `external: true` to use an existing network", n.Name)
  959. return nil
  960. }
  961. var ipam *network.IPAM
  962. if n.Ipam.Config != nil {
  963. var config []network.IPAMConfig
  964. for _, pool := range n.Ipam.Config {
  965. config = append(config, network.IPAMConfig{
  966. Subnet: pool.Subnet,
  967. IPRange: pool.IPRange,
  968. Gateway: pool.Gateway,
  969. AuxAddress: pool.AuxiliaryAddresses,
  970. })
  971. }
  972. ipam = &network.IPAM{
  973. Driver: n.Ipam.Driver,
  974. Config: config,
  975. }
  976. }
  977. createOpts := moby.NetworkCreate{
  978. CheckDuplicate: true,
  979. Labels: n.Labels,
  980. Driver: n.Driver,
  981. Options: n.DriverOpts,
  982. Internal: n.Internal,
  983. Attachable: n.Attachable,
  984. IPAM: ipam,
  985. EnableIPv6: n.EnableIPv6,
  986. }
  987. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  988. createOpts.IPAM = &network.IPAM{}
  989. }
  990. if n.Ipam.Driver != "" {
  991. createOpts.IPAM.Driver = n.Ipam.Driver
  992. }
  993. for _, ipamConfig := range n.Ipam.Config {
  994. config := network.IPAMConfig{
  995. Subnet: ipamConfig.Subnet,
  996. IPRange: ipamConfig.IPRange,
  997. Gateway: ipamConfig.Gateway,
  998. AuxAddress: ipamConfig.AuxiliaryAddresses,
  999. }
  1000. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  1001. }
  1002. networkEventName := fmt.Sprintf("Network %s", n.Name)
  1003. w := progress.ContextWriter(ctx)
  1004. w.Event(progress.CreatingEvent(networkEventName))
  1005. _, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
  1006. if err != nil {
  1007. w.Event(progress.ErrorEvent(networkEventName))
  1008. return fmt.Errorf("failed to create network %s: %w", n.Name, err)
  1009. }
  1010. w.Event(progress.CreatedEvent(networkEventName))
  1011. return nil
  1012. }
  1013. func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
  1014. // NetworkInspect will match on ID prefix, so NetworkList with a name
  1015. // filter is used to look for an exact match to prevent e.g. a network
  1016. // named `db` from getting erroneously matched to a network with an ID
  1017. // like `db9086999caf`
  1018. networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
  1019. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  1020. })
  1021. if err != nil {
  1022. return err
  1023. }
  1024. if len(networks) == 0 {
  1025. // in this instance, n.Name is really an ID
  1026. sn, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  1027. if err != nil {
  1028. return err
  1029. }
  1030. networks = append(networks, sn)
  1031. }
  1032. // NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
  1033. networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
  1034. // later in this function, the name is changed the to ID.
  1035. // this function is called during the rebuild stage of `compose watch`.
  1036. // we still require just one network back, but we need to run the search on the ID
  1037. return net.Name == n.Name || net.ID == n.Name
  1038. })
  1039. switch len(networks) {
  1040. case 1:
  1041. n.Name = networks[0].ID
  1042. return nil
  1043. case 0:
  1044. if n.Driver == "overlay" {
  1045. // Swarm nodes do not register overlay networks that were
  1046. // created on a different node unless they're in use.
  1047. // Here we assume `driver` is relevant for a network we don't manage
  1048. // which is a non-sense, but this is our legacy ¯\(ツ)/¯
  1049. // networkAttach will later fail anyway if network actually doesn't exists
  1050. enabled, err := s.isSWarmEnabled(ctx)
  1051. if err != nil {
  1052. return err
  1053. }
  1054. if enabled {
  1055. return nil
  1056. }
  1057. }
  1058. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  1059. default:
  1060. return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
  1061. }
  1062. }
  1063. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
  1064. inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
  1065. if err != nil {
  1066. if !errdefs.IsNotFound(err) {
  1067. return err
  1068. }
  1069. if volume.External.External {
  1070. return fmt.Errorf("external volume %q not found", volume.Name)
  1071. }
  1072. err := s.createVolume(ctx, volume)
  1073. return err
  1074. }
  1075. if volume.External.External {
  1076. return nil
  1077. }
  1078. // Volume exists with name, but let's double-check this is the expected one
  1079. p, ok := inspected.Labels[api.ProjectLabel]
  1080. if !ok {
  1081. logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
  1082. }
  1083. if ok && p != project {
  1084. logrus.Warnf("volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume", volume.Name, p, project)
  1085. }
  1086. return nil
  1087. }
  1088. func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
  1089. eventName := fmt.Sprintf("Volume %q", volume.Name)
  1090. w := progress.ContextWriter(ctx)
  1091. w.Event(progress.CreatingEvent(eventName))
  1092. _, err := s.apiClient().VolumeCreate(ctx, volume_api.CreateOptions{
  1093. Labels: volume.Labels,
  1094. Name: volume.Name,
  1095. Driver: volume.Driver,
  1096. DriverOpts: volume.DriverOpts,
  1097. })
  1098. if err != nil {
  1099. w.Event(progress.ErrorEvent(eventName))
  1100. return err
  1101. }
  1102. w.Event(progress.CreatedEvent(eventName))
  1103. return nil
  1104. }