create.go 48 KB

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