create.go 50 KB

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