create.go 36 KB

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