create.go 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646
  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/filepath"
  22. "slices"
  23. "strconv"
  24. "strings"
  25. "github.com/compose-spec/compose-go/v2/paths"
  26. "github.com/compose-spec/compose-go/v2/types"
  27. cerrdefs "github.com/containerd/errdefs"
  28. "github.com/docker/docker/api/types/blkiodev"
  29. "github.com/docker/docker/api/types/container"
  30. "github.com/docker/docker/api/types/filters"
  31. "github.com/docker/docker/api/types/mount"
  32. "github.com/docker/docker/api/types/network"
  33. "github.com/docker/docker/api/types/versions"
  34. volumetypes "github.com/docker/docker/api/types/volume"
  35. "github.com/docker/go-connections/nat"
  36. "github.com/sirupsen/logrus"
  37. cdi "tags.cncf.io/container-device-interface/pkg/parser"
  38. "github.com/docker/compose/v2/pkg/api"
  39. "github.com/docker/compose/v2/pkg/progress"
  40. "github.com/docker/compose/v2/pkg/prompt"
  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. err := project.CheckContainerNameUnicity()
  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. err = s.ensureModels(ctx, project, options.QuietPull)
  72. if err != nil {
  73. return err
  74. }
  75. prepareNetworks(project)
  76. networks, err := s.ensureNetworks(ctx, project)
  77. if err != nil {
  78. return err
  79. }
  80. volumes, err := s.ensureProjectVolumes(ctx, project, options.AssumeYes)
  81. if err != nil {
  82. return err
  83. }
  84. var observedState Containers
  85. observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
  86. if err != nil {
  87. return err
  88. }
  89. orphans := observedState.filter(isOrphaned(project))
  90. if len(orphans) > 0 && !options.IgnoreOrphans {
  91. if options.RemoveOrphans {
  92. err := s.removeContainers(ctx, orphans, nil, 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. // Temporary implementation of use_api_socket until we get actual support inside docker engine
  104. project, err = s.useAPISocket(project)
  105. if err != nil {
  106. return err
  107. }
  108. return newConvergence(options.Services, observedState, networks, volumes, s).apply(ctx, project, options)
  109. }
  110. func prepareNetworks(project *types.Project) {
  111. for k, nw := range project.Networks {
  112. nw.CustomLabels = nw.CustomLabels.
  113. Add(api.NetworkLabel, k).
  114. Add(api.ProjectLabel, project.Name).
  115. Add(api.VersionLabel, api.ComposeVersion)
  116. project.Networks[k] = nw
  117. }
  118. }
  119. func (s *composeService) ensureNetworks(ctx context.Context, project *types.Project) (map[string]string, error) {
  120. networks := map[string]string{}
  121. for name, nw := range project.Networks {
  122. id, err := s.ensureNetwork(ctx, project, name, &nw)
  123. if err != nil {
  124. return nil, err
  125. }
  126. networks[name] = id
  127. project.Networks[name] = nw
  128. }
  129. return networks, nil
  130. }
  131. func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project, assumeYes bool) (map[string]string, error) {
  132. ids := map[string]string{}
  133. for k, volume := range project.Volumes {
  134. volume.CustomLabels = volume.CustomLabels.Add(api.VolumeLabel, k)
  135. volume.CustomLabels = volume.CustomLabels.Add(api.ProjectLabel, project.Name)
  136. volume.CustomLabels = volume.CustomLabels.Add(api.VersionLabel, api.ComposeVersion)
  137. id, err := s.ensureVolume(ctx, k, volume, project, assumeYes)
  138. if err != nil {
  139. return nil, err
  140. }
  141. ids[k] = id
  142. }
  143. return ids, nil
  144. }
  145. //nolint:gocyclo
  146. func (s *composeService) getCreateConfigs(ctx context.Context,
  147. p *types.Project,
  148. service types.ServiceConfig,
  149. number int,
  150. inherit *container.Summary,
  151. opts createOptions,
  152. ) (createConfigs, error) {
  153. labels, err := s.prepareLabels(opts.Labels, service, number)
  154. if err != nil {
  155. return createConfigs{}, err
  156. }
  157. var runCmd, entrypoint []string
  158. if service.Command != nil {
  159. runCmd = service.Command
  160. }
  161. if service.Entrypoint != nil {
  162. entrypoint = service.Entrypoint
  163. }
  164. var (
  165. tty = service.Tty
  166. stdinOpen = service.StdinOpen
  167. )
  168. proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
  169. env := proxyConfig.OverrideBy(service.Environment)
  170. var mainNwName string
  171. var mainNw *types.ServiceNetworkConfig
  172. if len(service.Networks) > 0 {
  173. mainNwName = service.NetworksByPriority()[0]
  174. mainNw = service.Networks[mainNwName]
  175. }
  176. macAddress, err := s.prepareContainerMACAddress(ctx, service, mainNw, mainNwName)
  177. if err != nil {
  178. return createConfigs{}, err
  179. }
  180. healthcheck, err := s.ToMobyHealthCheck(ctx, service.HealthCheck)
  181. if err != nil {
  182. return createConfigs{}, err
  183. }
  184. containerConfig := container.Config{
  185. Hostname: service.Hostname,
  186. Domainname: service.DomainName,
  187. User: service.User,
  188. ExposedPorts: buildContainerPorts(service),
  189. Tty: tty,
  190. OpenStdin: stdinOpen,
  191. StdinOnce: opts.AttachStdin && stdinOpen,
  192. AttachStdin: opts.AttachStdin,
  193. AttachStderr: true,
  194. AttachStdout: true,
  195. Cmd: runCmd,
  196. Image: api.GetImageNameOrDefault(service, p.Name),
  197. WorkingDir: service.WorkingDir,
  198. Entrypoint: entrypoint,
  199. NetworkDisabled: service.NetworkMode == "disabled",
  200. MacAddress: macAddress, // Field is deprecated since API v1.44, but kept for compatibility with older API versions.
  201. Labels: labels,
  202. StopSignal: service.StopSignal,
  203. Env: ToMobyEnv(env),
  204. Healthcheck: healthcheck,
  205. StopTimeout: ToSeconds(service.StopGracePeriod),
  206. } // VOLUMES/MOUNTS/FILESYSTEMS
  207. tmpfs := map[string]string{}
  208. for _, t := range service.Tmpfs {
  209. if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
  210. tmpfs[arr[0]] = arr[1]
  211. } else {
  212. tmpfs[arr[0]] = ""
  213. }
  214. }
  215. binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
  216. if err != nil {
  217. return createConfigs{}, err
  218. }
  219. // NETWORKING
  220. links, err := s.getLinks(ctx, p.Name, service, number)
  221. if err != nil {
  222. return createConfigs{}, err
  223. }
  224. apiVersion, err := s.RuntimeVersion(ctx)
  225. if err != nil {
  226. return createConfigs{}, err
  227. }
  228. networkMode, networkingConfig, err := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases, apiVersion)
  229. if err != nil {
  230. return createConfigs{}, err
  231. }
  232. portBindings := buildContainerPortBindingOptions(service)
  233. // MISC
  234. resources := getDeployResources(service)
  235. var logConfig container.LogConfig
  236. if service.Logging != nil {
  237. logConfig = container.LogConfig{
  238. Type: service.Logging.Driver,
  239. Config: service.Logging.Options,
  240. }
  241. }
  242. securityOpts, unconfined, err := parseSecurityOpts(p, service.SecurityOpt)
  243. if err != nil {
  244. return createConfigs{}, err
  245. }
  246. hostConfig := container.HostConfig{
  247. AutoRemove: opts.AutoRemove,
  248. Annotations: service.Annotations,
  249. Binds: binds,
  250. Mounts: mounts,
  251. CapAdd: service.CapAdd,
  252. CapDrop: service.CapDrop,
  253. NetworkMode: networkMode,
  254. Init: service.Init,
  255. IpcMode: container.IpcMode(service.Ipc),
  256. CgroupnsMode: container.CgroupnsMode(service.Cgroup),
  257. ReadonlyRootfs: service.ReadOnly,
  258. RestartPolicy: getRestartPolicy(service),
  259. ShmSize: int64(service.ShmSize),
  260. Sysctls: service.Sysctls,
  261. PortBindings: portBindings,
  262. Resources: resources,
  263. VolumeDriver: service.VolumeDriver,
  264. VolumesFrom: service.VolumesFrom,
  265. DNS: service.DNS,
  266. DNSSearch: service.DNSSearch,
  267. DNSOptions: service.DNSOpts,
  268. ExtraHosts: service.ExtraHosts.AsList(":"),
  269. SecurityOpt: securityOpts,
  270. StorageOpt: service.StorageOpt,
  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. // prepareContainerMACAddress handles the service-level mac_address field and the newer mac_address field added to service
  296. // network config. This newer field is only compatible with the Engine API v1.44 (and onwards), and this API version
  297. // also deprecates the container-wide mac_address field. Thus, this method will validate service config and mutate the
  298. // passed mainNw to provide backward-compatibility whenever possible.
  299. //
  300. // It returns the container-wide MAC address, but this value will be kept empty for newer API versions.
  301. func (s *composeService) prepareContainerMACAddress(ctx context.Context, service types.ServiceConfig, mainNw *types.ServiceNetworkConfig, nwName string) (string, error) {
  302. version, err := s.RuntimeVersion(ctx)
  303. if err != nil {
  304. return "", err
  305. }
  306. // Engine API 1.44 added support for endpoint-specific MAC address and now returns a warning when a MAC address is
  307. // set in container.Config. Thus, we have to jump through a number of hoops:
  308. //
  309. // 1. Top-level mac_address and main endpoint's MAC address should be the same ;
  310. // 2. If supported by the API, top-level mac_address should be migrated to the main endpoint and container.Config
  311. // should be kept empty ;
  312. // 3. Otherwise, the endpoint mac_address should be set in container.Config and no other endpoint-specific
  313. // mac_address can be specified. If that's the case, use top-level mac_address ;
  314. //
  315. // After that, if an endpoint mac_address is set, it's either user-defined or migrated by the code below, so
  316. // there's no need to check for API version in defaultNetworkSettings.
  317. macAddress := service.MacAddress
  318. if macAddress != "" && mainNw != nil && mainNw.MacAddress != "" && mainNw.MacAddress != macAddress {
  319. return "", fmt.Errorf("the service-level mac_address should have the same value as network %s", nwName)
  320. }
  321. if versions.GreaterThanOrEqualTo(version, "1.44") {
  322. if mainNw != nil && mainNw.MacAddress == "" {
  323. mainNw.MacAddress = macAddress
  324. }
  325. macAddress = ""
  326. } else if len(service.Networks) > 0 {
  327. var withMacAddress []string
  328. for nwName, nw := range service.Networks {
  329. if nw != nil && nw.MacAddress != "" {
  330. withMacAddress = append(withMacAddress, nwName)
  331. }
  332. }
  333. if len(withMacAddress) > 1 {
  334. return "", fmt.Errorf("a MAC address is specified for multiple networks (%s), but this feature requires Docker Engine v25 or later", strings.Join(withMacAddress, ", "))
  335. }
  336. if mainNw != nil && mainNw.MacAddress != "" {
  337. macAddress = mainNw.MacAddress
  338. }
  339. }
  340. return macAddress, nil
  341. }
  342. func getAliases(project *types.Project, service types.ServiceConfig, serviceIndex int, cfg *types.ServiceNetworkConfig, useNetworkAliases bool) []string {
  343. aliases := []string{getContainerName(project.Name, service, serviceIndex)}
  344. if useNetworkAliases {
  345. aliases = append(aliases, service.Name)
  346. if cfg != nil {
  347. aliases = append(aliases, cfg.Aliases...)
  348. }
  349. }
  350. return aliases
  351. }
  352. func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings {
  353. const ifname = "com.docker.network.endpoint.ifname"
  354. config := service.Networks[networkKey]
  355. var ipam *network.EndpointIPAMConfig
  356. var (
  357. ipv4Address string
  358. ipv6Address string
  359. macAddress string
  360. driverOpts types.Options
  361. gwPriority int
  362. )
  363. if config != nil {
  364. ipv4Address = config.Ipv4Address
  365. ipv6Address = config.Ipv6Address
  366. ipam = &network.EndpointIPAMConfig{
  367. IPv4Address: ipv4Address,
  368. IPv6Address: ipv6Address,
  369. LinkLocalIPs: config.LinkLocalIPs,
  370. }
  371. macAddress = config.MacAddress
  372. driverOpts = config.DriverOpts
  373. if config.InterfaceName != "" {
  374. if driverOpts == nil {
  375. driverOpts = map[string]string{}
  376. }
  377. if name, ok := driverOpts[ifname]; ok && name != config.InterfaceName {
  378. logrus.Warnf("ignoring services.%s.networks.%s.interface_name as %s driver_opts is already declared", service.Name, networkKey, ifname)
  379. }
  380. driverOpts[ifname] = config.InterfaceName
  381. }
  382. gwPriority = config.GatewayPriority
  383. }
  384. return &network.EndpointSettings{
  385. Aliases: getAliases(p, service, serviceIndex, config, useNetworkAliases),
  386. Links: links,
  387. IPAddress: ipv4Address,
  388. IPv6Gateway: ipv6Address,
  389. IPAMConfig: ipam,
  390. MacAddress: macAddress,
  391. DriverOpts: driverOpts,
  392. GwPriority: gwPriority,
  393. }
  394. }
  395. // copy/pasted from https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 + RelativePath
  396. // TODO find so way to share this code with docker/cli
  397. func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool, error) {
  398. var (
  399. unconfined bool
  400. parsed []string
  401. )
  402. for _, opt := range securityOpts {
  403. if opt == "systempaths=unconfined" {
  404. unconfined = true
  405. continue
  406. }
  407. con := strings.SplitN(opt, "=", 2)
  408. if len(con) == 1 && con[0] != "no-new-privileges" {
  409. if strings.Contains(opt, ":") {
  410. con = strings.SplitN(opt, ":", 2)
  411. } else {
  412. return securityOpts, false, fmt.Errorf("invalid security-opt: %q", opt)
  413. }
  414. }
  415. if con[0] == "seccomp" && con[1] != "unconfined" && con[1] != "builtin" {
  416. f, err := os.ReadFile(p.RelativePath(con[1]))
  417. if err != nil {
  418. return securityOpts, false, fmt.Errorf("opening seccomp profile (%s) failed: %w", con[1], err)
  419. }
  420. b := bytes.NewBuffer(nil)
  421. if err := json.Compact(b, f); err != nil {
  422. return securityOpts, false, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", con[1], err)
  423. }
  424. parsed = append(parsed, fmt.Sprintf("seccomp=%s", b.Bytes()))
  425. } else {
  426. parsed = append(parsed, opt)
  427. }
  428. }
  429. return parsed, unconfined, nil
  430. }
  431. func (s *composeService) prepareLabels(labels types.Labels, service types.ServiceConfig, number int) (map[string]string, error) {
  432. hash, err := ServiceHash(service)
  433. if err != nil {
  434. return nil, err
  435. }
  436. labels[api.ConfigHashLabel] = hash
  437. if number > 0 {
  438. // One-off containers are not indexed
  439. labels[api.ContainerNumberLabel] = strconv.Itoa(number)
  440. }
  441. var dependencies []string
  442. for s, d := range service.DependsOn {
  443. dependencies = append(dependencies, fmt.Sprintf("%s:%s:%t", s, d.Condition, d.Restart))
  444. }
  445. labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
  446. return labels, nil
  447. }
  448. // defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable).
  449. func defaultNetworkSettings(project *types.Project,
  450. service types.ServiceConfig, serviceIndex int,
  451. links []string, useNetworkAliases bool,
  452. version string,
  453. ) (container.NetworkMode, *network.NetworkingConfig, error) {
  454. if service.NetworkMode != "" {
  455. return container.NetworkMode(service.NetworkMode), nil, nil
  456. }
  457. if len(project.Networks) == 0 {
  458. return "none", nil, nil
  459. }
  460. var primaryNetworkKey string
  461. if len(service.Networks) > 0 {
  462. primaryNetworkKey = service.NetworksByPriority()[0]
  463. } else {
  464. primaryNetworkKey = "default"
  465. }
  466. primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name
  467. primaryNetworkEndpoint := createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases)
  468. endpointsConfig := map[string]*network.EndpointSettings{}
  469. // Starting from API version 1.44, the Engine will take several EndpointsConfigs
  470. // so we can pass all the extra networks we want the container to be connected to
  471. // in the network configuration instead of connecting the container to each extra
  472. // network individually after creation.
  473. if versions.GreaterThanOrEqualTo(version, "1.44") {
  474. if len(service.Networks) > 1 {
  475. serviceNetworks := service.NetworksByPriority()
  476. for _, networkKey := range serviceNetworks[1:] {
  477. mobyNetworkName := project.Networks[networkKey].Name
  478. epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
  479. endpointsConfig[mobyNetworkName] = epSettings
  480. }
  481. }
  482. if primaryNetworkEndpoint.MacAddress == "" {
  483. primaryNetworkEndpoint.MacAddress = service.MacAddress
  484. }
  485. }
  486. if versions.LessThan(version, "1.49") {
  487. for _, config := range service.Networks {
  488. if config != nil && config.InterfaceName != "" {
  489. return "", nil, fmt.Errorf("interface_name requires Docker Engine v28.1 or later")
  490. }
  491. }
  492. }
  493. endpointsConfig[primaryNetworkMobyNetworkName] = primaryNetworkEndpoint
  494. networkConfig := &network.NetworkingConfig{
  495. EndpointsConfig: endpointsConfig,
  496. }
  497. // From the Engine API docs:
  498. // > Supported standard values are: bridge, host, none, and container:<name|id>.
  499. // > Any other value is taken as a custom network's name to which this container should connect to.
  500. return container.NetworkMode(primaryNetworkMobyNetworkName), networkConfig, nil
  501. }
  502. func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
  503. var restart container.RestartPolicy
  504. if service.Restart != "" {
  505. split := strings.Split(service.Restart, ":")
  506. var attempts int
  507. if len(split) > 1 {
  508. attempts, _ = strconv.Atoi(split[1])
  509. }
  510. restart = container.RestartPolicy{
  511. Name: mapRestartPolicyCondition(split[0]),
  512. MaximumRetryCount: attempts,
  513. }
  514. }
  515. if service.Deploy != nil && service.Deploy.RestartPolicy != nil {
  516. policy := *service.Deploy.RestartPolicy
  517. var attempts int
  518. if policy.MaxAttempts != nil {
  519. attempts = int(*policy.MaxAttempts)
  520. }
  521. restart = container.RestartPolicy{
  522. Name: mapRestartPolicyCondition(policy.Condition),
  523. MaximumRetryCount: attempts,
  524. }
  525. }
  526. return restart
  527. }
  528. func mapRestartPolicyCondition(condition string) container.RestartPolicyMode {
  529. // map definitions of deploy.restart_policy to engine definitions
  530. switch condition {
  531. case "none", "no":
  532. return container.RestartPolicyDisabled
  533. case "on-failure":
  534. return container.RestartPolicyOnFailure
  535. case "unless-stopped":
  536. return container.RestartPolicyUnlessStopped
  537. case "any", "always":
  538. return container.RestartPolicyAlways
  539. default:
  540. return container.RestartPolicyMode(condition)
  541. }
  542. }
  543. func getDeployResources(s types.ServiceConfig) container.Resources {
  544. var swappiness *int64
  545. if s.MemSwappiness != 0 {
  546. val := int64(s.MemSwappiness)
  547. swappiness = &val
  548. }
  549. resources := container.Resources{
  550. CgroupParent: s.CgroupParent,
  551. Memory: int64(s.MemLimit),
  552. MemorySwap: int64(s.MemSwapLimit),
  553. MemorySwappiness: swappiness,
  554. MemoryReservation: int64(s.MemReservation),
  555. OomKillDisable: &s.OomKillDisable,
  556. CPUCount: s.CPUCount,
  557. CPUPeriod: s.CPUPeriod,
  558. CPUQuota: s.CPUQuota,
  559. CPURealtimePeriod: s.CPURTPeriod,
  560. CPURealtimeRuntime: s.CPURTRuntime,
  561. CPUShares: s.CPUShares,
  562. NanoCPUs: int64(s.CPUS * 1e9),
  563. CPUPercent: int64(s.CPUPercent * 100),
  564. CpusetCpus: s.CPUSet,
  565. DeviceCgroupRules: s.DeviceCgroupRules,
  566. }
  567. if s.PidsLimit != 0 {
  568. resources.PidsLimit = &s.PidsLimit
  569. }
  570. setBlkio(s.BlkioConfig, &resources)
  571. if s.Deploy != nil {
  572. setLimits(s.Deploy.Resources.Limits, &resources)
  573. setReservations(s.Deploy.Resources.Reservations, &resources)
  574. }
  575. var cdiDeviceNames []string
  576. for _, device := range s.Devices {
  577. if device.Source == device.Target && cdi.IsQualifiedName(device.Source) {
  578. cdiDeviceNames = append(cdiDeviceNames, device.Source)
  579. continue
  580. }
  581. resources.Devices = append(resources.Devices, container.DeviceMapping{
  582. PathOnHost: device.Source,
  583. PathInContainer: device.Target,
  584. CgroupPermissions: device.Permissions,
  585. })
  586. }
  587. if len(cdiDeviceNames) > 0 {
  588. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  589. Driver: "cdi",
  590. DeviceIDs: cdiDeviceNames,
  591. })
  592. }
  593. for _, gpus := range s.Gpus {
  594. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  595. Driver: gpus.Driver,
  596. Count: int(gpus.Count),
  597. DeviceIDs: gpus.IDs,
  598. Capabilities: [][]string{append(gpus.Capabilities, "gpu")},
  599. Options: gpus.Options,
  600. })
  601. }
  602. ulimits := toUlimits(s.Ulimits)
  603. resources.Ulimits = ulimits
  604. return resources
  605. }
  606. func toUlimits(m map[string]*types.UlimitsConfig) []*container.Ulimit {
  607. var ulimits []*container.Ulimit
  608. for name, u := range m {
  609. soft := u.Single
  610. if u.Soft != 0 {
  611. soft = u.Soft
  612. }
  613. hard := u.Single
  614. if u.Hard != 0 {
  615. hard = u.Hard
  616. }
  617. ulimits = append(ulimits, &container.Ulimit{
  618. Name: name,
  619. Hard: int64(hard),
  620. Soft: int64(soft),
  621. })
  622. }
  623. return ulimits
  624. }
  625. func setReservations(reservations *types.Resource, resources *container.Resources) {
  626. if reservations == nil {
  627. return
  628. }
  629. // Cpu reservation is a swarm option and PIDs is only a limit
  630. // So we only need to map memory reservation and devices
  631. if reservations.MemoryBytes != 0 {
  632. resources.MemoryReservation = int64(reservations.MemoryBytes)
  633. }
  634. for _, device := range reservations.Devices {
  635. resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
  636. Capabilities: [][]string{device.Capabilities},
  637. Count: int(device.Count),
  638. DeviceIDs: device.IDs,
  639. Driver: device.Driver,
  640. Options: device.Options,
  641. })
  642. }
  643. }
  644. func setLimits(limits *types.Resource, resources *container.Resources) {
  645. if limits == nil {
  646. return
  647. }
  648. if limits.MemoryBytes != 0 {
  649. resources.Memory = int64(limits.MemoryBytes)
  650. }
  651. if limits.NanoCPUs != 0 {
  652. resources.NanoCPUs = int64(limits.NanoCPUs * 1e9)
  653. }
  654. if limits.Pids > 0 {
  655. resources.PidsLimit = &limits.Pids
  656. }
  657. }
  658. func setBlkio(blkio *types.BlkioConfig, resources *container.Resources) {
  659. if blkio == nil {
  660. return
  661. }
  662. resources.BlkioWeight = blkio.Weight
  663. for _, b := range blkio.WeightDevice {
  664. resources.BlkioWeightDevice = append(resources.BlkioWeightDevice, &blkiodev.WeightDevice{
  665. Path: b.Path,
  666. Weight: b.Weight,
  667. })
  668. }
  669. for _, b := range blkio.DeviceReadBps {
  670. resources.BlkioDeviceReadBps = append(resources.BlkioDeviceReadBps, &blkiodev.ThrottleDevice{
  671. Path: b.Path,
  672. Rate: uint64(b.Rate),
  673. })
  674. }
  675. for _, b := range blkio.DeviceReadIOps {
  676. resources.BlkioDeviceReadIOps = append(resources.BlkioDeviceReadIOps, &blkiodev.ThrottleDevice{
  677. Path: b.Path,
  678. Rate: uint64(b.Rate),
  679. })
  680. }
  681. for _, b := range blkio.DeviceWriteBps {
  682. resources.BlkioDeviceWriteBps = append(resources.BlkioDeviceWriteBps, &blkiodev.ThrottleDevice{
  683. Path: b.Path,
  684. Rate: uint64(b.Rate),
  685. })
  686. }
  687. for _, b := range blkio.DeviceWriteIOps {
  688. resources.BlkioDeviceWriteIOps = append(resources.BlkioDeviceWriteIOps, &blkiodev.ThrottleDevice{
  689. Path: b.Path,
  690. Rate: uint64(b.Rate),
  691. })
  692. }
  693. }
  694. func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
  695. ports := nat.PortSet{}
  696. for _, s := range s.Expose {
  697. p := nat.Port(s)
  698. ports[p] = struct{}{}
  699. }
  700. for _, p := range s.Ports {
  701. p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
  702. ports[p] = struct{}{}
  703. }
  704. return ports
  705. }
  706. func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
  707. bindings := nat.PortMap{}
  708. for _, port := range s.Ports {
  709. p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
  710. binding := nat.PortBinding{
  711. HostIP: port.HostIP,
  712. HostPort: port.Published,
  713. }
  714. bindings[p] = append(bindings[p], binding)
  715. }
  716. return bindings
  717. }
  718. func getDependentServiceFromMode(mode string) string {
  719. if strings.HasPrefix(
  720. mode,
  721. types.NetworkModeServicePrefix,
  722. ) {
  723. return mode[len(types.NetworkModeServicePrefix):]
  724. }
  725. return ""
  726. }
  727. func (s *composeService) buildContainerVolumes(
  728. ctx context.Context,
  729. p types.Project,
  730. service types.ServiceConfig,
  731. inherit *container.Summary,
  732. ) ([]string, []mount.Mount, error) {
  733. var mounts []mount.Mount
  734. var binds []string
  735. mountOptions, err := s.buildContainerMountOptions(ctx, p, service, inherit)
  736. if err != nil {
  737. return nil, nil, err
  738. }
  739. for _, m := range mountOptions {
  740. switch m.Type {
  741. case mount.TypeBind:
  742. // `Mount` is preferred but does not offer option to created host path if missing
  743. // so `Bind` API is used here with raw volume string
  744. // see https://github.com/moby/moby/issues/43483
  745. v := findVolumeByTarget(service.Volumes, m.Target)
  746. if v != nil {
  747. if v.Type != types.VolumeTypeBind {
  748. v.Source = m.Source
  749. }
  750. if !bindRequiresMountAPI(v.Bind) {
  751. source := m.Source
  752. if vol := findVolumeByName(p.Volumes, m.Source); vol != nil {
  753. source = m.Source
  754. }
  755. binds = append(binds, toBindString(source, v))
  756. continue
  757. }
  758. }
  759. case mount.TypeVolume:
  760. v := findVolumeByTarget(service.Volumes, m.Target)
  761. vol := findVolumeByName(p.Volumes, m.Source)
  762. if v != nil && vol != nil {
  763. // Prefer the bind API if no advanced option is used, to preserve backward compatibility
  764. if !volumeRequiresMountAPI(v.Volume) {
  765. binds = append(binds, toBindString(vol.Name, v))
  766. continue
  767. }
  768. }
  769. case mount.TypeImage:
  770. version, err := s.RuntimeVersion(ctx)
  771. if err != nil {
  772. return nil, nil, err
  773. }
  774. if versions.LessThan(version, "1.48") {
  775. return nil, nil, fmt.Errorf("volume with type=image require Docker Engine v28 or later")
  776. }
  777. }
  778. mounts = append(mounts, m)
  779. }
  780. return binds, mounts, nil
  781. }
  782. func toBindString(name string, v *types.ServiceVolumeConfig) string {
  783. access := "rw"
  784. if v.ReadOnly {
  785. access = "ro"
  786. }
  787. options := []string{access}
  788. if v.Bind != nil && v.Bind.SELinux != "" {
  789. options = append(options, v.Bind.SELinux)
  790. }
  791. if v.Bind != nil && v.Bind.Propagation != "" {
  792. options = append(options, v.Bind.Propagation)
  793. }
  794. if v.Volume != nil && v.Volume.NoCopy {
  795. options = append(options, "nocopy")
  796. }
  797. return fmt.Sprintf("%s:%s:%s", name, v.Target, strings.Join(options, ","))
  798. }
  799. func findVolumeByName(volumes types.Volumes, name string) *types.VolumeConfig {
  800. for _, vol := range volumes {
  801. if vol.Name == name {
  802. return &vol
  803. }
  804. }
  805. return nil
  806. }
  807. func findVolumeByTarget(volumes []types.ServiceVolumeConfig, target string) *types.ServiceVolumeConfig {
  808. for _, v := range volumes {
  809. if v.Target == target {
  810. return &v
  811. }
  812. }
  813. return nil
  814. }
  815. // bindRequiresMountAPI check if Bind declaration can be implemented by the plain old Bind API or uses any of the advanced
  816. // options which require use of Mount API
  817. func bindRequiresMountAPI(bind *types.ServiceVolumeBind) bool {
  818. switch {
  819. case bind == nil:
  820. return false
  821. case !bind.CreateHostPath:
  822. return true
  823. case bind.Propagation != "":
  824. return true
  825. case bind.Recursive != "":
  826. return true
  827. default:
  828. return false
  829. }
  830. }
  831. // volumeRequiresMountAPI check if Volume declaration can be implemented by the plain old Bind API or uses any of the advanced
  832. // options which require use of Mount API
  833. func volumeRequiresMountAPI(vol *types.ServiceVolumeVolume) bool {
  834. switch {
  835. case vol == nil:
  836. return false
  837. case len(vol.Labels) > 0:
  838. return true
  839. case vol.Subpath != "":
  840. return true
  841. case vol.NoCopy:
  842. return true
  843. default:
  844. return false
  845. }
  846. }
  847. func (s *composeService) buildContainerMountOptions(ctx context.Context, p types.Project, service types.ServiceConfig, inherit *container.Summary) ([]mount.Mount, error) {
  848. mounts := map[string]mount.Mount{}
  849. if inherit != nil {
  850. for _, m := range inherit.Mounts {
  851. if m.Type == "tmpfs" {
  852. continue
  853. }
  854. src := m.Source
  855. if m.Type == "volume" {
  856. src = m.Name
  857. }
  858. img, err := s.apiClient().ImageInspect(ctx, api.GetImageNameOrDefault(service, p.Name))
  859. if err != nil {
  860. return nil, err
  861. }
  862. if img.Config != nil {
  863. if _, ok := img.Config.Volumes[m.Destination]; ok {
  864. // inherit previous container's anonymous volume
  865. mounts[m.Destination] = mount.Mount{
  866. Type: m.Type,
  867. Source: src,
  868. Target: m.Destination,
  869. ReadOnly: !m.RW,
  870. }
  871. }
  872. }
  873. volumes := []types.ServiceVolumeConfig{}
  874. for _, v := range service.Volumes {
  875. if v.Target != m.Destination || v.Source != "" {
  876. volumes = append(volumes, v)
  877. continue
  878. }
  879. // inherit previous container's anonymous volume
  880. mounts[m.Destination] = mount.Mount{
  881. Type: m.Type,
  882. Source: src,
  883. Target: m.Destination,
  884. ReadOnly: !m.RW,
  885. }
  886. }
  887. service.Volumes = volumes
  888. }
  889. }
  890. mounts, err := fillBindMounts(p, service, mounts)
  891. if err != nil {
  892. return nil, err
  893. }
  894. values := make([]mount.Mount, 0, len(mounts))
  895. for _, v := range mounts {
  896. values = append(values, v)
  897. }
  898. return values, nil
  899. }
  900. func fillBindMounts(p types.Project, s types.ServiceConfig, m map[string]mount.Mount) (map[string]mount.Mount, error) {
  901. for _, v := range s.Volumes {
  902. bindMount, err := buildMount(p, v)
  903. if err != nil {
  904. return nil, err
  905. }
  906. m[bindMount.Target] = bindMount
  907. }
  908. secrets, err := buildContainerSecretMounts(p, s)
  909. if err != nil {
  910. return nil, err
  911. }
  912. for _, s := range secrets {
  913. if _, found := m[s.Target]; found {
  914. continue
  915. }
  916. m[s.Target] = s
  917. }
  918. configs, err := buildContainerConfigMounts(p, s)
  919. if err != nil {
  920. return nil, err
  921. }
  922. for _, c := range configs {
  923. if _, found := m[c.Target]; found {
  924. continue
  925. }
  926. m[c.Target] = c
  927. }
  928. return m, nil
  929. }
  930. func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  931. mounts := map[string]mount.Mount{}
  932. configsBaseDir := "/"
  933. for _, config := range s.Configs {
  934. target := config.Target
  935. if config.Target == "" {
  936. target = configsBaseDir + config.Source
  937. } else if !isAbsTarget(config.Target) {
  938. target = configsBaseDir + config.Target
  939. }
  940. definedConfig := p.Configs[config.Source]
  941. if definedConfig.External {
  942. return nil, fmt.Errorf("unsupported external config %s", definedConfig.Name)
  943. }
  944. if definedConfig.Driver != "" {
  945. return nil, errors.New("Docker Compose does not support configs.*.driver") //nolint:staticcheck
  946. }
  947. if definedConfig.TemplateDriver != "" {
  948. return nil, errors.New("Docker Compose does not support configs.*.template_driver") //nolint:staticcheck
  949. }
  950. if definedConfig.Environment != "" || definedConfig.Content != "" {
  951. continue
  952. }
  953. if config.UID != "" || config.GID != "" || config.Mode != nil {
  954. logrus.Warn("config `uid`, `gid` and `mode` are not supported, they will be ignored")
  955. }
  956. bindMount, err := buildMount(p, types.ServiceVolumeConfig{
  957. Type: types.VolumeTypeBind,
  958. Source: definedConfig.File,
  959. Target: target,
  960. ReadOnly: true,
  961. })
  962. if err != nil {
  963. return nil, err
  964. }
  965. mounts[target] = bindMount
  966. }
  967. values := make([]mount.Mount, 0, len(mounts))
  968. for _, v := range mounts {
  969. values = append(values, v)
  970. }
  971. return values, nil
  972. }
  973. func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
  974. mounts := map[string]mount.Mount{}
  975. secretsDir := "/run/secrets/"
  976. for _, secret := range s.Secrets {
  977. target := secret.Target
  978. if secret.Target == "" {
  979. target = secretsDir + secret.Source
  980. } else if !isAbsTarget(secret.Target) {
  981. target = secretsDir + secret.Target
  982. }
  983. definedSecret := p.Secrets[secret.Source]
  984. if definedSecret.External {
  985. return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
  986. }
  987. if definedSecret.Driver != "" {
  988. return nil, errors.New("Docker Compose does not support secrets.*.driver") //nolint:staticcheck
  989. }
  990. if definedSecret.TemplateDriver != "" {
  991. return nil, errors.New("Docker Compose does not support secrets.*.template_driver") //nolint:staticcheck
  992. }
  993. if definedSecret.Environment != "" {
  994. continue
  995. }
  996. if secret.UID != "" || secret.GID != "" || secret.Mode != nil {
  997. logrus.Warn("secrets `uid`, `gid` and `mode` are not supported, they will be ignored")
  998. }
  999. if _, err := os.Stat(definedSecret.File); os.IsNotExist(err) {
  1000. logrus.Warnf("secret file %s does not exist", definedSecret.Name)
  1001. }
  1002. mnt, err := buildMount(p, types.ServiceVolumeConfig{
  1003. Type: types.VolumeTypeBind,
  1004. Source: definedSecret.File,
  1005. Target: target,
  1006. ReadOnly: true,
  1007. Bind: &types.ServiceVolumeBind{
  1008. CreateHostPath: false,
  1009. },
  1010. })
  1011. if err != nil {
  1012. return nil, err
  1013. }
  1014. mounts[target] = mnt
  1015. }
  1016. values := make([]mount.Mount, 0, len(mounts))
  1017. for _, v := range mounts {
  1018. values = append(values, v)
  1019. }
  1020. return values, nil
  1021. }
  1022. func isAbsTarget(p string) bool {
  1023. return isUnixAbs(p) || isWindowsAbs(p)
  1024. }
  1025. func isUnixAbs(p string) bool {
  1026. return strings.HasPrefix(p, "/")
  1027. }
  1028. func isWindowsAbs(p string) bool {
  1029. return paths.IsWindowsAbs(p)
  1030. }
  1031. func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
  1032. source := volume.Source
  1033. switch volume.Type {
  1034. case types.VolumeTypeBind:
  1035. if !filepath.IsAbs(source) && !isUnixAbs(source) && !isWindowsAbs(source) {
  1036. // volume source has already been prefixed with workdir if required, by compose-go project loader
  1037. var err error
  1038. source, err = filepath.Abs(source)
  1039. if err != nil {
  1040. return mount.Mount{}, err
  1041. }
  1042. }
  1043. case types.VolumeTypeVolume:
  1044. if volume.Source != "" {
  1045. pVolume, ok := project.Volumes[volume.Source]
  1046. if ok {
  1047. source = pVolume.Name
  1048. }
  1049. }
  1050. }
  1051. bind, vol, tmpfs, img := buildMountOptions(volume)
  1052. if bind != nil {
  1053. volume.Type = types.VolumeTypeBind
  1054. }
  1055. return mount.Mount{
  1056. Type: mount.Type(volume.Type),
  1057. Source: source,
  1058. Target: volume.Target,
  1059. ReadOnly: volume.ReadOnly,
  1060. Consistency: mount.Consistency(volume.Consistency),
  1061. BindOptions: bind,
  1062. VolumeOptions: vol,
  1063. TmpfsOptions: tmpfs,
  1064. ImageOptions: img,
  1065. }, nil
  1066. }
  1067. func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions, *mount.ImageOptions) {
  1068. if volume.Type != types.VolumeTypeBind && volume.Bind != nil {
  1069. logrus.Warnf("mount of type `%s` should not define `bind` option", volume.Type)
  1070. }
  1071. if volume.Type != types.VolumeTypeVolume && volume.Volume != nil {
  1072. logrus.Warnf("mount of type `%s` should not define `volume` option", volume.Type)
  1073. }
  1074. if volume.Type != types.VolumeTypeTmpfs && volume.Tmpfs != nil {
  1075. logrus.Warnf("mount of type `%s` should not define `tmpfs` option", volume.Type)
  1076. }
  1077. if volume.Type != types.VolumeTypeImage && volume.Image != nil {
  1078. logrus.Warnf("mount of type `%s` should not define `image` option", volume.Type)
  1079. }
  1080. switch volume.Type {
  1081. case "bind":
  1082. return buildBindOption(volume.Bind), nil, nil, nil
  1083. case "volume":
  1084. return nil, buildVolumeOptions(volume.Volume), nil, nil
  1085. case "tmpfs":
  1086. return nil, nil, buildTmpfsOptions(volume.Tmpfs), nil
  1087. case "image":
  1088. return nil, nil, nil, buildImageOptions(volume.Image)
  1089. }
  1090. return nil, nil, nil, nil
  1091. }
  1092. func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
  1093. if bind == nil {
  1094. return nil
  1095. }
  1096. opts := &mount.BindOptions{
  1097. Propagation: mount.Propagation(bind.Propagation),
  1098. CreateMountpoint: bind.CreateHostPath,
  1099. }
  1100. switch bind.Recursive {
  1101. case "disabled":
  1102. opts.NonRecursive = true
  1103. case "writable":
  1104. opts.ReadOnlyNonRecursive = true
  1105. case "readonly":
  1106. opts.ReadOnlyForceRecursive = true
  1107. }
  1108. return opts
  1109. }
  1110. func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
  1111. if vol == nil {
  1112. return nil
  1113. }
  1114. return &mount.VolumeOptions{
  1115. NoCopy: vol.NoCopy,
  1116. Subpath: vol.Subpath,
  1117. Labels: vol.Labels,
  1118. // DriverConfig: , // FIXME missing from model ?
  1119. }
  1120. }
  1121. func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
  1122. if tmpfs == nil {
  1123. return nil
  1124. }
  1125. return &mount.TmpfsOptions{
  1126. SizeBytes: int64(tmpfs.Size),
  1127. Mode: os.FileMode(tmpfs.Mode),
  1128. }
  1129. }
  1130. func buildImageOptions(image *types.ServiceVolumeImage) *mount.ImageOptions {
  1131. if image == nil {
  1132. return nil
  1133. }
  1134. return &mount.ImageOptions{
  1135. Subpath: image.SubPath,
  1136. }
  1137. }
  1138. func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
  1139. if n.External {
  1140. return s.resolveExternalNetwork(ctx, n)
  1141. }
  1142. id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
  1143. if cerrdefs.IsConflict(err) {
  1144. // Maybe another execution of `docker compose up|run` created same network
  1145. // let's retry once
  1146. return s.resolveOrCreateNetwork(ctx, project, name, n)
  1147. }
  1148. return id, err
  1149. }
  1150. func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) { //nolint:gocyclo
  1151. // This is containers that could be left after a diverged network was removed
  1152. var dangledContainers Containers
  1153. // First, try to find a unique network matching by name or ID
  1154. inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
  1155. if err == nil {
  1156. // NetworkInspect will match on ID prefix, so double check we get the expected one
  1157. // as looking for network named `db` we could erroneously match network ID `db9086999caf`
  1158. if inspect.Name == n.Name || inspect.ID == n.Name {
  1159. p, ok := inspect.Labels[api.ProjectLabel]
  1160. if !ok {
  1161. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  1162. "Set `external: true` to use an existing network", n.Name)
  1163. } else if p != project.Name {
  1164. logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
  1165. "Set `external: true` to use an existing network", n.Name, project.Name)
  1166. }
  1167. if inspect.Labels[api.NetworkLabel] != name {
  1168. return "", fmt.Errorf(
  1169. "network %s was found but has incorrect label %s set to %q (expected: %q)",
  1170. n.Name,
  1171. api.NetworkLabel,
  1172. inspect.Labels[api.NetworkLabel],
  1173. name,
  1174. )
  1175. }
  1176. hash := inspect.Labels[api.ConfigHashLabel]
  1177. expected, err := NetworkHash(n)
  1178. if err != nil {
  1179. return "", err
  1180. }
  1181. if hash == "" || hash == expected {
  1182. return inspect.ID, nil
  1183. }
  1184. dangledContainers, err = s.removeDivergedNetwork(ctx, project, name, n)
  1185. if err != nil {
  1186. return "", err
  1187. }
  1188. }
  1189. }
  1190. // ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
  1191. // Either not found, or name is ambiguous - use NetworkList to list by name
  1192. networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
  1193. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  1194. })
  1195. if err != nil {
  1196. return "", err
  1197. }
  1198. // NetworkList Matches all or part of a network name, so we have to filter for a strict match
  1199. networks = slices.DeleteFunc(networks, func(net network.Summary) bool {
  1200. return net.Name != n.Name
  1201. })
  1202. for _, net := range networks {
  1203. if net.Labels[api.ProjectLabel] == project.Name &&
  1204. net.Labels[api.NetworkLabel] == name {
  1205. return net.ID, nil
  1206. }
  1207. }
  1208. // we could have set NetworkList with a projectFilter and networkFilter but not doing so allows to catch this
  1209. // scenario were a network with same name exists but doesn't have label, and use of `CheckDuplicate: true`
  1210. // prevents to create another one.
  1211. if len(networks) > 0 {
  1212. logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
  1213. "Set `external: true` to use an existing network", n.Name)
  1214. return networks[0].ID, nil
  1215. }
  1216. var ipam *network.IPAM
  1217. if n.Ipam.Config != nil {
  1218. var config []network.IPAMConfig
  1219. for _, pool := range n.Ipam.Config {
  1220. config = append(config, network.IPAMConfig{
  1221. Subnet: pool.Subnet,
  1222. IPRange: pool.IPRange,
  1223. Gateway: pool.Gateway,
  1224. AuxAddress: pool.AuxiliaryAddresses,
  1225. })
  1226. }
  1227. ipam = &network.IPAM{
  1228. Driver: n.Ipam.Driver,
  1229. Config: config,
  1230. }
  1231. }
  1232. hash, err := NetworkHash(n)
  1233. if err != nil {
  1234. return "", err
  1235. }
  1236. n.CustomLabels = n.CustomLabels.Add(api.ConfigHashLabel, hash)
  1237. createOpts := network.CreateOptions{
  1238. Labels: mergeLabels(n.Labels, n.CustomLabels),
  1239. Driver: n.Driver,
  1240. Options: n.DriverOpts,
  1241. Internal: n.Internal,
  1242. Attachable: n.Attachable,
  1243. IPAM: ipam,
  1244. EnableIPv6: n.EnableIPv6,
  1245. EnableIPv4: n.EnableIPv4,
  1246. }
  1247. if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
  1248. createOpts.IPAM = &network.IPAM{}
  1249. }
  1250. if n.Ipam.Driver != "" {
  1251. createOpts.IPAM.Driver = n.Ipam.Driver
  1252. }
  1253. for _, ipamConfig := range n.Ipam.Config {
  1254. config := network.IPAMConfig{
  1255. Subnet: ipamConfig.Subnet,
  1256. IPRange: ipamConfig.IPRange,
  1257. Gateway: ipamConfig.Gateway,
  1258. AuxAddress: ipamConfig.AuxiliaryAddresses,
  1259. }
  1260. createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
  1261. }
  1262. networkEventName := fmt.Sprintf("Network %s", n.Name)
  1263. w := progress.ContextWriter(ctx)
  1264. w.Event(progress.CreatingEvent(networkEventName))
  1265. resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
  1266. if err != nil {
  1267. w.Event(progress.ErrorEvent(networkEventName))
  1268. return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
  1269. }
  1270. w.Event(progress.CreatedEvent(networkEventName))
  1271. err = s.connectNetwork(ctx, n.Name, dangledContainers, nil)
  1272. if err != nil {
  1273. return "", err
  1274. }
  1275. return resp.ID, nil
  1276. }
  1277. func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (Containers, error) {
  1278. // Remove services attached to this network to force recreation
  1279. var services []string
  1280. for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
  1281. _, ok := config.Networks[name]
  1282. return ok
  1283. }) {
  1284. services = append(services, service.Name)
  1285. }
  1286. // Stop containers so we can remove network
  1287. // They will be restarted (actually: recreated) with the updated network
  1288. err := s.stop(ctx, project.Name, api.StopOptions{
  1289. Services: services,
  1290. Project: project,
  1291. }, nil)
  1292. if err != nil {
  1293. return nil, err
  1294. }
  1295. containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
  1296. if err != nil {
  1297. return nil, err
  1298. }
  1299. err = s.disconnectNetwork(ctx, n.Name, containers)
  1300. if err != nil {
  1301. return nil, err
  1302. }
  1303. err = s.apiClient().NetworkRemove(ctx, n.Name)
  1304. eventName := fmt.Sprintf("Network %s", n.Name)
  1305. progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
  1306. return containers, err
  1307. }
  1308. func (s *composeService) disconnectNetwork(
  1309. ctx context.Context,
  1310. network string,
  1311. containers Containers,
  1312. ) error {
  1313. for _, c := range containers {
  1314. err := s.apiClient().NetworkDisconnect(ctx, network, c.ID, true)
  1315. if err != nil {
  1316. return err
  1317. }
  1318. }
  1319. return nil
  1320. }
  1321. func (s *composeService) connectNetwork(
  1322. ctx context.Context,
  1323. network string,
  1324. containers Containers,
  1325. config *network.EndpointSettings,
  1326. ) error {
  1327. for _, c := range containers {
  1328. err := s.apiClient().NetworkConnect(ctx, network, c.ID, config)
  1329. if err != nil {
  1330. return err
  1331. }
  1332. }
  1333. return nil
  1334. }
  1335. func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, error) {
  1336. // NetworkInspect will match on ID prefix, so NetworkList with a name
  1337. // filter is used to look for an exact match to prevent e.g. a network
  1338. // named `db` from getting erroneously matched to a network with an ID
  1339. // like `db9086999caf`
  1340. networks, err := s.apiClient().NetworkList(ctx, network.ListOptions{
  1341. Filters: filters.NewArgs(filters.Arg("name", n.Name)),
  1342. })
  1343. if err != nil {
  1344. return "", err
  1345. }
  1346. if len(networks) == 0 {
  1347. // in this instance, n.Name is really an ID
  1348. sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
  1349. if err == nil {
  1350. networks = append(networks, sn)
  1351. } else if !cerrdefs.IsNotFound(err) {
  1352. return "", err
  1353. }
  1354. }
  1355. // NetworkList API doesn't return the exact name match, so we can retrieve more than one network with a request
  1356. networks = slices.DeleteFunc(networks, func(net network.Inspect) bool {
  1357. // this function is called during the rebuild stage of `compose watch`.
  1358. // we still require just one network back, but we need to run the search on the ID
  1359. return net.Name != n.Name && net.ID != n.Name
  1360. })
  1361. switch len(networks) {
  1362. case 1:
  1363. return networks[0].ID, nil
  1364. case 0:
  1365. enabled, err := s.isSWarmEnabled(ctx)
  1366. if err != nil {
  1367. return "", err
  1368. }
  1369. if enabled {
  1370. // Swarm nodes do not register overlay networks that were
  1371. // created on a different node unless they're in use.
  1372. // So we can't preemptively check network exists, but
  1373. // networkAttach will later fail anyway if network actually doesn't exist
  1374. return "swarm", nil
  1375. }
  1376. return "", fmt.Errorf("network %s declared as external, but could not be found", n.Name)
  1377. default:
  1378. return "", fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
  1379. }
  1380. }
  1381. func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
  1382. inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
  1383. if err != nil {
  1384. if !cerrdefs.IsNotFound(err) {
  1385. return "", err
  1386. }
  1387. if volume.External {
  1388. return "", fmt.Errorf("external volume %q not found", volume.Name)
  1389. }
  1390. err = s.createVolume(ctx, volume)
  1391. return volume.Name, err
  1392. }
  1393. if volume.External {
  1394. return volume.Name, nil
  1395. }
  1396. // Volume exists with name, but let's double-check this is the expected one
  1397. p, ok := inspected.Labels[api.ProjectLabel]
  1398. if !ok {
  1399. logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
  1400. }
  1401. if ok && p != project.Name {
  1402. 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.Name)
  1403. }
  1404. expected, err := VolumeHash(volume)
  1405. if err != nil {
  1406. return "", err
  1407. }
  1408. actual, ok := inspected.Labels[api.ConfigHashLabel]
  1409. if ok && actual != expected {
  1410. confirm := assumeYes
  1411. if !assumeYes {
  1412. msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name)
  1413. confirm, err = prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false)
  1414. if err != nil {
  1415. return "", err
  1416. }
  1417. }
  1418. if confirm {
  1419. err = s.removeDivergedVolume(ctx, name, volume, project)
  1420. if err != nil {
  1421. return "", err
  1422. }
  1423. return volume.Name, s.createVolume(ctx, volume)
  1424. }
  1425. }
  1426. return inspected.Name, nil
  1427. }
  1428. func (s *composeService) removeDivergedVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) error {
  1429. // Remove services mounting divergent volume
  1430. var services []string
  1431. for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
  1432. for _, cfg := range config.Volumes {
  1433. if cfg.Source == name {
  1434. return true
  1435. }
  1436. }
  1437. return false
  1438. }) {
  1439. services = append(services, service.Name)
  1440. }
  1441. err := s.stop(ctx, project.Name, api.StopOptions{
  1442. Services: services,
  1443. Project: project,
  1444. }, nil)
  1445. if err != nil {
  1446. return err
  1447. }
  1448. containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
  1449. if err != nil {
  1450. return err
  1451. }
  1452. // FIXME (ndeloof) we have to remove container so we can recreate volume
  1453. // but doing so we can't inherit anonymous volumes from previous instance
  1454. err = s.remove(ctx, containers, api.RemoveOptions{
  1455. Services: services,
  1456. Project: project,
  1457. })
  1458. if err != nil {
  1459. return err
  1460. }
  1461. return s.apiClient().VolumeRemove(ctx, volume.Name, true)
  1462. }
  1463. func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
  1464. eventName := fmt.Sprintf("Volume %q", volume.Name)
  1465. w := progress.ContextWriter(ctx)
  1466. w.Event(progress.CreatingEvent(eventName))
  1467. hash, err := VolumeHash(volume)
  1468. if err != nil {
  1469. return err
  1470. }
  1471. volume.CustomLabels.Add(api.ConfigHashLabel, hash)
  1472. _, err = s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
  1473. Labels: mergeLabels(volume.Labels, volume.CustomLabels),
  1474. Name: volume.Name,
  1475. Driver: volume.Driver,
  1476. DriverOpts: volume.DriverOpts,
  1477. })
  1478. if err != nil {
  1479. w.Event(progress.ErrorEvent(eventName))
  1480. return err
  1481. }
  1482. w.Event(progress.CreatedEvent(eventName))
  1483. return nil
  1484. }