create.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  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. "errors"
  19. "fmt"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "strconv"
  24. "strings"
  25. moby "github.com/docker/docker/api/types"
  26. "github.com/docker/docker/api/types/blkiodev"
  27. "github.com/docker/docker/api/types/container"
  28. "github.com/docker/docker/api/types/filters"
  29. "github.com/docker/docker/api/types/mount"
  30. "github.com/docker/docker/api/types/network"
  31. "github.com/docker/docker/api/types/strslice"
  32. volume_api "github.com/docker/docker/api/types/volume"
  33. "github.com/docker/docker/errdefs"
  34. "github.com/docker/go-connections/nat"
  35. "github.com/docker/go-units"
  36. "github.com/sirupsen/logrus"
  37. "github.com/compose-spec/compose-go/v2/types"
  38. "github.com/docker/compose/v2/pkg/api"
  39. "github.com/docker/compose/v2/pkg/progress"
  40. "github.com/docker/compose/v2/pkg/utils"
  41. )
  42. type createOptions struct {
  43. AutoRemove bool
  44. AttachStdin bool
  45. UseNetworkAliases bool
  46. Labels types.Labels
  47. }
  48. type createConfigs struct {
  49. Container *container.Config
  50. Host *container.HostConfig
  51. Network *network.NetworkingConfig
  52. Links []string
  53. }
  54. func (s *composeService) Create(ctx context.Context, project *types.Project, createOpts api.CreateOptions) error {
  55. return progress.RunWithTitle(ctx, func(ctx context.Context) error {
  56. return s.create(ctx, project, createOpts)
  57. }, s.stdinfo(), "Creating")
  58. }
  59. func (s *composeService) create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
  60. if len(options.Services) == 0 {
  61. options.Services = project.ServiceNames()
  62. }
  63. var observedState Containers
  64. observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
  65. if err != nil {
  66. return err
  67. }
  68. err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
  69. if err != nil {
  70. return err
  71. }
  72. prepareNetworks(project)
  73. if err := s.ensureNetworks(ctx, project.Networks); err != nil {
  74. return err
  75. }
  76. if err := s.ensureProjectVolumes(ctx, project); err != nil {
  77. return err
  78. }
  79. allServiceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
  80. orphans := observedState.filter(isNotService(allServiceNames...))
  81. if len(orphans) > 0 && !options.IgnoreOrphans {
  82. if options.RemoveOrphans {
  83. w := progress.ContextWriter(ctx)
  84. err := s.removeContainers(ctx, w, orphans, nil, false)
  85. if err != nil {
  86. return err
  87. }
  88. } else {
  89. logrus.Warnf("Found orphan containers (%s) for this project. If "+
  90. "you removed or renamed this service in your compose "+
  91. "file, you can run this command with the "+
  92. "--remove-orphans flag to clean it up.", orphans.names())
  93. }
  94. }
  95. return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
  96. }
  97. func prepareNetworks(project *types.Project) {
  98. for k, network := range project.Networks {
  99. network.Labels = network.Labels.Add(api.NetworkLabel, k)
  100. network.Labels = network.Labels.Add(api.ProjectLabel, project.Name)
  101. network.Labels = network.Labels.Add(api.VersionLabel, api.ComposeVersion)
  102. project.Networks[k] = network
  103. }
  104. }
  105. func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
  106. for i, network := range networks {
  107. err := s.ensureNetwork(ctx, &network)
  108. if err != nil {
  109. return err
  110. }
  111. networks[i] = network
  112. }
  113. return nil
  114. }
  115. func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
  116. for k, volume := range project.Volumes {
  117. volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
  118. volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
  119. volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
  120. err := s.ensureVolume(ctx, volume, project.Name)
  121. if err != nil {
  122. return err
  123. }
  124. }
  125. return nil
  126. }
  127. func (s *composeService) getCreateConfigs(ctx context.Context,
  128. p *types.Project,
  129. service types.ServiceConfig,
  130. number int,
  131. inherit *moby.Container,
  132. opts createOptions,
  133. ) (createConfigs, error) {
  134. labels, err := s.prepareLabels(opts.Labels, service, number)
  135. if err != nil {
  136. return createConfigs{}, err
  137. }
  138. var (
  139. runCmd strslice.StrSlice
  140. entrypoint strslice.StrSlice
  141. )
  142. if service.Command != nil {
  143. runCmd = strslice.StrSlice(service.Command)
  144. }
  145. if service.Entrypoint != nil {
  146. entrypoint = strslice.StrSlice(service.Entrypoint)
  147. }
  148. var (
  149. tty = service.Tty
  150. stdinOpen = service.StdinOpen
  151. )
  152. proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
  153. env := proxyConfig.OverrideBy(service.Environment)
  154. healthcheck, err := s.ToMobyHealthCheck(ctx, service.HealthCheck)
  155. if err != nil {
  156. return createConfigs{}, err
  157. }
  158. var containerConfig = container.Config{
  159. Hostname: service.Hostname,
  160. Domainname: service.DomainName,
  161. User: service.User,
  162. ExposedPorts: buildContainerPorts(service),
  163. Tty: tty,
  164. OpenStdin: stdinOpen,
  165. StdinOnce: opts.AttachStdin && stdinOpen,
  166. AttachStdin: opts.AttachStdin,
  167. AttachStderr: true,
  168. AttachStdout: true,
  169. Cmd: runCmd,
  170. Image: api.GetImageNameOrDefault(service, p.Name),
  171. WorkingDir: service.WorkingDir,
  172. Entrypoint: entrypoint,
  173. NetworkDisabled: service.NetworkMode == "disabled",
  174. MacAddress: service.MacAddress,
  175. Labels: labels,
  176. StopSignal: service.StopSignal,
  177. Env: ToMobyEnv(env),
  178. Healthcheck: healthcheck,
  179. StopTimeout: ToSeconds(service.StopGracePeriod),
  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: mapRestartPolicyCondition(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) container.RestartPolicyMode {
  403. // map definitions of deploy.restart_policy to engine definitions
  404. switch condition {
  405. case "none", "no":
  406. return container.RestartPolicyDisabled
  407. case "on-failure":
  408. return container.RestartPolicyOnFailure
  409. case "unless-stopped":
  410. return container.RestartPolicyUnlessStopped
  411. case "any", "always":
  412. return container.RestartPolicyAlways
  413. default:
  414. return container.RestartPolicyMode(condition)
  415. }
  416. }
  417. func getDeployResources(s types.ServiceConfig) container.Resources {
  418. var swappiness *int64
  419. if s.MemSwappiness != 0 {
  420. val := int64(s.MemSwappiness)
  421. swappiness = &val
  422. }
  423. resources := container.Resources{
  424. CgroupParent: s.CgroupParent,
  425. Memory: int64(s.MemLimit),
  426. MemorySwap: int64(s.MemSwapLimit),
  427. MemorySwappiness: swappiness,
  428. MemoryReservation: int64(s.MemReservation),
  429. OomKillDisable: &s.OomKillDisable,
  430. CPUCount: s.CPUCount,
  431. CPUPeriod: s.CPUPeriod,
  432. CPUQuota: s.CPUQuota,
  433. CPURealtimePeriod: s.CPURTPeriod,
  434. CPURealtimeRuntime: s.CPURTRuntime,
  435. CPUShares: s.CPUShares,
  436. NanoCPUs: int64(s.CPUS * 1e9),
  437. CPUPercent: int64(s.CPUPercent * 100),
  438. CpusetCpus: s.CPUSet,
  439. DeviceCgroupRules: s.DeviceCgroupRules,
  440. }
  441. if s.PidsLimit != 0 {
  442. resources.PidsLimit = &s.PidsLimit
  443. }
  444. setBlkio(s.BlkioConfig, &resources)
  445. if s.Deploy != nil {
  446. setLimits(s.Deploy.Resources.Limits, &resources)
  447. setReservations(s.Deploy.Resources.Reservations, &resources)
  448. }
  449. for _, device := range s.Devices {
  450. // FIXME should use docker/cli parseDevice, unfortunately private
  451. src := ""
  452. dst := ""
  453. permissions := "rwm"
  454. arr := strings.Split(device, ":")
  455. switch len(arr) {
  456. case 3:
  457. permissions = arr[2]
  458. fallthrough
  459. case 2:
  460. dst = arr[1]
  461. fallthrough
  462. case 1:
  463. src = arr[0]
  464. }
  465. if dst == "" {
  466. dst = src
  467. }
  468. resources.Devices = append(resources.Devices, container.DeviceMapping{
  469. PathOnHost: src,
  470. PathInContainer: dst,
  471. CgroupPermissions: permissions,
  472. })
  473. }
  474. ulimits := toUlimits(s.Ulimits)
  475. resources.Ulimits = ulimits
  476. return resources
  477. }
  478. func toUlimits(m map[string]*types.UlimitsConfig) []*units.Ulimit {
  479. var ulimits []*units.Ulimit
  480. for name, u := range m {
  481. soft := u.Single
  482. if u.Soft != 0 {
  483. soft = u.Soft
  484. }
  485. hard := u.Single
  486. if u.Hard != 0 {
  487. hard = u.Hard
  488. }
  489. ulimits = append(ulimits, &units.Ulimit{
  490. Name: name,
  491. Hard: int64(hard),
  492. Soft: int64(soft),
  493. })
  494. }
  495. return ulimits
  496. }
  497. func setReservations(reservations *types.Resource, resources *container.Resources) {
  498. if reservations == nil {
  499. return
  500. }
  501. // Cpu reservation is a swarm option and PIDs is only a limit
  502. // So we only need to map memory reservation and devices
  503. if reservations.MemoryBytes != 0 {
  504. resources.MemoryReservation = int64(reservations.MemoryBytes)
  505. }
  506. for _, device := range reservations.Devices {
  507. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  508. Capabilities: [][]string{device.Capabilities},
  509. Count: int(device.Count),
  510. DeviceIDs: device.IDs,
  511. Driver: device.Driver,
  512. })
  513. }
  514. }
  515. func setLimits(limits *types.Resource, resources *container.Resources) {
  516. if limits == nil {
  517. return
  518. }
  519. if limits.MemoryBytes != 0 {
  520. resources.Memory = int64(limits.MemoryBytes)
  521. }
  522. if limits.NanoCPUs != "" {
  523. if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
  524. resources.NanoCPUs = int64(f * 1e9)
  525. }
  526. }
  527. if limits.Pids > 0 {
  528. resources.PidsLimit = &limits.Pids
  529. }
  530. }
  531. func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
  532. if blkio == nil {
  533. return
  534. }
  535. resources.BlkioWeight = blkio.Weight
  536. for _, b := range blkio.WeightDevice {
  537. resources.BlkioWeightDevice = append(resources.BlkioWeightDevice, &blkiodev.WeightDevice{
  538. Path: b.Path,
  539. Weight: b.Weight,
  540. })
  541. }
  542. for _, b := range blkio.DeviceReadBps {
  543. resources.BlkioDeviceReadBps = append(resources.BlkioDeviceReadBps, &blkiodev.ThrottleDevice{
  544. Path: b.Path,
  545. Rate: uint64(b.Rate),
  546. })
  547. }
  548. for _, b := range blkio.DeviceReadIOps {
  549. resources.BlkioDeviceReadIOps = append(resources.BlkioDeviceReadIOps, &blkiodev.ThrottleDevice{
  550. Path: b.Path,
  551. Rate: uint64(b.Rate),
  552. })
  553. }
  554. for _, b := range blkio.DeviceWriteBps {
  555. resources.BlkioDeviceWriteBps = append(resources.BlkioDeviceWriteBps, &blkiodev.ThrottleDevice{
  556. Path: b.Path,
  557. Rate: uint64(b.Rate),
  558. })
  559. }
  560. for _, b := range blkio.DeviceWriteIOps {
  561. resources.BlkioDeviceWriteIOps = append(resources.BlkioDeviceWriteIOps, &blkiodev.ThrottleDevice{
  562. Path: b.Path,
  563. Rate: uint64(b.Rate),
  564. })
  565. }
  566. }
  567. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  568. ports := nat.PortSet{}
  569. for _, s := range s.Expose {
  570. p := nat.Port(s)
  571. ports[p] = struct{}{}
  572. }
  573. for _, p := range s.Ports {
  574. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  575. ports[p] = struct{}{}
  576. }
  577. return ports
  578. }
  579. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  580. bindings := nat.PortMap{}
  581. for _, port := range s.Ports {
  582. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  583. binding := nat.PortBinding{
  584. HostIP: port.HostIP,
  585. HostPort: port.Published,
  586. }
  587. bindings[p] = append(bindings[p], binding)
  588. }
  589. return bindings
  590. }
  591. func getDependentServiceFromMode(mode string) string {
  592. if strings.HasPrefix(
  593. mode,
  594. types.NetworkModeServicePrefix,
  595. ) {
  596. return mode[len(types.NetworkModeServicePrefix):]
  597. }
  598. return ""
  599. }
  600. func (s *composeService) buildContainerVolumes(
  601. ctx context.Context,
  602. p types.Project,
  603. service types.ServiceConfig,
  604. inherit *moby.Container,
  605. ) ([]string, []mount.Mount, error) {
  606. var mounts []mount.Mount
  607. var binds []string
  608. image := api.GetImageNameOrDefault(service, p.Name)
  609. imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
  610. if err != nil {
  611. return nil, nil, err
  612. }
  613. mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
  614. if err != nil {
  615. return nil, nil, err
  616. }
  617. MOUNTS:
  618. for _, m := range mountOptions {
  619. if m.Type == mount.TypeNamedPipe {
  620. mounts = append(mounts, m)
  621. continue
  622. }
  623. if m.Type == mount.TypeBind {
  624. // `Mount` is preferred but does not offer option to created host path if missing
  625. // so `Bind` API is used here with raw volume string
  626. // see https://github.com/moby/moby/issues/43483
  627. for _, v := range service.Volumes {
  628. if v.Target == m.Target {
  629. switch {
  630. case string(m.Type) != v.Type:
  631. v.Source = m.Source
  632. fallthrough
  633. case v.Bind != nil && v.Bind.CreateHostPath:
  634. binds = append(binds, v.String())
  635. continue MOUNTS
  636. }
  637. }
  638. }
  639. }
  640. mounts = append(mounts, m)
  641. }
  642. return binds, mounts, nil
  643. }
  644. func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
  645. var mounts = map[string]mount.Mount{}
  646. if inherit != nil {
  647. for _, m := range inherit.Mounts {
  648. if m.Type == "tmpfs" {
  649. continue
  650. }
  651. src := m.Source
  652. if m.Type == "volume" {
  653. src = m.Name
  654. }
  655. m.Destination = path.Clean(m.Destination)
  656. if img.Config != nil {
  657. if _, ok := img.Config.Volumes[m.Destination]; ok {
  658. // inherit previous container's anonymous volume
  659. mounts[m.Destination] = mount.Mount{
  660. Type: m.Type,
  661. Source: src,
  662. Target: m.Destination,
  663. ReadOnly: !m.RW,
  664. }
  665. }
  666. }
  667. volumes := []types.ServiceVolumeConfig{}
  668. for _, v := range s.Volumes {
  669. if v.Target != m.Destination || v.Source != "" {
  670. volumes = append(volumes, v)
  671. continue
  672. }
  673. // inherit previous container's anonymous volume
  674. mounts[m.Destination] = mount.Mount{
  675. Type: m.Type,
  676. Source: src,
  677. Target: m.Destination,
  678. ReadOnly: !m.RW,
  679. }
  680. }
  681. s.Volumes = volumes
  682. }
  683. }
  684. mounts, err := fillBindMounts(p, s, mounts)
  685. if err != nil {
  686. return nil, err
  687. }
  688. values := make([]mount.Mount, 0, len(mounts))
  689. for _, v := range mounts {
  690. values = append(values, v)
  691. }
  692. return values, nil
  693. }
  694. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  695. for _, v := range s.Volumes {
  696. bindMount, err := buildMount(p, v)
  697. if err != nil {
  698. return nil, err
  699. }
  700. m[bindMount.Target] = bindMount
  701. }
  702. secrets, err := buildContainerSecretMounts(p, s)
  703. if err != nil {
  704. return nil, err
  705. }
  706. for _, s := range secrets {
  707. if _, found := m[s.Target]; found {
  708. continue
  709. }
  710. m[s.Target] = s
  711. }
  712. configs, err := buildContainerConfigMounts(p, s)
  713. if err != nil {
  714. return nil, err
  715. }
  716. for _, c := range configs {
  717. if _, found := m[c.Target]; found {
  718. continue
  719. }
  720. m[c.Target] = c
  721. }
  722. return m, nil
  723. }
  724. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  725. var mounts = map[string]mount.Mount{}
  726. configsBaseDir := "/"
  727. for _, config := range s.Configs {
  728. target := config.Target
  729. if config.Target == "" {
  730. target = configsBaseDir + config.Source
  731. } else if !isAbsTarget(config.Target) {
  732. target = configsBaseDir + config.Target
  733. }
  734. if config.UID != "" || config.GID != "" || config.Mode != nil {
  735. logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
  736. }
  737. definedConfig := p.Configs[config.Source]
  738. if definedConfig.External {
  739. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  740. }
  741. if definedConfig.Driver != "" {
  742. return nil, errors.New("Docker Compose does not support configs.*.driver")
  743. }
  744. if definedConfig.TemplateDriver != "" {
  745. return nil, errors.New("Docker Compose does not support configs.*.template_driver")
  746. }
  747. if definedConfig.Environment != "" || definedConfig.Content != "" {
  748. continue
  749. }
  750. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  751. Type: types.VolumeTypeBind,
  752. Source: definedConfig.File,
  753. Target: target,
  754. ReadOnly: true,
  755. })
  756. if err != nil {
  757. return nil, err
  758. }
  759. mounts[target] = bindMount
  760. }
  761. values := make([]mount.Mount, 0, len(mounts))
  762. for _, v := range mounts {
  763. values = append(values, v)
  764. }
  765. return values, nil
  766. }
  767. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  768. var mounts = map[string]mount.Mount{}
  769. secretsDir := "/run/secrets/"
  770. for _, secret := range s.Secrets {
  771. target := secret.Target
  772. if secret.Target == "" {
  773. target = secretsDir + secret.Source
  774. } else if !isAbsTarget(secret.Target) {
  775. target = secretsDir + secret.Target
  776. }
  777. if secret.UID != "" || secret.GID != "" || secret.Mode != nil {
  778. logrus.Warn("secrets `uid`, `gid` and `mode` are not supported, they will be ignored")
  779. }
  780. definedSecret := p.Secrets[secret.Source]
  781. if definedSecret.External {
  782. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  783. }
  784. if definedSecret.Driver != "" {
  785. return nil, errors.New("Docker Compose does not support secrets.*.driver")
  786. }
  787. if definedSecret.TemplateDriver != "" {
  788. return nil, errors.New("Docker Compose does not support secrets.*.template_driver")
  789. }
  790. if definedSecret.Environment != "" {
  791. continue
  792. }
  793. mnt, err := buildMount(p, types.ServiceVolumeConfig{
  794. Type: types.VolumeTypeBind,
  795. Source: definedSecret.File,
  796. Target: target,
  797. ReadOnly: true,
  798. })
  799. if err != nil {
  800. return nil, err
  801. }
  802. mounts[target] = mnt
  803. }
  804. values := make([]mount.Mount, 0, len(mounts))
  805. for _, v := range mounts {
  806. values = append(values, v)
  807. }
  808. return values, nil
  809. }
  810. func isAbsTarget(p string) bool {
  811. return isUnixAbs(p) || isWindowsAbs(p)
  812. }
  813. func isUnixAbs(p string) bool {
  814. return strings.HasPrefix(p, "/")
  815. }
  816. func isWindowsAbs(p string) bool {
  817. if strings.HasPrefix(p, "\\\\") {
  818. return true
  819. }
  820. if len(p) > 2 && p[1] == ':' {
  821. return p[2] == '\\'
  822. }
  823. return false
  824. }
  825. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  826. source := volume.Source
  827. // on windows, filepath.IsAbs(source) is false for unix style abs path like /var/run/docker.sock.
  828. // do not replace these with filepath.Abs(source) that will include a default drive.
  829. if volume.Type == types.VolumeTypeBind && !filepath.IsAbs(source) && !strings.HasPrefix(source, "/") {
  830. // volume source has already been prefixed with workdir if required, by compose-go project loader
  831. var err error
  832. source, err = filepath.Abs(source)
  833. if err != nil {
  834. return mount.Mount{}, err
  835. }
  836. }
  837. if volume.Type == types.VolumeTypeVolume {
  838. if volume.Source != "" {
  839. pVolume, ok := project.Volumes[volume.Source]
  840. if ok {
  841. source = pVolume.Name
  842. }
  843. }
  844. }
  845. bind, vol, tmpfs := buildMountOptions(project, volume)
  846. volume.Target = path.Clean(volume.Target)
  847. if bind != nil {
  848. volume.Type = types.VolumeTypeBind
  849. }
  850. return mount.Mount{
  851. Type: mount.Type(volume.Type),
  852. Source: source,
  853. Target: volume.Target,
  854. ReadOnly: volume.ReadOnly,
  855. Consistency: mount.Consistency(volume.Consistency),
  856. BindOptions: bind,
  857. VolumeOptions: vol,
  858. TmpfsOptions: tmpfs,
  859. }, nil
  860. }
  861. func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
  862. switch volume.Type {
  863. case "bind":
  864. if volume.Volume != nil {
  865. logrus.Warnf("mount of type `bind` should not define `volume` option")
  866. }
  867. if volume.Tmpfs != nil {
  868. logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
  869. }
  870. return buildBindOption(volume.Bind), nil, nil
  871. case "volume":
  872. if volume.Bind != nil {
  873. logrus.Warnf("mount of type `volume` should not define `bind` option")
  874. }
  875. if volume.Tmpfs != nil {
  876. logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
  877. }
  878. if v, ok := project.Volumes[volume.Source]; ok && v.DriverOpts["o"] == types.VolumeTypeBind {
  879. return buildBindOption(&types.ServiceVolumeBind{
  880. CreateHostPath: true,
  881. }), nil, nil
  882. }
  883. return nil, buildVolumeOptions(volume.Volume), nil
  884. case "tmpfs":
  885. if volume.Bind != nil {
  886. logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
  887. }
  888. if volume.Volume != nil {
  889. logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
  890. }
  891. return nil, nil, buildTmpfsOptions(volume.Tmpfs)
  892. }
  893. return nil, nil, nil
  894. }
  895. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  896. if bind == nil {
  897. return nil
  898. }
  899. return &mount.BindOptions{
  900. Propagation: mount.Propagation(bind.Propagation),
  901. // NonRecursive: false, FIXME missing from model ?
  902. }
  903. }
  904. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  905. if vol == nil {
  906. return nil
  907. }
  908. return &mount.VolumeOptions{
  909. NoCopy: vol.NoCopy,
  910. // Labels: , // FIXME missing from model ?
  911. // DriverConfig: , // FIXME missing from model ?
  912. }
  913. }
  914. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  915. if tmpfs == nil {
  916. return nil
  917. }
  918. return &mount.TmpfsOptions{
  919. SizeBytes: int64(tmpfs.Size),
  920. Mode: os.FileMode(tmpfs.Mode),
  921. }
  922. }
  923. func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
  924. if n.External {
  925. return s.resolveExternalNetwork(ctx, n)
  926. }
  927. err := s.resolveOrCreateNetwork(ctx, n)
  928. if errdefs.IsConflict(err) {
  929. // Maybe another execution of `docker compose up|run` created same network
  930. // let's retry once
  931. return s.resolveOrCreateNetwork(ctx, n)
  932. }
  933. return err
  934. }
  935. func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
  936. expectedNetworkLabel := n.Labels[api.NetworkLabel]
  937. expectedProjectLabel := n.Labels[api.ProjectLabel]
  938. // First, try to find a unique network matching by name or ID
  939. inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  940. if err == nil {
  941. // NetworkInspect will match on ID prefix, so double check we get the expected one
  942. // as looking for network named `db` we could erroneously matched network ID `db9086999caf`
  943. if inspect.Name == n.Name || inspect.ID == n.Name {
  944. p, ok := inspect.Labels[api.ProjectLabel]
  945. if !ok {
  946. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  947. "Set `external: true` to use an existing network", n.Name)
  948. } else if p != expectedProjectLabel {
  949. logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
  950. "Set `external: true` to use an existing network", n.Name, expectedProjectLabel)
  951. }
  952. if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
  953. return fmt.Errorf("network %s was found but has incorrect label %s set to %q", n.Name, api.NetworkLabel, inspect.Labels[api.NetworkLabel])
  954. }
  955. return nil
  956. }
  957. }
  958. // ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
  959. // Either not found, or name is ambiguous - use NetworkList to list by name
  960. networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
  961. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  962. })
  963. if err != nil {
  964. return err
  965. }
  966. // NetworkList Matches all or part of a network name, so we have to filter for a strict match
  967. networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
  968. return net.Name == n.Name
  969. })
  970. for _, net := range networks {
  971. if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
  972. net.Labels[api.NetworkLabel] == expectedNetworkLabel {
  973. return nil
  974. }
  975. }
  976. // we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
  977. // scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
  978. // prevents to create another one.
  979. if len(networks) > 0 {
  980. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  981. "Set `external: true` to use an existing network", n.Name)
  982. return nil
  983. }
  984. var ipam *network.IPAM
  985. if n.Ipam.Config != nil {
  986. var config []network.IPAMConfig
  987. for _, pool := range n.Ipam.Config {
  988. config = append(config, network.IPAMConfig{
  989. Subnet: pool.Subnet,
  990. IPRange: pool.IPRange,
  991. Gateway: pool.Gateway,
  992. AuxAddress: pool.AuxiliaryAddresses,
  993. })
  994. }
  995. ipam = &network.IPAM{
  996. Driver: n.Ipam.Driver,
  997. Config: config,
  998. }
  999. }
  1000. createOpts := moby.NetworkCreate{
  1001. CheckDuplicate: true,
  1002. Labels: n.Labels,
  1003. Driver: n.Driver,
  1004. Options: n.DriverOpts,
  1005. Internal: n.Internal,
  1006. Attachable: n.Attachable,
  1007. IPAM: ipam,
  1008. EnableIPv6: n.EnableIPv6,
  1009. }
  1010. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  1011. createOpts.IPAM = &network.IPAM{}
  1012. }
  1013. if n.Ipam.Driver != "" {
  1014. createOpts.IPAM.Driver = n.Ipam.Driver
  1015. }
  1016. for _, ipamConfig := range n.Ipam.Config {
  1017. config := network.IPAMConfig{
  1018. Subnet: ipamConfig.Subnet,
  1019. IPRange: ipamConfig.IPRange,
  1020. Gateway: ipamConfig.Gateway,
  1021. AuxAddress: ipamConfig.AuxiliaryAddresses,
  1022. }
  1023. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  1024. }
  1025. networkEventName := fmt.Sprintf("Network %s", n.Name)
  1026. w := progress.ContextWriter(ctx)
  1027. w.Event(progress.CreatingEvent(networkEventName))
  1028. _, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
  1029. if err != nil {
  1030. w.Event(progress.ErrorEvent(networkEventName))
  1031. return fmt.Errorf("failed to create network %s: %w", n.Name, err)
  1032. }
  1033. w.Event(progress.CreatedEvent(networkEventName))
  1034. return nil
  1035. }
  1036. func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
  1037. // NetworkInspect will match on ID prefix, so NetworkList with a name
  1038. // filter is used to look for an exact match to prevent e.g. a network
  1039. // named `db` from getting erroneously matched to a network with an ID
  1040. // like `db9086999caf`
  1041. networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
  1042. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  1043. })
  1044. if err != nil {
  1045. return err
  1046. }
  1047. if len(networks) == 0 {
  1048. // in this instance, n.Name is really an ID
  1049. sn, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
  1050. if err != nil {
  1051. return err
  1052. }
  1053. networks = append(networks, sn)
  1054. }
  1055. // NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
  1056. networks = utils.Filter(networks, func(net moby.NetworkResource) bool {
  1057. // later in this function, the name is changed the to ID.
  1058. // this function is called during the rebuild stage of `compose watch`.
  1059. // we still require just one network back, but we need to run the search on the ID
  1060. return net.Name == n.Name || net.ID == n.Name
  1061. })
  1062. switch len(networks) {
  1063. case 1:
  1064. n.Name = networks[0].ID
  1065. return nil
  1066. case 0:
  1067. if n.Driver == "overlay" {
  1068. // Swarm nodes do not register overlay networks that were
  1069. // created on a different node unless they're in use.
  1070. // Here we assume `driver` is relevant for a network we don't manage
  1071. // which is a non-sense, but this is our legacy ¯\(ツ)/¯
  1072. // networkAttach will later fail anyway if network actually doesn't exists
  1073. enabled, err := s.isSWarmEnabled(ctx)
  1074. if err != nil {
  1075. return err
  1076. }
  1077. if enabled {
  1078. return nil
  1079. }
  1080. }
  1081. return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  1082. default:
  1083. return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
  1084. }
  1085. }
  1086. func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
  1087. inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
  1088. if err != nil {
  1089. if !errdefs.IsNotFound(err) {
  1090. return err
  1091. }
  1092. if volume.External {
  1093. return fmt.Errorf("external volume %q not found", volume.Name)
  1094. }
  1095. err := s.createVolume(ctx, volume)
  1096. return err
  1097. }
  1098. if volume.External {
  1099. return nil
  1100. }
  1101. // Volume exists with name, but let's double-check this is the expected one
  1102. p, ok := inspected.Labels[api.ProjectLabel]
  1103. if !ok {
  1104. logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
  1105. }
  1106. if ok && p != project {
  1107. 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)
  1108. }
  1109. return nil
  1110. }
  1111. func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
  1112. eventName := fmt.Sprintf("Volume %q", volume.Name)
  1113. w := progress.ContextWriter(ctx)
  1114. w.Event(progress.CreatingEvent(eventName))
  1115. _, err := s.apiClient().VolumeCreate(ctx, volume_api.CreateOptions{
  1116. Labels: volume.Labels,
  1117. Name: volume.Name,
  1118. Driver: volume.Driver,
  1119. DriverOpts: volume.DriverOpts,
  1120. })
  1121. if err != nil {
  1122. w.Event(progress.ErrorEvent(eventName))
  1123. return err
  1124. }
  1125. w.Event(progress.CreatedEvent(eventName))
  1126. return nil
  1127. }