create.go 48 KB

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