create.go 48 KB

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