create.go 48 KB

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