Compare commits

...

139 Commits

Author SHA1 Message Date
Sebastiaan van Stijn
42710b7c43 docs: also generate "usage" in MarkDown files
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-09 14:40:35 +01:00
Sebastiaan van Stijn
b9b3a3d91f docs: update cli-docs-tool to v0.4.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-09 14:40:35 +01:00
Sebastiaan van Stijn
1d06741032 docs: fix trailing whitespace from markdown and regenerate
Trailing whitespace in Markdown can force line-breaks, which doesn't seem to
be the intent on these;

    find . -type f -print0 | xargs -0 perl -pi -e 's/ +$//'

The trailing whitespace also can cause the YAML to go wonky (although the
cli-docs-tool now takes that into account), and caused the "examples" section
to be missed in the `docker compose pull` page (something we should fix in
the tool).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-09 14:40:35 +01:00
Sebastiaan van Stijn
35b790dcdf docs: fix "source" path for YAML generator, and regenerate
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-09 14:40:35 +01:00
Guillaume Lours
7036cda306 Merge pull request #9198 from ndeloof/dockerCli_stdout
composeService to use dockerCli's In/Out/Err streams
2022-03-09 14:36:07 +01:00
Guillaume Lours
540ad83a6d Merge pull request #9251 from ndeloof/device_cgroup_rules
add support for device_cgroup_rules
2022-03-09 13:27:53 +01:00
Guillaume Lours
92be1e7112 Merge pull request #9252 from glours/test-run-with-dependencies
add run with dependencies e2e test
2022-03-09 12:35:59 +01:00
Guillaume Lours
a97576e844 add run with dependencies e2e test
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-09 12:22:38 +01:00
Nicolas De Loof
d8775c7a28 add support for device_cgroup_rules
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-09 11:39:20 +01:00
Nicolas De Loof
158b5ff6a3 build full compose model from resources, then filter by services
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-09 11:29:30 +01:00
Guillaume Lours
ce944520ff Merge pull request #9247 from ndeloof/plain
use plain text progress when ansi=never is set
2022-03-08 16:55:55 +01:00
Nicolas De Loof
57e8e8ef29 use plain text progress when ansi=never is set
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-08 15:05:37 +01:00
Guillaume Lours
a2b9c81254 Merge pull request #9246 from ndeloof/interactive
map --interactive to StdinOpen
2022-03-08 10:23:28 +01:00
Guillaume Lours
d75f22cc7b publish a draft release with auto generate release notes
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-08 10:19:06 +01:00
Nicolas De Loof
2282159922 map --interactive to StdinOpen
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-08 10:17:17 +01:00
Nicolas De Loof
8c7951465e filter containers after project has been rebuilt from resources
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-08 09:47:22 +01:00
Victor Timofei
9054f6a18b Fix json format for version command
Signed-off-by: Victor Timofei <victor@vtimothy.com>
2022-03-08 09:40:25 +01:00
Nicolas De Loof
22b8c731c7 prevent getCanonicalContainerName to crash
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-07 14:58:39 +01:00
Guillaume Lours
0ef4b90fae don't failed when trying to stop or rm services with no containers running
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-07 10:35:22 +01:00
Guillaume Lours
908c12120e Merge pull request #9237 from ndeloof/run_regression
don't try to start dependencies when there are none
2022-03-07 08:42:01 +01:00
Nicolas De Loof
dbe7de50a7 don't try to start dependencies when there are none
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-07 08:25:05 +01:00
Zixuan James Li
fcff39631a Add documentation for COMPOSE_IGNORE_ORPHANS
Signed-off-by: Zixuan James Li <359101898@qq.com>
2022-03-05 22:54:16 +01:00
PIG208
bcaa908f74 Support COMPOSE_IGNORE_ORPHANS for compose run
This revives #7020 and resolves the issue mentioned in #4992.

Signed-off-by: Zixuan James Li <359101898@qq.com>
2022-03-05 22:54:16 +01:00
Guillaume Lours
c64b044b7e Merge pull request #9229 from ulyssessouza/add-ti-run-exec
Add -i and -t to run and exec
2022-03-04 17:33:09 +01:00
Guillaume Lours
1a4e6c2465 Merge pull request #9230 from ulyssessouza/treat-env-bool-vars
Add function to convert strings to bool
2022-03-04 17:32:21 +01:00
Ulysses Souza
67b4669f9b Add function to convert strings to bool
Typically used on boolean environment variable values

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-04 16:42:37 +01:00
Ulysses Souza
61735c0012 Add -i and -t to run and exec
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-04 14:27:06 +01:00
Pavel Sirotkin
c12a948f97 CONTRIBUTING.md file. Update slack sign-up link
Signed-off-by: Pavel Sirotkin <pav.pnz@gmail.com>
2022-03-04 12:25:11 +01:00
Nicolas De Loof
f86f252a66 composeService to use dockerCli's In/Out/Err streams
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-03 16:34:57 +01:00
dependabot[bot]
bf5785307b Bump github.com/containerd/containerd from 1.6.0 to 1.6.1
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 14:13:39 +01:00
Nicolas De Loof
52eeda9aa7 report external volume name not found
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-02 08:09:31 +01:00
Guillaume Lours
1f0cf0723c Merge pull request #9221 from ulyssessouza/bump-docker-distribution-2.8.0
Bump docker/distribution -> v2.8.0
2022-03-01 16:15:22 +01:00
Ulysses Souza
315f16e5fb Bump docker/distribution -> v2.8.0
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-01 16:08:48 +01:00
Ulysses Souza
327a1bb27b Merge pull request #9219 from ulyssessouza/bump-compose-go-1.1.0
Bump compose-go 1.1.0
2022-03-01 15:58:12 +01:00
Ulysses Souza
16914e372e Bump compose-go 1.1.0
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-01 15:52:14 +01:00
Guillaume Lours
ec080f184a Merge pull request #9214 from ndeloof/inconsistent_hash
exclude com.docker.compose.image label from service hash
2022-02-28 10:20:36 +01:00
Nicolas De Loof
b5b8e7a116 exclude com.docker.compose.image label from service hash
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-28 10:10:39 +01:00
Guillaume Lours
9d73cc88cc Merge pull request #9148 from arhemd/issue#9147
Using start, stop, restart from outside the working directory using --project-name (#9147)
2022-02-27 09:45:36 +01:00
Mehrad Dadar
9c68c76bea minor improvement and fix
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-27 03:40:46 +03:30
Mehrad Dadar
aeb7448449 minor improvement and fix
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-25 06:26:05 +03:30
Mehrad Dadar
42c3adb236 project existence check: added error message
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-25 05:36:22 +03:30
Mehrad Dadar
35f37cd1f7 fix lint
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-25 00:00:36 +03:30
Mehrad Dadar
c0465616bb check service existence in project
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-24 23:46:07 +03:30
Mehrad Dadar
32d44dfc25 added scale to toProjectName
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-24 22:39:38 +03:30
Mehrad Dadar
5885a250bc Merge branch 'docker:v2' into issue#9147 2022-02-24 18:03:05 +03:30
Mehrad Dadar
ce1c788237 minor code cleanup
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-24 14:50:50 +03:30
Mehrad Dadar
67f7b84829 modified com.docker.compose.depends_on label to contain dependency type
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-23 21:34:34 +03:30
Guillaume Lours
fd676adc5d Merge pull request #9200 from ndeloof/COMPOSE_REMOVE_ORPHANS
COMPOSE_REMOVE_ORPHANS can be set to always apply --remove-orphans
2022-02-23 14:10:32 +01:00
Nicolas De Loof
aa864fde20 COMPOSE_REMOVE_ORPHANS can be set to always apply --remove-orphans
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-23 14:04:12 +01:00
dependabot[bot]
f7a6c3bc54 Bump github.com/containerd/containerd from 1.5.8 to 1.6.0
Bumps [github.com/containerd/containerd](https://github.com/containerd/containerd) from 1.5.8 to 1.6.0.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.5.8...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-23 13:37:56 +01:00
Ulysses Souza
8a9498c571 Merge pull request #9192 from glours/wait-and-scale-0
Wait and scale 0
2022-02-23 12:47:34 +01:00
Mehrad Dadar
7e7262bc77 Merge branch 'docker:v2' into issue#9147 2022-02-22 05:08:29 +03:30
Nicolas De Loof
981aea674d bump buildx to 0.7.1
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-21 14:28:22 +01:00
Guillaume Lours
64a9e4bf01 fix cyclomatic complexity of composeService.waitDependencies function
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-02-20 11:59:26 +01:00
Guillaume Lours
4be38f84df do not stop the dependencies wait process when reaching a dependency with service_started condition
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-02-20 10:28:34 +01:00
Guillaume Lours
09e0fa94b8 do not wait for dependencies with scale 0
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-02-20 10:28:34 +01:00
Nicolas De Loof
416498441c declare --volume as an alias for --volumes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-18 17:40:47 +01:00
Vedant Koditkar
cb45c6f2df Add unit tests for combinedConfigFiles logic
Signed-off-by: Vedant Koditkar <vedant.koditkar@outlook.com>
2022-02-18 16:37:35 +01:00
Vedant Koditkar
90ca37344f Update breaking test cases
Signed-off-by: Vedant Koditkar <vedant.koditkar@outlook.com>
2022-02-18 16:37:35 +01:00
Vedant Koditkar
fb3f9e270f Add compose file path to ls command
docker compose ls will not include config files section in result

Signed-off-by: Vedant Koditkar <vedant.koditkar@outlook.com>
2022-02-18 16:37:35 +01:00
Ulysses Souza
02f78d2893 Merge pull request #9183 from ndeloof/golangci-lint-action
use golangci-lint-action
2022-02-17 18:17:06 +01:00
Nicolas De Loof
d47dcef1a6 use golangci-lint-action
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-17 18:10:28 +01:00
Nicolas De Loof
fb9310caf2 use CustomLabels for composeV2 metadata and not impact service hash
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-02-17 09:05:54 +01:00
Mehrad Dadar
ced9eba940 refactor: removed redundant code
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-17 07:55:11 +03:30
Ulysses Souza
2eeed8481d Fix pause/unpause by only applying to running containers
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-02-16 15:13:12 +01:00
Ulysses Souza
10ca0314bc Merge pull request #9164 from danBamikiya/compose-up-typo-fix
Fix typo in `reference/compose_up.md`
2022-02-11 15:18:59 +01:00
Dan Bámíkíyá
336b825fdd Fix the typo in the corresponding Yaml file also
Signed-off-by: Dan Bámíkíyá <dudeawesome732@gmail.com>
2022-02-11 15:10:08 +01:00
Dan Bámíkíyá
213d9166dc Fix typo in reference/compose_up
Signed-off-by: Dan Bámíkíyá <dudeawesome732@gmail.com>
2022-02-11 15:08:33 +01:00
Guillaume Lours
598b59f8bf Merge pull request #9142 from thaJeztah/update_goterm
go.mod: github.com/buger/goterm v1.0.4
2022-02-08 12:28:18 +01:00
Mehrad Dadar
65ed8cf4c2 Implemented #9147
Signed-off-by: Mehrad Dadar <mehrad.dadar@gmail.com>
2022-02-05 10:27:52 +03:30
Ulysses Souza
a23cbb580e Merge pull request #9146 from ulyssessouza/bump-compose-go-1.0.9
Bump compose-go 1.0.9
2022-02-05 02:59:23 +01:00
Ulysses Souza
a89e194558 Bump compose-go 1.0.9
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-02-04 14:47:16 +01:00
Sebastiaan van Stijn
b31695a66e go.mod: github.com/buger/goterm v1.0.4
Version v1.0.4 was released, containing a fix for a regression in
v1.0.3, so the previous downgrade can be reverted, and we can go
back to the latest version:
https://github.com/buger/goterm/compare/v1.0.3...v1.0.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-02-03 21:33:39 +01:00
Ulysses Souza
5262d3bbf5 Merge pull request #9130 from laurazard/fix_mac_address_network_priority
Set NetworkMode correctly according to network priorities
2022-02-01 14:53:25 +01:00
Laura Brehm
a4836391a5 Set NetworkMode correctly according to network priorities
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-02-01 12:59:05 +00:00
Ulysses Souza
bfd7428619 Merge pull request #9074 from ndeloof/down_volumes
only remove volumes set by compose file
2022-01-21 13:09:10 +01:00
Nicolas De Loof
feba34e406 only remove volumes set by compose file
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-01-20 14:32:45 +01:00
Yuri Kanivetsky
37f763f009 Don't SetRawTerminal() when exec is run with -T
Signed-off-by: Yuri Kanivetsky <yuri.kanivetsky@gmail.com>
2022-01-19 09:11:35 +01:00
Guillaume Rose
99cd90a4b2 Return only numbers in short version
Python version of docker compose removes the v prefix in the version.

```
$ docker-compose version --short
1.25.5
```

Signed-off-by: Guillaume Rose <gurose@redhat.com>
2022-01-17 07:30:27 +01:00
Ulysses Souza
a279c3a934 Discard env_file section on convert/config
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-01-15 11:45:08 +01:00
Nikhil Benesch
ee586e7f1e Introduce ergonomic API for handling multiple container events
Signed-off-by: Nikhil Benesch <nikhil.benesch@gmail.com>
2022-01-07 21:22:57 +01:00
Nikhil Benesch
5eb314a4ca Add progress output while waiting for dependencies
This commit adds progress output while waiting for `depends_on`
conditions to resolve. The initial output looks like so:

     ⠿ Container chbench-zookeeper-1        Waiting      0s
     ⠿ Container chbench-kafka-1            Waiting      0s
     ⠿ Container chbench-one-off            Waiting      0s

Once all conditions have been resolved, the ouput looks like this:

     ⠿ Container chbench-zookeeper-1        Healthy      1.2s
     ⠿ Container chbench-kafka-1            Healthy      3.2s
     ⠿ Container chbench-schema-registry-1  Exited       4s

As shown above, `service_healthy` conditions result in a terminal status
of "Healthy" while `service_exited_successfully` conditions result in a
terminal status of "Exited".

Signed-off-by: Nikhil Benesch <nikhil.benesch@gmail.com>
2022-01-07 21:22:57 +01:00
Nikhil Benesch
c5cdce0b60 Don't wait forever for unhealthy dependencies
The previous code would wait for dependencies to become healthy forever,
even if they'd become unhealthy in the meantime. I can't find an issue
report for this bug, but it was described in a comment on the PR that
introduced the `--wait` flag [0].

[0]: https://github.com/docker/compose/pull/8777#issuecomment-965643839

Signed-off-by: Nikhil Benesch <nikhil.benesch@gmail.com>
2022-01-07 19:13:43 +01:00
Ulysses Souza
6dc6bedb60 Downgrade goterm to 1.0.1
This is to avoid a release error:
https://github.com/docker/compose/runs/4701103662?check_suite_focus=true

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-01-04 14:54:16 +01:00
Tymoteusz Blazejczyk
9f06a02eb5 Moved bind mode creation to getBindMode function
Added unit tests for all uses cases.

Signed-off-by: Tymoteusz Blazejczyk <tymoteusz.blazejczyk@tymonx.com>
2022-01-03 08:04:34 +01:00
Tymoteusz Blazejczyk
9d0421a929 Removed the replace directive in the go.mod file
Signed-off-by: Tymoteusz Blazejczyk <tymoteusz.blazejczyk@tymonx.com>
2022-01-03 08:04:34 +01:00
Tymoteusz Blazejczyk
340b5482b0 Changed to the official compose-spec/compose-go
Changes were merged. Updated the `replace` directive in the `go.mod`
file to the latest official package.

Signed-off-by: Tymoteusz Blazejczyk <tymoteusz.blazejczyk@tymonx.com>
2022-01-03 08:04:34 +01:00
Tymoteusz Blazejczyk
faaa93bf12 Added volume bind option SELinux label :z :Z
Added unsupported volume bind option SELinux label `:z` and `:Z` in v2.

It is a regression compared to v1 written in Python.

The v2 uses the compose-spec/compose-go to parse Compose YAML
specification files but there was missing support for volume bind option
SELinux label `:z` and `:Z` in parser. It is fixed in:

- https://github.com/compose-spec/compose-go/pull/213

It fixes #9072

References:

- https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label

Signed-off-by: Tymoteusz Blazejczyk <tymoteusz.blazejczyk@tymonx.com>
2022-01-03 08:04:34 +01:00
dependabot[bot]
381df20010 Bump github.com/AlecAivazis/survey/v2 from 2.2.3 to 2.3.2
Bumps [github.com/AlecAivazis/survey/v2](https://github.com/AlecAivazis/survey) from 2.2.3 to 2.3.2.
- [Release notes](https://github.com/AlecAivazis/survey/releases)
- [Commits](https://github.com/AlecAivazis/survey/compare/v2.2.3...v2.3.2)

---
updated-dependencies:
- dependency-name: github.com/AlecAivazis/survey/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 15:19:03 +01:00
dependabot[bot]
a9e8164a8d Bump github.com/containerd/console from 1.0.2 to 1.0.3
Bumps [github.com/containerd/console](https://github.com/containerd/console) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/containerd/console/releases)
- [Commits](https://github.com/containerd/console/compare/v1.0.2...v1.0.3)

---
updated-dependencies:
- dependency-name: github.com/containerd/console
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 15:09:03 +01:00
Lance Chen
1191023fb6 Propagate GroupAdd from ServiceConfig to HostConfig
The `group_add` key is parsed correctly from a compose file, but it is not
passed into the `ContainerCreate` API call, thus the configuration does
not take effect. This commit fixes the issue by propagating the
configuration from Docker compose's ServiceConfig to Docker container's
HostConfig.

Signed-off-by: Lance Chen <hello@lancechen.tw>
2021-12-17 15:08:14 +01:00
dependabot[bot]
a108690ac2 Bump github.com/buger/goterm from 1.0.0 to 1.0.3
Bumps [github.com/buger/goterm](https://github.com/buger/goterm) from 1.0.0 to 1.0.3.
- [Release notes](https://github.com/buger/goterm/releases)
- [Commits](https://github.com/buger/goterm/compare/v1.0.0...v1.0.3)

---
updated-dependencies:
- dependency-name: github.com/buger/goterm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 14:59:02 +01:00
dependabot[bot]
0e81d1c88e Bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/hashicorp/go-multierror/releases)
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-multierror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 07:39:21 +01:00
dependabot[bot]
22002531d8 Bump github.com/spf13/cobra from 1.2.1 to 1.3.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 06:24:57 +01:00
dependabot[bot]
b66ff0c3d8 Bump github.com/golang/mock from 1.5.0 to 1.6.0
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/golang/mock
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-17 06:24:35 +01:00
Ulysses Souza
7b6439997d Fix output redirection on command run
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-15 15:10:22 +01:00
Ulysses Souza
d37b3fe413 Merge pull request #9036 from ulyssessouza/add-dependabot
Add dependabot
2021-12-15 10:38:28 +01:00
Ulysses Souza
ce7a2412b1 Add dependabot
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-14 16:19:41 +01:00
Ulysses Souza
9e52e9fbe3 Merge pull request #9004 from ndeloof/cp_stopped
compose cp doesn't need a full project and can copy from stopped containers
2021-12-14 14:27:51 +01:00
Nicolas De Loof
c5b7624d10 compose cp doesn't need a full project and can copy from stopped container
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-12-12 16:15:56 +01:00
Ulysses Souza
cf7319fc6e Only kill running containers
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-10 08:48:38 +01:00
Nicolas De Loof
740276f550 handle "stop" event
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-12-09 13:57:52 +01:00
Ulysses Souza
40bca10250 Merge pull request #9007 from ulyssessouza/add-test-modes
Add 2 modes test mechanism
2021-12-09 11:16:37 +01:00
Ulysses Souza
8ae8d99528 Use build tags for selecting e2e test mode
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-09 10:52:18 +01:00
Jon Cram
f03b7085c3 compose ps: fix typo "unknow" -> "unknown"
Signed-off-by: Jon Cram <webignition@gmail.com>
2021-12-09 08:56:35 +01:00
Ulysses Souza
36c2947e4d Merge pull request #9012 from notok/rm_containers_when_build_succeed
Remove intermediate containers when build succeeded in classic build
2021-12-08 12:39:10 +01:00
notok
3bbcc3d4d0 Fix lint error
Signed-off-by: notok <noto.kazufumi@gmail.com>
2021-12-08 18:42:00 +09:00
notok
b47d8ea868 Remove intermediate containers when build succeeded
Intermediate containers remain even when build succeeded
when building with classic way (i.e. not with buildkit).
Remove them when build succeeded like default behavior of docker build.

Signed-off-by: notok <noto.kazufumi@gmail.com>
2021-12-08 04:18:05 +09:00
Ulysses Souza
a97a73600e Add 2 modes test mechanism
This should test on plugin and standalone mode

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-07 10:25:50 +01:00
Nicolas De Loof
6735220557 ignore missing (swarm) overlay networks
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-12-06 14:06:34 +01:00
Kevin Roy
d7d29b25bc compose images should list created containers images
Signed-off-by: Kevin Roy <kiniou@gmail.com>
2021-12-05 10:59:25 +01:00
Ulysses Souza
e2f33af831 Merge and fix Convert function from docker/compose-switch
This also removes the vendoring of the repo
Note that it fixes the problem of double "compose" on calling
the compose plugin with "docker" root flags.
Like in "docker --context default compose version"

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-03 12:17:17 +01:00
Guillaume Lours
19b9fdf536 upgrade version of opencontainers/image-spec (security issue)
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-12-03 12:16:14 +01:00
Guillaume Lours
a842522f49 use filepath instead of path to check if the dockerfile path is abolute or not
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-12-03 12:15:22 +01:00
Ulysses Souza
bc1160de72 Turn external volume usage into a warning instead of erroring
This avoids a compatibility break since returning an error would avoid
the project to be started

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-02 13:11:20 +01:00
Ulysses Souza
f791bc8a42 Merge pull request #8984 from ndeloof/logs_restart
compose logs to notify printer about container lifecycle events
2021-12-02 09:27:11 +01:00
Nicolas De Loof
0d7567131a compose logs to notify printer about container lifecycle events
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-12-02 09:18:49 +01:00
Mathieu Champlon
7b84f2c2a5 Merge pull request #8974 from ulyssessouza/fix-links-resolution3
Return an error when failing to list containers
2021-11-29 10:34:00 +01:00
Ulysses Souza
cf7b1441d9 Return an error when failing to list containers
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-29 10:28:45 +01:00
Mathieu Champlon
32005b0bfe Merge pull request #8953 from ulyssessouza/test-multiargs
Add multiargs build e2e tests
2021-11-29 09:46:47 +01:00
Mathieu Champlon
025a72a417 Merge pull request #8972 from ulyssessouza/fix-links-resolution2
Refactoring variable name
2021-11-29 09:46:24 +01:00
Ulysses Souza
95c4502b81 Refactoring variable name
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-28 15:43:27 +01:00
Ulysses Souza
2290ce2c24 Merge pull request #8970 from ndeloof/external_volume_check
Don't check compose labels on external volumes
2021-11-28 15:36:48 +01:00
Nicolas De Loof
bac732837e Don't check compose labels on external volumes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-27 16:34:42 +01:00
Ulysses Souza
b725c56c42 Fix links resolution
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-26 20:20:55 +01:00
Ulysses Souza
cfcc9533b3 Add multiargs build e2e tests
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-25 21:22:30 +01:00
Mathieu Champlon
cffdb69c5e Fix test config dir content
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-25 21:22:30 +01:00
Kyungsik Park
709190312c Fix to use Key instead of Service for graph updates
Signed-off-by: Kyungsik Park <kay.pak@naverlabs.com>
2021-11-24 08:02:50 +01:00
Mathieu Champlon
e1a38f984b Merge pull request #8947 from ndeloof/run_quietpull
introduce run —quiet-pull to align with up
2021-11-23 19:17:08 +01:00
Nicolas De Loof
4dafeb57a5 introduce run —quiet-pull to align with up
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-23 19:14:12 +01:00
Nicolas De Loof
45956c36fb introduce docker compose config --images
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-23 19:13:04 +01:00
Ulysses Souza
9eb69465b7 Merge pull request #8834 from akerouanton/fix-start-0-replicas
v2: Don't try to start services with 0 replicas
2021-11-23 14:53:08 +01:00
Albin Kerouanton
5f392258cb Don't try to start services with 0 replicas
When a service has 0 replicas to start, it don't try to create any
container. However `composeService.startService()` still gets executed.
This method checks if there's been containers created for that service
beforehand. If there's none, it returns the following error: `no
containers to start`.

This change checks if replicas == 0 and exits early from
`composeService.startService()` when that's the case.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2021-11-23 11:57:09 +01:00
Mathieu Champlon
382c1cd68e Merge pull request #8943 from ndeloof/config_no_normalize
don't normalize compose model in compatibility mode
2021-11-23 11:16:36 +01:00
Mathieu Champlon
5994050f51 Merge pull request #8956 from mat007/bump-compose-go
Bump compose-go to v1.0.8
2021-11-23 11:10:57 +01:00
Mathieu Champlon
28a00571ef Bump compose-go to v1.0.8
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-23 11:05:26 +01:00
Nicolas De Loof
d00eacbba0 don't normalize compose model in compatibility mode
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-22 21:02:44 +01:00
Ulysses Souza
a6c76a9c0f Merge pull request #8954 from mat007/bump-compose-go
Bump compose-go to v1.0.7
2021-11-22 18:18:41 +01:00
Mathieu Champlon
5754d6084c Bump compose-go to v1.0.7
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-22 18:13:07 +01:00
147 changed files with 4028 additions and 1061 deletions

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily

View File

@@ -28,9 +28,9 @@ jobs:
- name: Run golangci-lint
env:
BUILD_TAGS: e2e
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/bin/ v1.39.0
make -f builder.Makefile lint
uses: golangci/golangci-lint-action@v2
with:
args: --timeout=180s
# only on main branch, costs too much for the gain on every PR
validate-cross-build:
@@ -59,8 +59,8 @@ jobs:
- name: Build packages
run: make -f builder.Makefile cross
build:
name: Build
build-plugin:
name: Build and tests in plugin mode
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
@@ -71,10 +71,6 @@ jobs:
go-version: 1.17
id: go
- name: Set up gosum
run: |
go get -u gotest.tools/gotestsum
- name: Setup docker CLI
run: |
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
@@ -89,8 +85,6 @@ jobs:
key: go-${{ hashFiles('**/go.sum') }}
- name: Test
env:
BUILD_TAGS: kube
run: make -f builder.Makefile test
- name: Build for local E2E
@@ -98,5 +92,38 @@ jobs:
BUILD_TAGS: e2e
run: make -f builder.Makefile compose-plugin
- name: E2E Test
- name: E2E Test in plugin mode
run: make e2e-compose
build-standalone:
name: Build and tests in standalone mode
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.17
id: go
- name: Setup docker CLI
run: |
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build for local E2E
env:
BUILD_TAGS: e2e
run: make -f builder.Makefile compose-plugin
- name: E2E Test in standalone mode
run: make e2e-compose-standalone

View File

@@ -44,7 +44,8 @@ jobs:
- uses: ncipollo/release-action@v1
with:
artifacts: "bin/*"
prerelease: true
generateReleaseNotes: true
draft: true
commit: "v2"
token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.event.inputs.tag }}

View File

@@ -83,7 +83,7 @@ don't get discouraged! Our contributor's guide explains
<tr>
<td>Community Slack</td>
<td>
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://dockercommunity.slack.com/ssb/redirect" target="_blank">with this link</a>.
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://www.docker.com/docker-community" target="_blank">with this link</a>.
</td>
</tr>
<tr>

View File

@@ -42,8 +42,16 @@ compose-plugin: ## Compile the compose cli-plugin
--output ./bin
.PHONY: e2e-compose
e2e-compose: ## Run End to end local tests. Set E2E_TEST=TestName to run a single test
gotestsum $(TEST_FLAGS) ./pkg/e2e -- -count=1
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
.PHONY: e2e-compose-standalone
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
go test $(TEST_FLAGS) -count=1 --tags=standalone ./pkg/e2e
.PHONY: e2e
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
.PHONY: cross
cross: ## Compile the CLI for linux, darwin and windows

View File

@@ -0,0 +1,97 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compatibility
import (
"fmt"
"os"
"github.com/docker/compose/v2/cmd/compose"
)
func getBoolFlags() []string {
return []string{
"--debug", "-D",
"--verbose",
"--tls",
"--tlsverify",
}
}
func getStringFlags() []string {
return []string{
"--tlscacert",
"--tlscert",
"--tlskey",
"--host", "-H",
"--context",
"--log-level",
}
}
// Convert transforms standalone docker-compose args into CLI plugin compliant ones
func Convert(args []string) []string {
var rootFlags []string
command := []string{compose.PluginName}
l := len(args)
for i := 0; i < l; i++ {
arg := args[i]
if arg[0] != '-' {
// not a top-level flag anymore, keep the rest of the command unmodified
if arg == compose.PluginName {
i++
}
command = append(command, args[i:]...)
break
}
if arg == "--verbose" {
arg = "--debug"
}
if arg == "-h" {
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
arg = "--help"
}
if arg == "--version" || arg == "-v" {
// redirect --version pseudo-command to actual command
arg = "version"
}
if contains(getBoolFlags(), arg) {
rootFlags = append(rootFlags, arg)
continue
}
if contains(getStringFlags(), arg) {
i++
if i >= l {
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
os.Exit(1)
}
rootFlags = append(rootFlags, arg, args[i])
continue
}
command = append(command, arg)
}
return append(rootFlags, command...)
}
func contains(array []string, needle string) bool {
for _, val := range array {
if val == needle {
return true
}
}
return false
}

View File

@@ -0,0 +1,78 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compatibility
import (
"testing"
"gotest.tools/v3/assert"
)
func Test_convert(t *testing.T) {
tests := []struct {
name string
args []string
want []string
}{
{
name: "compose only",
args: []string{"up"},
want: []string{"compose", "up"},
},
{
name: "with context",
args: []string{"--context", "foo", "-f", "compose.yaml", "up"},
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
},
{
name: "with host",
args: []string{"--host", "tcp://1.2.3.4", "up"},
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
},
{
name: "compose --version",
args: []string{"--version"},
want: []string{"compose", "version"},
},
{
name: "help",
args: []string{"-h"},
want: []string{"compose", "--help"},
},
{
name: "issues/1962",
args: []string{"psql", "-h", "postgres"},
want: []string{"compose", "psql", "-h", "postgres"}, // -h should not be converted to --help
},
{
name: "issues/8648",
args: []string{"exec", "mongo", "mongo", "--host", "mongo"},
want: []string{"compose", "exec", "mongo", "mongo", "--host", "mongo"}, // --host is passed to exec
},
{
name: "issues/12",
args: []string{"--log-level", "INFO", "up"},
want: []string{"--log-level", "INFO", "compose", "up"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Convert(tt.args)
assert.DeepEqual(t, tt.want, got)
})
}
}

View File

@@ -29,15 +29,17 @@ import (
"github.com/compose-spec/compose-go/types"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
)
// Command defines a compose CLI command as a func with args
@@ -120,24 +122,6 @@ func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
return err
}
if o.EnvFile != "" {
var services types.Services
for _, s := range project.Services {
ef := o.EnvFile
if ef != "" {
if !filepath.IsAbs(ef) {
ef = filepath.Join(project.WorkingDir, o.EnvFile)
}
if s.Labels == nil {
s.Labels = make(map[string]string)
}
s.Labels[api.EnvironmentFileLabel] = ef
services = append(services, s)
}
}
project.Services = services
}
return fn(ctx, project, args)
})
}
@@ -176,10 +160,29 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
return nil, compose.WrapComposeError(err)
}
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
compose.Separator = "_"
}
ef := o.EnvFile
if ef != "" && !filepath.IsAbs(ef) {
ef = filepath.Join(project.WorkingDir, o.EnvFile)
}
for i, s := range project.Services {
s.CustomLabels = map[string]string{
api.ProjectLabel: project.Name,
api.ServiceLabel: s.Name,
api.VersionLabel: api.ComposeVersion,
api.WorkingDirLabel: project.WorkingDir,
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
api.OneoffLabel: "False", // default, will be overridden by `run` command
}
if ef != "" {
s.CustomLabels[api.EnvironmentFileLabel] = ef
}
project.Services[i] = s
}
if len(services) > 0 {
s, err := project.GetServices(services...)
if err != nil {
@@ -212,11 +215,12 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
cli.WithName(o.ProjectName))...)
}
const pluginName = "compose"
// PluginName is the name of the plugin
const PluginName = "compose"
// RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone() bool {
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != pluginName
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
}
// RootCommand returns the compose command with its child commands
@@ -230,7 +234,7 @@ func RootCommand(backend api.Service) *cobra.Command {
)
command := &cobra.Command{
Short: "Docker Compose",
Use: pluginName,
Use: PluginName,
TraverseChildren: true,
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
RunE: func(cmd *cobra.Command, args []string) error {
@@ -268,6 +272,12 @@ func RootCommand(backend api.Service) *cobra.Command {
logrus.SetLevel(logrus.TraceLevel)
}
formatter.SetANSIMode(ansi)
switch ansi {
case "never":
progress.Mode = progress.ModePlain
case "tty":
progress.Mode = progress.ModeTTY
}
if opts.WorkDir != "" {
if opts.ProjectDir != "" {
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)

View File

@@ -44,9 +44,11 @@ type convertOptions struct {
quiet bool
resolveImageDigests bool
noInterpolate bool
noNormalize bool
services bool
volumes bool
profiles bool
images bool
hash string
}
@@ -66,6 +68,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
os.Stdout = devnull
}
if p.Compatibility {
opts.noNormalize = true
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
@@ -81,6 +86,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
if opts.profiles {
return runProfiles(opts, args)
}
if opts.images {
return runConfigImages(opts, args)
}
return runConvert(ctx, backend, opts, args)
}),
@@ -91,10 +99,12 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
@@ -103,7 +113,12 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
var json []byte
project, err := opts.toProject(services, cli.WithInterpolation(!opts.noInterpolate), cli.WithResolvedPaths(true))
project, err := opts.toProject(services,
cli.WithInterpolation(!opts.noInterpolate),
cli.WithResolvedPaths(true),
cli.WithNormalization(!opts.noNormalize),
cli.WithDiscardEnvFile)
if err != nil {
return err
}
@@ -207,3 +222,18 @@ func runProfiles(opts convertOptions, services []string) error {
}
return nil
}
func runConfigImages(opts convertOptions, services []string) error {
project, err := opts.toProject(services)
if err != nil {
return err
}
for _, s := range project.Services {
if s.Image != "" {
fmt.Println(s.Image)
} else {
fmt.Printf("%s_%s\n", project.Name, s.Name)
}
}
return nil
}

View File

@@ -73,12 +73,12 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
projects, err := opts.toProject(nil)
name, err := opts.toProjectName()
if err != nil {
return err
}
return backend.Copy(ctx, projects, api.CopyOptions{
return backend.Copy(ctx, name, api.CopyOptions{
Source: opts.source,
Destination: opts.destination,
All: opts.all,

View File

@@ -19,10 +19,14 @@ package compose
import (
"context"
"fmt"
"os"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v2/pkg/api"
)
@@ -58,10 +62,19 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
ValidArgsFunction: noCompletion(),
}
flags := downCmd.Flags()
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS "))
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "volume":
name = "volumes"
logrus.Warn("--volume is deprecated, please use --volumes")
}
return pflag.NormalizedName(name)
})
return downCmd
}

View File

@@ -18,11 +18,8 @@ package compose
import (
"context"
"fmt"
"os"
"github.com/compose-spec/compose-go/types"
"github.com/containerd/console"
"github.com/docker/cli/cli"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
@@ -70,9 +67,14 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
runCmd.Flags().BoolP("interactive", "i", true, "Keep STDIN open even if not attached. DEPRECATED")
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY. DEPRECATED")
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
runCmd.Flags().SetInterspersed(false)
return runCmd
}
@@ -100,27 +102,8 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
Index: opts.index,
Detach: opts.detach,
WorkingDir: opts.workingDir,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if execOpts.Tty {
con := console.Current()
if err := con.SetRaw(); err != nil {
return err
}
defer func() {
if err := con.Reset(); err != nil {
fmt.Println("Unable to close the console")
}
}()
execOpts.Stdin = con
execOpts.Stdout = con
execOpts.Stderr = con
}
exitCode, err := backend.Exec(ctx, projectName, execOpts)
if exitCode != 0 {
errMsg := ""

View File

@@ -92,22 +92,24 @@ func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
view := viewFromStackList(stackList)
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
for _, stack := range view {
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
}
}, "NAME", "STATUS")
}, "NAME", "STATUS", "CONFIG FILES")
}
type stackView struct {
Name string
Status string
Name string
Status string
ConfigFiles string
}
func viewFromStackList(stackList []api.Stack) []stackView {
retList := make([]stackView, len(stackList))
for i, s := range stackList {
retList[i] = stackView{
Name: s.Name,
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
Name: s.Name,
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
ConfigFiles: s.ConfigFiles,
}
}
return retList

View File

@@ -59,7 +59,7 @@ func (p *psOptions) parseFilter() error {
case "source":
return api.ErrNotImplemented
default:
return fmt.Errorf("unknow filter %s", parts[0])
return fmt.Errorf("unknown filter %s", parts[0])
}
return nil
}

View File

@@ -59,7 +59,7 @@ Any data which is not in a volume will be lost.`,
}
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
project, err := opts.toProject(services)
project, err := opts.toProjectName()
if err != nil {
return err
}

View File

@@ -49,13 +49,13 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
project, err := opts.toProject(services)
projectName, err := opts.toProjectName()
if err != nil {
return err
}
timeout := time.Duration(opts.timeout) * time.Second
return backend.Restart(ctx, project, api.RestartOptions{
return backend.Restart(ctx, projectName, api.RestartOptions{
Timeout: &timeout,
Services: services,
})

View File

@@ -19,13 +19,11 @@ package compose
import (
"context"
"fmt"
"os"
"strings"
cgo "github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
"github.com/mattn/go-isatty"
"github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -43,6 +41,7 @@ type runOptions struct {
Detach bool
Remove bool
noTty bool
interactive bool
user string
workdir string
entrypoint string
@@ -54,6 +53,8 @@ type runOptions struct {
servicePorts bool
name string
noDeps bool
ignoreOrphans bool
quietPull bool
}
func (opts runOptions) apply(project *types.Project) error {
@@ -134,6 +135,8 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
if err != nil {
return err
}
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
return runRun(ctx, backend, project, opts)
}),
ValidArgsFunction: serviceCompletion(p),
@@ -143,7 +146,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
flags.BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
flags.BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
@@ -153,6 +156,11 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
cmd.Flags().MarkHidden("tty") //nolint:errcheck
flags.SetNormalizeFunc(normalizeRunFlags)
flags.SetInterspersed(false)
@@ -169,11 +177,6 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
return pflag.NormalizedName(name)
}
func notAtTTY() bool {
b := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
return !b
}
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
err := opts.apply(project)
if err != nil {
@@ -181,7 +184,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
}
err = progress.Run(ctx, func(ctx context.Context) error {
return startDependencies(ctx, backend, *project, opts.Service)
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
})
if err != nil {
return err
@@ -203,9 +206,6 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
Command: opts.Command,
Detach: opts.Detach,
AutoRemove: opts.Remove,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: !opts.noTty,
WorkingDir: opts.workdir,
User: opts.user,
@@ -215,7 +215,16 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
UseNetworkAliases: opts.useAliases,
NoDeps: opts.noDeps,
Index: 0,
QuietPull: opts.quietPull,
}
for i, service := range project.Services {
if service.Name == opts.Service {
service.StdinOpen = opts.interactive
project.Services[i] = service
}
}
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
if exitCode != 0 {
errMsg := ""
@@ -227,7 +236,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
return err
}
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
dependencies := types.Services{}
var requestedService types.ServiceConfig
for _, service := range project.Services {
@@ -240,8 +249,15 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
project.Services = dependencies
project.DisabledServices = append(project.DisabledServices, requestedService)
if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
err := backend.Create(ctx, &project, api.CreateOptions{
IgnoreOrphans: ignoreOrphans,
})
if err != nil {
return err
}
return backend.Start(ctx, &project, api.StartOptions{})
if len(dependencies) > 0 {
return backend.Start(ctx, project.Name, api.StartOptions{})
}
return nil
}

View File

@@ -43,10 +43,12 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
project, err := opts.toProject(services)
projectName, err := opts.toProjectName()
if err != nil {
return err
}
return backend.Start(ctx, project, api.StartOptions{})
return backend.Start(ctx, projectName, api.StartOptions{
AttachTo: services,
})
}

View File

@@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
project, err := opts.toProject(services)
projectName, err := opts.toProjectName()
if err != nil {
return err
}
@@ -63,7 +63,7 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue
}
return backend.Stop(ctx, project, api.StopOptions{
return backend.Stop(ctx, projectName, api.StopOptions{
Timeout: timeout,
Services: services,
})

View File

@@ -103,8 +103,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
return validateFlags(&up, &create)
}),
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
create.ignoreOrphans = strings.ToLower(ignore) == "true"
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
if create.ignoreOrphans && create.removeOrphans {
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
}

View File

@@ -18,6 +18,7 @@ package compose
import (
"fmt"
"strings"
"github.com/docker/compose/v2/cmd/formatter"
@@ -52,11 +53,11 @@ func versionCommand() *cobra.Command {
func runVersion(opts versionOptions) {
if opts.short {
fmt.Println(internal.Version)
fmt.Println(strings.TrimPrefix(internal.Version, "v"))
return
}
if opts.format == formatter.JSON {
fmt.Printf(`{"version":%q}\n`, internal.Version)
fmt.Printf("{\"version\":%q}\n", internal.Version)
return
}
fmt.Println("Docker Compose version", internal.Version)

View File

@@ -23,9 +23,9 @@ import (
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"github.com/docker/compose-switch/redirect"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/cmd/compatibility"
commands "github.com/docker/compose/v2/cmd/compose"
"github.com/docker/compose/v2/internal"
"github.com/docker/compose/v2/pkg/api"
@@ -46,7 +46,7 @@ func pluginMain() {
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
return err
}
lazyInit.WithService(compose.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile()))
lazyInit.WithService(compose.NewComposeService(dockerCli))
if originalPreRun != nil {
return originalPreRun(cmd, args)
}
@@ -69,7 +69,7 @@ func pluginMain() {
func main() {
if commands.RunningAsStandalone() {
os.Args = append([]string{"docker"}, redirect.Convert(os.Args[1:])...)
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
}
pluginMain()
}

View File

@@ -1,4 +1,54 @@
# docker compose
<!---MARKER_GEN_START-->
Docker Compose
### Subcommands
| Name | Description |
| --- | --- |
| [`build`](compose_build.md) | Build or rebuild services |
| [`convert`](compose_convert.md) | Converts the compose file to platform's canonical format |
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
| [`create`](compose_create.md) | Creates containers for a service. |
| [`down`](compose_down.md) | Stop and remove containers, networks |
| [`events`](compose_events.md) | Receive real time events from containers. |
| [`exec`](compose_exec.md) | Execute a command in a running container. |
| [`images`](compose_images.md) | List images used by the created containers |
| [`kill`](compose_kill.md) | Force stop service containers. |
| [`logs`](compose_logs.md) | View output from containers |
| [`ls`](compose_ls.md) | List running compose projects |
| [`pause`](compose_pause.md) | Pause services |
| [`port`](compose_port.md) | Print the public port for a port binding. |
| [`ps`](compose_ps.md) | List containers |
| [`pull`](compose_pull.md) | Pull service images |
| [`push`](compose_push.md) | Push service images |
| [`restart`](compose_restart.md) | Restart containers |
| [`rm`](compose_rm.md) | Removes stopped service containers |
| [`run`](compose_run.md) | Run a one-off command on a service. |
| [`start`](compose_start.md) | Start services |
| [`stop`](compose_stop.md) | Stop services |
| [`top`](compose_top.md) | Display the running processes |
| [`unpause`](compose_unpause.md) | Unpause services |
| [`up`](compose_up.md) | Create and start containers |
| [`version`](compose_version.md) | Show the Docker Compose version information |
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
| `--compatibility` | | | Run compose in backward compatibility mode |
| `--env-file` | `string` | | Specify an alternate environment file. |
| `-f`, `--file` | `stringArray` | | Compose configuration files |
| `--profile` | `stringArray` | | Specify a profile to enable |
| `--project-directory` | `string` | | Specify an alternate working directory
(default: the path of the Compose file) |
| `-p`, `--project-name` | `string` | | Project name |
<!---MARKER_GEN_END-->
## Description
@@ -9,8 +59,8 @@ multiple services in Docker containers.
Use the `-f` flag to specify the location of a Compose configuration file.
#### Specifying multiple Compose files
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
to their predecessors.
For example, consider this command line:
@@ -30,7 +80,7 @@ services:
volumes:
- "/data"
```
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
New values, add to the `webapp` service configuration.
```yaml
@@ -41,22 +91,22 @@ services:
- DEBUG=1
```
When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
with `-f`. You can use the `--project-directory` option to override this base path.
Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
configuration are relative to the current working directory.
The `-f` flag is optional. If you dont provide this flag on the command line, Compose traverses the working directory
The `-f` flag is optional. If you dont provide this flag on the command line, Compose traverses the working directory
and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
#### Specifying a path to a single Compose file
You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
```console
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
@@ -64,17 +114,17 @@ $ docker compose -f ~/sandbox/rails/compose.yaml pull db
### Use `-p` to specify a project name
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you dont
specify the flag, Compose uses the current directory name.
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you dont
specify the flag, Compose uses the current directory name.
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
Most compose subcommand can be ran without a compose file, just passing
Most compose subcommand can be ran without a compose file, just passing
project name to retrieve the relevant resources.
```console
$ docker compose -p my_project ps -a
NAME SERVICE STATUS PORTS
my_project_demo_1 demo running
my_project_demo_1 demo running
$ docker compose -p my_project logs
demo_1 | PING localhost (127.0.0.1): 56 data bytes
@@ -84,8 +134,8 @@ demo_1 | 64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.095 ms
### Use profiles to enable optional services
Use `--profile` to specify one or more active profiles
Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
without any specified profiles.
Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
without any specified profiles.
You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` will be enabled.
Profiles can also be set by `COMPOSE_PROFILES` environment variable.
@@ -99,3 +149,6 @@ Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
If flags are explicitly set on command line, associated environment variable is ignored
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
containers for the project.

View File

@@ -1,12 +1,29 @@
# docker compose build
<!---MARKER_GEN_START-->
Build or rebuild services
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
| `--no-cache` | | | Do not use cache when building the image |
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
| `--pull` | | | Always attempt to pull a newer version of the image. |
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
<!---MARKER_GEN_END-->
## Description
Services are built once and then tagged, by default as `project_service`.
Services are built once and then tagged, by default as `project_service`.
If the Compose file specifies an
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
the image is tagged with that name, substituting any variables beforehand. See
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
If you change a service's `Dockerfile` or the contents of its build directory,
If you change a service's `Dockerfile` or the contents of its build directory,
run `docker compose build` to rebuild it.

View File

@@ -1,9 +1,35 @@
# docker compose convert
<!---MARKER_GEN_START-->
Converts the compose file to platform's canonical format
### Aliases
`convert`, `config`
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
| `--hash` | `string` | | Print the service config hash, one per line. |
| `--images` | | | Print the image names, one per line. |
| `--no-interpolate` | | | Don't interpolate environment variables. |
| `--no-normalize` | | | Don't normalize compose model. |
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
| `--profiles` | | | Print the profile names, one per line. |
| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
| `--resolve-image-digests` | | | Pin image tags to digests. |
| `--services` | | | Print the service names, one per line. |
| `--volumes` | | | Print the volume names, one per line. |
<!---MARKER_GEN_END-->
## Description
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
fully defined Compose model.
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
fully defined Compose model.
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`

View File

@@ -0,0 +1,17 @@
# docker compose cp
<!---MARKER_GEN_START-->
Copy files/folders between a service container and the local filesystem
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--all` | | | Copy to all the containers of the service. |
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
| `--index` | `int` | `1` | Index of the container if there are multiple instances of a service [default: 1]. |
<!---MARKER_GEN_END-->

View File

@@ -0,0 +1,17 @@
# docker compose create
<!---MARKER_GEN_START-->
Creates containers for a service.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--build` | | | Build images before starting containers. |
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
| `--no-build` | | | Don't build an image, even if it's missing. |
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
<!---MARKER_GEN_END-->

View File

@@ -1,4 +1,19 @@
# docker compose down
<!---MARKER_GEN_START-->
Stop and remove containers, networks
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
| `-v`, `--volumes` | | | Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers. |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,16 @@
# docker compose events
<!---MARKER_GEN_START-->
Receive real time events from containers.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--json` | | | Output events as a stream of json objects |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,7 +1,26 @@
# docker compose exec
<!---MARKER_GEN_START-->
Execute a command in a running container.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
| `--privileged` | | | Give extended privileges to the process. |
| `-u`, `--user` | `string` | | Run the command as this user. |
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
<!---MARKER_GEN_END-->
## Description
This is the equivalent of `docker exec` targeting a Compose service.
This is the equivalent of `docker exec` targeting a Compose service.
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
you can use a command such as `docker compose exec web sh` to get an interactive prompt.

View File

@@ -0,0 +1,14 @@
# docker compose images
<!---MARKER_GEN_START-->
List images used by the created containers
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-q`, `--quiet` | | | Only display IDs |
<!---MARKER_GEN_END-->

View File

@@ -1,3 +1,16 @@
# docker compose kill
<!---MARKER_GEN_START-->
Force stop service containers.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,22 @@
# docker compose logs
<!---MARKER_GEN_START-->
View output from containers
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-f`, `--follow` | | | Follow log output. |
| `--no-color` | | | Produce monochrome output. |
| `--no-log-prefix` | | | Don't print prefix in logs. |
| `--since` | `string` | | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
| `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. |
| `-t`, `--timestamps` | | | Show timestamps. |
| `--until` | `string` | | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,19 @@
# docker compose ls
<!---MARKER_GEN_START-->
List running compose projects
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-a`, `--all` | | | Show all stopped Compose projects |
| `--filter` | `filter` | | Filter output based on conditions provided. |
| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json]. |
| `-q`, `--quiet` | | | Only display IDs. |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,10 @@
# docker compose pause
<!---MARKER_GEN_START-->
Pause services
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,17 @@
# docker compose port
<!---MARKER_GEN_START-->
Print the public port for a port binding.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--index` | `int` | `1` | index of the container if service has multiple replicas |
| `--protocol` | `string` | `tcp` | tcp or udp |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,20 @@
# docker compose ps
<!---MARKER_GEN_START-->
List containers
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json] |
| `-q`, `--quiet` | | | Only display IDs |
| `--services` | | | Display services |
| `--status` | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
<!---MARKER_GEN_END-->
## Description
@@ -7,5 +24,5 @@ Lists containers for a Compose project, with current status and exposed ports.
$ docker compose ps
NAME SERVICE STATUS PORTS
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
example_bar_1 bar exited (1)
example_bar_1 bar exited (1)
```

View File

@@ -1,11 +1,26 @@
# docker compose pull
<!---MARKER_GEN_START-->
Pull service images
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures |
| `--include-deps` | | | Also pull services declared as dependencies |
| `-q`, `--quiet` | | | Pull without printing progress information |
<!---MARKER_GEN_END-->
## Description
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
those images.
## Examples
## Examples
suppose you have this `compose.yaml`:
@@ -24,8 +39,8 @@ services:
- db
```
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
you would run `docker compose pull db`.
```console
@@ -46,4 +61,4 @@ $ docker compose pull db
⠹ f63c47038e66 Waiting 9.3s
⠹ 77a0c198cde5 Waiting 9.3s
⠹ c8752d5b785c Waiting 9.3s
`
``

View File

@@ -1,3 +1,16 @@
# docker compose push
<!---MARKER_GEN_START-->
Push service images
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,8 +1,24 @@
# docker compose restart
<!---MARKER_GEN_START-->
Restart containers
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
<!---MARKER_GEN_END-->
## Description
Restarts all stopped and running services.
If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added
after a container is built, but before the container's command is executed) are not updated
If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added
after a container is built, but before the container's command is executed) are not updated
after restarting.
If you are looking to configure a service's restart policy, please refer to

View File

@@ -1,3 +1,23 @@
# docker compose rm
<!---MARKER_GEN_START-->
Removes stopped service containers
By default, anonymous volumes attached to containers will not be removed. You
can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume will be lost.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-f`, `--force` | | | Don't ask to confirm removal |
| `-s`, `--stop` | | | Stop the containers, if required, before removing |
| `-v`, `--volumes` | | | Remove any anonymous volumes attached to containers |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,7 +1,35 @@
# docker compose run
<!---MARKER_GEN_START-->
Run a one-off command on a service.
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-d`, `--detach` | | | Run container in background and print container ID |
| `--entrypoint` | `string` | | Override the entrypoint of the image |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
| `-l`, `--label` | `stringArray` | | Add or override a label |
| `--name` | `string` | | Assign a name to the container |
| `-T`, `--no-TTY` | | | Disable pseudo-noTty allocation. By default docker compose run allocates a TTY |
| `--no-deps` | | | Don't start linked services. |
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
| `--quiet-pull` | | | Pull without printing progress information. |
| `--rm` | | | Automatically remove the container when it exits |
| `--service-ports` | | | Run command with the service's ports enabled and mapped to the host. |
| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to. |
| `-u`, `--user` | `string` | | Run as specified username or uid |
| `-v`, `--volume` | `stringArray` | | Bind mount a volume. |
| `-w`, `--workdir` | `string` | | Working directory inside the container |
<!---MARKER_GEN_END-->
## Description
Runs a one-time command against a service.
Runs a one-time command against a service.
the following command starts the `web` service and runs `bash` as its command:
@@ -12,12 +40,12 @@ $ docker compose run web bash
Commands you use with run start in new containers with configuration defined by that of the service,
including volumes, links, and other details. However, there are two important differences:
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
`python app.py`.
The second difference is that the `docker compose run` command does not create any of the ports specified in the
service configuration. This prevents port collisions with already-open ports. If you do want the services ports
The second difference is that the `docker compose run` command does not create any of the ports specified in the
service configuration. This prevents port collisions with already-open ports. If you do want the services ports
to be created and mapped to the host, specify the `--service-ports`
```console
@@ -30,8 +58,8 @@ Alternatively, manual port mapping can be specified with the `--publish` or `-p`
$ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
```
If you start a service configured with links, the run command first checks to see if the linked service is running
and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
If you start a service configured with links, the run command first checks to see if the linked service is running
and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
passed it. For example, you could run:
```console
@@ -52,5 +80,5 @@ If you want to remove the container after running while overriding the container
$ docker compose run --rm web python manage.py db upgrade
```
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
specified in the service configuration.

View File

@@ -1,3 +1,10 @@
# docker compose start
<!---MARKER_GEN_START-->
Start services
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,16 @@
# docker compose stop
<!---MARKER_GEN_START-->
Stop services
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,10 @@
# docker compose top
<!---MARKER_GEN_START-->
Display the running processes
<!---MARKER_GEN_END-->
## Description
@@ -9,5 +16,5 @@ Displays the running processes.
$ docker compose top
example_foo_1
UID PID PPID C STIME TTY TIME CMD
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
```

View File

@@ -1,3 +1,10 @@
# docker compose unpause
<!---MARKER_GEN_START-->
Unpause services
<!---MARKER_GEN_END-->
## Description

View File

@@ -1,3 +1,35 @@
# docker compose up
<!---MARKER_GEN_START-->
Create and start containers
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
| `--attach` | `stringArray` | | Attach to service output. |
| `--attach-dependencies` | | | Attach to dependent containers. |
| `--build` | | | Build images before starting containers. |
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
| `--no-build` | | | Don't build an image, even if it's missing. |
| `--no-color` | | | Produce monochrome output. |
| `--no-deps` | | | Don't start linked services. |
| `--no-log-prefix` | | | Don't print prefix in logs. |
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--no-start` | | | Don't start the services after creating them. |
| `--quiet-pull` | | | Pull without printing progress information. |
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. |
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
| `-t`, `--timeout` | `int` | `10` | Use this timeout in seconds for container shutdown when attached or when containers are already running. |
| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
<!---MARKER_GEN_END-->
## Description
@@ -5,12 +37,12 @@ Builds, (re)creates, starts, and attaches to containers for a service.
Unless they are already running, this command also starts any linked services.
The `docker compose up` command aggregates the output of each container (liked `docker compose logs --follow` does).
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
background and leaves them running.
If there are existing containers for a service, and the services configuration or image was changed after the
containers creation, `docker compose up` picks up the changes by stopping and recreating the containers
If there are existing containers for a service, and the services configuration or image was changed after the
containers creation, `docker compose up` picks up the changes by stopping and recreating the containers
(preserving mounted volumes). To prevent Compose from picking up changes, use the `--no-recreate` flag.
If you want to force Compose to stop and recreate all containers, use the `--force-recreate` flag.

View File

@@ -0,0 +1,14 @@
# docker compose version
<!---MARKER_GEN_START-->
Show the Docker Compose version information
### Options
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-f`, `--format` | `string` | | Format the output. Values: [pretty \| json]. (Default: pretty) |
| `--short` | | | Shows only Compose's version number. |
<!---MARKER_GEN_END-->

View File

@@ -98,6 +98,9 @@ long: |-
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
If flags are explicitly set on command line, associated environment variable is ignored
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
containers for the project.
usage: docker compose
pname: docker
plink: docker.yaml
@@ -126,6 +129,7 @@ cname:
- docker compose top
- docker compose unpause
- docker compose up
- docker compose version
clink:
- docker_compose_build.yaml
- docker_compose_convert.yaml
@@ -151,6 +155,7 @@ clink:
- docker_compose_top.yaml
- docker_compose_unpause.yaml
- docker_compose_up.yaml
- docker_compose_version.yaml
options:
- option: ansi
value_type: string
@@ -158,6 +163,17 @@ options:
description: |
Control when to print ANSI control characters ("never"|"always"|"auto")
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: compatibility
value_type: bool
default_value: "false"
description: Run compose in backward compatibility mode
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -166,6 +182,7 @@ options:
value_type: string
description: Specify an alternate environment file.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -176,6 +193,7 @@ options:
default_value: '[]'
description: Compose configuration files
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -185,6 +203,7 @@ options:
default_value: "false"
description: Do not print ANSI control characters (DEPRECATED)
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -194,6 +213,7 @@ options:
default_value: '[]'
description: Specify a profile to enable
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -204,6 +224,7 @@ options:
Specify an alternate working directory
(default: the path of the Compose file)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -213,6 +234,7 @@ options:
value_type: string
description: Project name
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -222,6 +244,18 @@ options:
default_value: "false"
description: Show more output
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: version
shorthand: v
value_type: bool
default_value: "false"
description: Show the Docker Compose version information
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -233,6 +267,7 @@ options:
Specify an alternate working directory
(default: the path of the Compose file)
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -19,6 +19,7 @@ options:
default_value: '[]'
description: Set build-time variables for services.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -28,6 +29,7 @@ options:
default_value: "true"
description: Compress the build context using gzip. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -37,6 +39,7 @@ options:
default_value: "true"
description: Always remove intermediate containers. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -47,6 +50,7 @@ options:
description: |
Set memory limit for the build container. Not supported on buildkit yet.
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -56,6 +60,7 @@ options:
default_value: "false"
description: Do not use cache when building the image
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -66,6 +71,7 @@ options:
description: |
Do not remove intermediate containers after a successful build. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -75,6 +81,7 @@ options:
default_value: "true"
description: Build images in parallel. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -82,8 +89,9 @@ options:
- option: progress
value_type: string
default_value: auto
description: Set type of progress output ("auto", "plain", "noTty")
description: Set type of progress output (auto, tty, plain, quiet)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -93,6 +101,7 @@ options:
default_value: "false"
description: Always attempt to pull a newer version of the image.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -103,6 +112,7 @@ options:
default_value: "false"
description: Don't print anything to STDOUT
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -16,6 +16,7 @@ options:
default_value: yaml
description: 'Format the output. Values: [yaml | json]'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -24,6 +25,17 @@ options:
value_type: string
description: Print the service config hash, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: images
value_type: bool
default_value: "false"
description: Print the image names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -33,6 +45,27 @@ options:
default_value: "false"
description: Don't interpolate environment variables.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: no-normalize
value_type: bool
default_value: "false"
description: Don't normalize compose model.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: output
shorthand: o
value_type: string
description: Save to file (default to stdout)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -42,6 +75,7 @@ options:
default_value: "false"
description: Print the profile names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -52,6 +86,7 @@ options:
default_value: "false"
description: Only validate the configuration, don't print anything.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -61,6 +96,7 @@ options:
default_value: "false"
description: Pin image tags to digests.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -70,6 +106,7 @@ options:
default_value: "false"
description: Print the service names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -79,6 +116,7 @@ options:
default_value: "false"
description: Print the volume names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -11,6 +11,7 @@ options:
default_value: "false"
description: Copy to all the containers of the service.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -21,6 +22,7 @@ options:
default_value: "false"
description: Archive mode (copy all uid/gid information)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -31,6 +33,7 @@ options:
default_value: "false"
description: Always follow symbol link in SRC_PATH
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -41,6 +44,7 @@ options:
description: |
Index of the container if there are multiple instances of a service [default: 1].
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -10,6 +10,7 @@ options:
default_value: "false"
description: Build images before starting containers.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -20,6 +21,7 @@ options:
description: |
Recreate containers even if their configuration and image haven't changed.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -29,6 +31,7 @@ options:
default_value: "false"
description: Don't build an image, even if it's missing.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -39,6 +42,7 @@ options:
description: |
If containers already exist, don't recreate them. Incompatible with --force-recreate.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -23,6 +23,7 @@ options:
default_value: "false"
description: Remove containers for services not defined in the Compose file.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -32,6 +33,7 @@ options:
description: |
Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -42,6 +44,7 @@ options:
default_value: "10"
description: Specify a shutdown timeout in seconds
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -53,6 +56,7 @@ options:
description: |
Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -29,6 +29,7 @@ options:
default_value: "false"
description: Output events as a stream of json objects
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -15,6 +15,7 @@ options:
default_value: "false"
description: 'Detached mode: Run command in the background.'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -25,6 +26,7 @@ options:
default_value: '[]'
description: Set environment variables
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -35,6 +37,18 @@ options:
description: |
index of the container if there are multiple instances of a service [default: 1].
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: interactive
shorthand: i
value_type: bool
default_value: "true"
description: Keep STDIN open even if not attached. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -46,6 +60,7 @@ options:
description: |
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -55,6 +70,18 @@ options:
default_value: "false"
description: Give extended privileges to the process.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: tty
shorthand: t
value_type: bool
default_value: "true"
description: Allocate a pseudo-TTY. DEPRECATED
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -64,6 +91,7 @@ options:
value_type: string
description: Run the command as this user.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -73,6 +101,7 @@ options:
value_type: string
description: Path to workdir directory for this command.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -11,6 +11,7 @@ options:
default_value: "false"
description: Only display IDs
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -16,6 +16,7 @@ options:
default_value: SIGKILL
description: SIGNAL to send to the container.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -11,6 +11,7 @@ options:
default_value: "false"
description: Follow log output.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -20,6 +21,7 @@ options:
default_value: "false"
description: Produce monochrome output.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -29,6 +31,7 @@ options:
default_value: "false"
description: Don't print prefix in logs.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -38,6 +41,7 @@ options:
description: |
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -48,6 +52,7 @@ options:
description: |
Number of lines to show from the end of the logs for each container.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -58,6 +63,7 @@ options:
default_value: "false"
description: Show timestamps.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -67,6 +73,7 @@ options:
description: |
Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -11,6 +11,7 @@ options:
default_value: "false"
description: Show all stopped Compose projects
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -19,6 +20,7 @@ options:
value_type: filter
description: Filter output based on conditions provided.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -28,6 +30,7 @@ options:
default_value: pretty
description: 'Format the output. Values: [pretty | json].'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -38,6 +41,7 @@ options:
default_value: "false"
description: Only display IDs.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -1,5 +1,5 @@
command: docker compose pause
short: pause services
short: Pause services
long: |
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
usage: docker compose pause [SERVICE...]

View File

@@ -10,6 +10,7 @@ options:
default_value: "1"
description: index of the container if service has multiple replicas
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -19,6 +20,7 @@ options:
default_value: tcp
description: tcp or udp
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -20,14 +20,16 @@ options:
description: |
Show all stopped containers (including those created by the run command)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: filter
value_type: string
description: Filter services by a property
description: Filter services by a property. Deprecated, use --status instead
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -37,6 +39,7 @@ options:
default_value: pretty
description: 'Format the output. Values: [pretty | json]'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -47,6 +50,7 @@ options:
default_value: "false"
description: Only display IDs
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -56,14 +60,18 @@ options:
default_value: "false"
description: Display services
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: status
value_type: string
description: Filter services by status
value_type: stringArray
default_value: '[]'
description: |
Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -12,6 +12,7 @@ options:
default_value: "false"
description: Pull what it can and ignores images with pull failures
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -21,6 +22,7 @@ options:
default_value: "false"
description: Also pull services declared as dependencies
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -30,6 +32,7 @@ options:
default_value: "true"
description: DEPRECATED disable parallel pulling.
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -39,6 +42,7 @@ options:
default_value: "true"
description: DEPRECATED pull multiple images in parallel.
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -49,10 +53,52 @@ options:
default_value: "false"
description: Pull without printing progress information
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
examples: |-
suppose you have this `compose.yaml`:
```yaml
services:
db:
image: postgres
web:
build: .
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
- db
```
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
you would run `docker compose pull db`.
```console
$ docker compose pull db
[+] Running 1/15
⠸ db Pulling 12.4s
⠿ 45b42c59be33 Already exists 0.0s
⠹ 40adec129f1a Downloading 3.374MB/4.178MB 9.3s
⠹ b4c431d00c78 Download complete 9.3s
⠹ 2696974e2815 Download complete 9.3s
⠹ 564b77596399 Downloading 5.622MB/7.965MB 9.3s
⠹ 5044045cf6f2 Downloading 216.7kB/391.1kB 9.3s
⠹ d736e67e6ac3 Waiting 9.3s
⠹ 390c1c9a5ae4 Waiting 9.3s
⠹ c0e62f172284 Waiting 9.3s
⠹ ebcdc659c5bf Waiting 9.3s
⠹ 29be22cb3acc Waiting 9.3s
⠹ f63c47038e66 Waiting 9.3s
⠹ 77a0c198cde5 Waiting 9.3s
⠹ c8752d5b785c Waiting 9.3s
``̀`
deprecated: false
experimental: false
experimentalcli: false

View File

@@ -28,6 +28,7 @@ options:
default_value: "false"
description: Push what it can and ignores images with push failures
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -1,6 +1,16 @@
command: docker compose restart
short: Restart containers
long: Restart containers
long: |-
Restarts all stopped and running services.
If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added
after a container is built, but before the container's command is executed) are not updated
after restarting.
If you are looking to configure a service's restart policy, please refer to
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
usage: docker compose restart
pname: docker compose
plink: docker_compose.yaml
@@ -11,6 +21,7 @@ options:
default_value: "10"
description: Specify a shutdown timeout in seconds
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -26,6 +26,7 @@ options:
default_value: "false"
description: Deprecated - no effect
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -36,6 +37,7 @@ options:
default_value: "false"
description: Don't ask to confirm removal
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -46,6 +48,7 @@ options:
default_value: "false"
description: Stop the containers, if required, before removing
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -56,6 +59,7 @@ options:
default_value: "false"
description: Remove any anonymous volumes attached to containers
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -65,6 +65,7 @@ options:
default_value: "false"
description: Run container in background and print container ID
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -73,6 +74,7 @@ options:
value_type: string
description: Override the entrypoint of the image
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -83,16 +85,29 @@ options:
default_value: '[]'
description: Set environment variables
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: labels
- option: interactive
shorthand: i
value_type: bool
default_value: "true"
description: Keep STDIN open even if not attached.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: label
shorthand: l
value_type: stringArray
default_value: '[]'
description: Add or override a label
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -101,6 +116,7 @@ options:
value_type: string
description: Assign a name to the container
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -112,6 +128,7 @@ options:
description: |
Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -121,6 +138,7 @@ options:
default_value: "false"
description: Don't start linked services.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -131,6 +149,17 @@ options:
default_value: '[]'
description: Publish a container's port(s) to the host.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet-pull
value_type: bool
default_value: "false"
description: Pull without printing progress information.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -140,6 +169,7 @@ options:
default_value: "false"
description: Automatically remove the container when it exits
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -150,6 +180,18 @@ options:
description: |
Run command with the service's ports enabled and mapped to the host.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: tty
shorthand: t
value_type: bool
default_value: "true"
description: Allocate a pseudo-TTY.
deprecated: false
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -160,6 +202,7 @@ options:
description: |
Use the service's network useAliases in the network(s) the container connects to.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -169,6 +212,7 @@ options:
value_type: string
description: Run as specified username or uid
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -179,6 +223,7 @@ options:
default_value: '[]'
description: Bind mount a volume.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -188,6 +233,7 @@ options:
value_type: string
description: Working directory inside the container
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -12,6 +12,7 @@ options:
default_value: "10"
description: Specify a shutdown timeout in seconds
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -1,5 +1,5 @@
command: docker compose unpause
short: unpause services
short: Unpause services
long: Unpauses paused containers of a service.
usage: docker compose unpause [SERVICE...]
pname: docker compose

View File

@@ -5,7 +5,7 @@ long: |-
Unless they are already running, this command also starts any linked services.
The `docker compose up` command aggregates the output of each container (liked `docker compose logs --follow` does).
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
background and leaves them running.
@@ -27,6 +27,7 @@ options:
description: |
Stops all containers if any container was stopped. Incompatible with -d
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -36,6 +37,7 @@ options:
default_value: "false"
description: Recreate dependent containers. Incompatible with --no-recreate.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -45,6 +47,7 @@ options:
default_value: '[]'
description: Attach to service output.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -54,6 +57,7 @@ options:
default_value: "false"
description: Attach to dependent containers.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -63,6 +67,7 @@ options:
default_value: "false"
description: Build images before starting containers.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -73,16 +78,7 @@ options:
default_value: "false"
description: 'Detached mode: Run containers in the background'
deprecated: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: environment
shorthand: e
value_type: stringArray
default_value: '[]'
description: Environment variables
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -92,6 +88,7 @@ options:
description: |
Return the exit code of the selected service container. Implies --abort-on-container-exit
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -102,6 +99,7 @@ options:
description: |
Recreate containers even if their configuration and image haven't changed.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -111,6 +109,7 @@ options:
default_value: "false"
description: Don't build an image, even if it's missing.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -120,6 +119,7 @@ options:
default_value: "false"
description: Produce monochrome output.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -129,6 +129,7 @@ options:
default_value: "false"
description: Don't start linked services.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -138,6 +139,7 @@ options:
default_value: "false"
description: Don't print prefix in logs.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -148,6 +150,7 @@ options:
description: |
If containers already exist, don't recreate them. Incompatible with --force-recreate.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -157,6 +160,7 @@ options:
default_value: "false"
description: Don't start the services after creating them.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -166,6 +170,7 @@ options:
default_value: "false"
description: Pull without printing progress information.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -175,6 +180,7 @@ options:
default_value: "false"
description: Remove containers for services not defined in the Compose file.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -186,6 +192,7 @@ options:
description: |
Recreate anonymous volumes instead of retrieving data from the previous containers.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -196,6 +203,7 @@ options:
description: |
Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -207,6 +215,17 @@ options:
description: |
Use this timeout in seconds for container shutdown when attached or when containers are already running.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: wait
value_type: bool
default_value: "false"
description: Wait for services to be running|healthy. Implies detached mode.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -10,6 +10,7 @@ options:
value_type: string
description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -19,6 +20,7 @@ options:
default_value: "false"
description: Shows only Compose's version number.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false

View File

@@ -26,13 +26,24 @@ import (
"github.com/spf13/cobra"
)
func generateCliYaml(opts *options) error {
cmd := &cobra.Command{Use: "docker"}
func generateDocs(opts *options) error {
cmd := &cobra.Command{
Use: "docker",
DisableAutoGenTag: true,
}
cmd.AddCommand(compose.RootCommand(nil))
disableFlagsInUseLine(cmd)
cmd.DisableAutoGenTag = true
return clidocstool.GenYamlTree(cmd, opts.target)
tool, err := clidocstool.New(clidocstool.Options{
Root: cmd,
SourceDir: opts.source,
TargetDir: opts.target,
Plugin: true,
})
if err != nil {
return err
}
return tool.GenAllTree()
}
func disableFlagsInUseLine(cmd *cobra.Command) {
@@ -60,12 +71,12 @@ type options struct {
func main() {
cwd, _ := os.Getwd()
opts := &options{
source: cwd,
source: filepath.Join(cwd, "docs", "reference"),
target: filepath.Join(cwd, "docs", "reference"),
}
fmt.Printf("Project root: %s\n", opts.source)
fmt.Printf("Generating yaml files into %s\n", opts.target)
if err := generateCliYaml(opts); err != nil {
fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error())
if err := generateDocs(opts); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to generate documentation: %s\n", err.Error())
}
}

139
go.mod
View File

@@ -3,129 +3,142 @@ module github.com/docker/compose/v2
go 1.17
require (
github.com/AlecAivazis/survey/v2 v2.2.3
github.com/buger/goterm v1.0.0
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/buger/goterm v1.0.4
github.com/cnabio/cnab-to-oci v0.3.1-beta1
github.com/compose-spec/compose-go v1.0.5
github.com/containerd/console v1.0.2
github.com/containerd/containerd v1.5.8
github.com/compose-spec/compose-go v1.1.0
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.1
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
github.com/docker/cli v20.10.7+incompatible
github.com/docker/cli-docs-tool v0.1.1
github.com/docker/compose-switch v1.0.2
github.com/docker/buildx v0.7.1
github.com/docker/cli v20.10.12+incompatible
github.com/docker/cli-docs-tool v0.4.0
github.com/docker/docker v20.10.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/golang/mock v1.5.0
github.com/hashicorp/go-multierror v1.1.0
github.com/golang/mock v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.3.0
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-shellwords v1.0.12
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf
github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/image-spec v1.0.2
github.com/pkg/errors v0.9.1
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/theupdateframework/notary v0.6.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.0.3
gotest.tools/v3 v3.1.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Microsoft/hcsshim v0.8.23 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
github.com/compose-spec/godotenv v1.1.0 // indirect
github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/containerd/continuity v0.2.2 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofrs/flock v0.8.0 // indirect
github.com/gogo/googleapis v1.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.11.13 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/klauspost/compress v1.13.5 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mount v0.2.0 // indirect
github.com/moby/sys/mountinfo v0.4.1 // indirect
github.com/moby/sys/symlink v0.1.0 // indirect
github.com/moby/sys/mountinfo v0.5.0 // indirect
github.com/moby/sys/signal v0.6.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/runc v1.1.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.7.1 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.10.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/qri-io/jsonpointer v0.1.0 // indirect
github.com/qri-io/jsonschema v0.1.1 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
go.opentelemetry.io/contrib v0.21.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
go.opentelemetry.io/otel v1.3.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
go.opentelemetry.io/otel/metric v0.21.0 // indirect
go.opentelemetry.io/otel/sdk v1.3.0 // indirect
go.opentelemetry.io/otel/trace v1.3.0 // indirect
go.opentelemetry.io/proto/otlp v0.11.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apimachinery v0.21.0 // indirect
k8s.io/client-go v0.21.0 // indirect
k8s.io/klog/v2 v2.8.0 // indirect
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
k8s.io/apimachinery v0.22.5 // indirect
k8s.io/client-go v0.22.5 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
// (for buildx)
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
replace (
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939
)

971
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@ package api
import (
"context"
"fmt"
"io"
"strings"
"time"
@@ -37,11 +36,11 @@ type Service interface {
// Create executes the equivalent to a `compose create`
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
// Start executes the equivalent to a `compose start`
Start(ctx context.Context, project *types.Project, options StartOptions) error
Start(ctx context.Context, projectName string, options StartOptions) error
// Restart restarts containers
Restart(ctx context.Context, project *types.Project, options RestartOptions) error
Restart(ctx context.Context, projectName string, options RestartOptions) error
// Stop executes the equivalent to a `compose stop`
Stop(ctx context.Context, project *types.Project, options StopOptions) error
Stop(ctx context.Context, projectName string, options StopOptions) error
// Up executes the equivalent to a `compose up`
Up(ctx context.Context, project *types.Project, options UpOptions) error
// Down executes the equivalent to a `compose down`
@@ -59,11 +58,11 @@ type Service interface {
// RunOneOffContainer creates a service oneoff container and starts its dependencies
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
// Remove executes the equivalent to a `compose rm`
Remove(ctx context.Context, project *types.Project, options RemoveOptions) error
Remove(ctx context.Context, project string, options RemoveOptions) error
// Exec executes a command in a running service container
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
// Copy copies a file/folder between a service container and the local filesystem
Copy(ctx context.Context, project *types.Project, opts CopyOptions) error
Copy(ctx context.Context, project string, options CopyOptions) error
// Pause executes the equivalent to a `compose pause`
Pause(ctx context.Context, project string, options PauseOptions) error
// UnPause executes the equivalent to a `compose unpause`
@@ -216,9 +215,6 @@ type RunOptions struct {
Entrypoint []string
Detach bool
AutoRemove bool
Stdin io.ReadCloser
Stdout io.WriteCloser
Stderr io.WriteCloser
Tty bool
WorkingDir string
User string
@@ -227,6 +223,8 @@ type RunOptions struct {
Privileged bool
UseNetworkAliases bool
NoDeps bool
// QuietPull makes the pulling process quiet
QuietPull bool
// used by exec
Index int
}
@@ -402,10 +400,11 @@ const (
// Stack holds the name and state of a compose application/stack
type Stack struct {
ID string
Name string
Status string
Reason string
ID string
Name string
Status string
ConfigFiles string
Reason string
}
// LogConsumer is a callback to process log messages from services
@@ -434,6 +433,8 @@ const (
ContainerEventLog = iota
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
ContainerEventAttach
// ContainerEventStopped is a ContainerEvent of type stopped.
ContainerEventStopped
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
ContainerEventExit
// UserCancel user cancelled compose up, we are stopping containers

View File

@@ -28,9 +28,9 @@ type ServiceProxy struct {
PushFn func(ctx context.Context, project *types.Project, options PushOptions) error
PullFn func(ctx context.Context, project *types.Project, opts PullOptions) error
CreateFn func(ctx context.Context, project *types.Project, opts CreateOptions) error
StartFn func(ctx context.Context, project *types.Project, options StartOptions) error
RestartFn func(ctx context.Context, project *types.Project, options RestartOptions) error
StopFn func(ctx context.Context, project *types.Project, options StopOptions) error
StartFn func(ctx context.Context, projectName string, options StartOptions) error
RestartFn func(ctx context.Context, projectName string, options RestartOptions) error
StopFn func(ctx context.Context, projectName string, options StopOptions) error
UpFn func(ctx context.Context, project *types.Project, options UpOptions) error
DownFn func(ctx context.Context, projectName string, options DownOptions) error
LogsFn func(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
@@ -39,9 +39,9 @@ type ServiceProxy struct {
ConvertFn func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
KillFn func(ctx context.Context, project *types.Project, options KillOptions) error
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
RemoveFn func(ctx context.Context, project *types.Project, options RemoveOptions) error
RemoveFn func(ctx context.Context, project string, options RemoveOptions) error
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
CopyFn func(ctx context.Context, project *types.Project, opts CopyOptions) error
CopyFn func(ctx context.Context, project string, options CopyOptions) error
PauseFn func(ctx context.Context, project string, options PauseOptions) error
UnPauseFn func(ctx context.Context, project string, options PauseOptions) error
TopFn func(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
@@ -141,36 +141,27 @@ func (s *ServiceProxy) Create(ctx context.Context, project *types.Project, optio
}
// Start implements Service interface
func (s *ServiceProxy) Start(ctx context.Context, project *types.Project, options StartOptions) error {
func (s *ServiceProxy) Start(ctx context.Context, projectName string, options StartOptions) error {
if s.StartFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.StartFn(ctx, project, options)
return s.StartFn(ctx, projectName, options)
}
// Restart implements Service interface
func (s *ServiceProxy) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
func (s *ServiceProxy) Restart(ctx context.Context, projectName string, options RestartOptions) error {
if s.RestartFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.RestartFn(ctx, project, options)
return s.RestartFn(ctx, projectName, options)
}
// Stop implements Service interface
func (s *ServiceProxy) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
func (s *ServiceProxy) Stop(ctx context.Context, projectName string, options StopOptions) error {
if s.StopFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.StopFn(ctx, project, options)
return s.StopFn(ctx, projectName, options)
}
// Up implements Service interface
@@ -250,13 +241,10 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
}
// Remove implements Service interface
func (s *ServiceProxy) Remove(ctx context.Context, project *types.Project, options RemoveOptions) error {
func (s *ServiceProxy) Remove(ctx context.Context, project string, options RemoveOptions) error {
if s.RemoveFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.RemoveFn(ctx, project, options)
}
@@ -269,13 +257,10 @@ func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOpti
}
// Copy implements Service interface
func (s *ServiceProxy) Copy(ctx context.Context, project *types.Project, options CopyOptions) error {
func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
if s.CopyFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.CopyFn(ctx, project, options)
}

View File

@@ -137,7 +137,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
var stdout io.ReadCloser
var stdin io.WriteCloser
cnx, err := s.apiClient.ContainerAttach(ctx, container, moby.ContainerAttachOptions{
cnx, err := s.apiClient().ContainerAttach(ctx, container, moby.ContainerAttachOptions{
Stream: true,
Stdin: true,
Stdout: true,
@@ -151,7 +151,7 @@ func (s *composeService) getContainerStreams(ctx context.Context, container stri
}
// Fallback to logs API
logs, err := s.apiClient.ContainerLogs(ctx, container, moby.ContainerLogsOptions{
logs, err := s.apiClient().ContainerLogs(ctx, container, moby.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,

View File

@@ -19,8 +19,6 @@ package compose
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"github.com/compose-spec/compose-go/types"
@@ -142,7 +140,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
if project.Services[i].Labels == nil {
project.Services[i].Labels = types.Labels{}
}
project.Services[i].Labels[api.ImageDigestLabel] = digest
project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
project.Services[i].Image = image
}
}
@@ -194,7 +192,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
}
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
ping, err := s.apiClient.Ping(ctx)
ping, err := s.apiClient().Ping(ctx)
if err != nil {
return command.ServerInfo{}, err
}
@@ -259,7 +257,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
NetworkMode: service.Build.Network,
ExtraHosts: service.Build.ExtraHosts,
Session: []session.Attachable{
authprovider.NewDockerAuthProvider(os.Stderr),
authprovider.NewDockerAuthProvider(s.stderr()),
},
}, nil
}
@@ -289,7 +287,7 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
}
func dockerFilePath(context string, dockerfile string) string {
if urlutil.IsGitURL(context) || path.IsAbs(dockerfile) {
if urlutil.IsGitURL(context) || filepath.IsAbs(dockerfile) {
return dockerfile
}
return filepath.Join(context, dockerfile)

View File

@@ -19,6 +19,7 @@ package compose
import (
"context"
"os"
"path/filepath"
"github.com/compose-spec/compose-go/types"
"github.com/docker/buildx/build"
@@ -28,7 +29,7 @@ import (
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
const drivername = "default"
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient(), s.configFile(), nil, nil, nil, nil, nil, project.WorkingDir)
if err != nil {
return nil, err
}
@@ -47,7 +48,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile().Filename), w)
errW := w.Wait()
if err == nil {
err = errW

View File

@@ -69,8 +69,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
dockerfileName := options.Inputs.DockerfilePath
specifiedContext := options.Inputs.ContextPath
progBuff := os.Stdout
buildBuff := os.Stdout
progBuff := s.stdout()
buildBuff := s.stdout()
if options.ImageIDFile != "" {
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
@@ -155,7 +155,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
}
configFile := s.configFile
configFile := s.configFile()
creds, err := configFile.GetAllCredentials()
if err != nil {
return "", err
@@ -171,7 +171,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
ctx, cancel := context.WithCancel(ctx)
defer cancel()
response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
if err != nil {
return "", err
}
@@ -181,13 +181,13 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
aux := func(msg jsonmessage.JSONMessage) {
var result dockertypes.BuildResult
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse aux message: %s", err)
fmt.Fprintf(s.stderr(), "Failed to parse aux message: %s", err)
} else {
imageID = result.ID
}
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.Fd(), true, aux)
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
@@ -203,7 +203,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
// daemon isn't running Windows.
if response.OSType != "windows" && runtime.GOOS == "windows" {
// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
fmt.Fprintln(os.Stdout, "SECURITY WARNING: You are building a Docker "+
fmt.Fprintln(s.stdout(), "SECURITY WARNING: You are building a Docker "+
"image from Windows against a non-Windows Docker host. All files and "+
"directories added to build context will have '-rwxr-xr-x' permissions. "+
"It is recommended to double check and reset permissions for sensitive "+
@@ -231,6 +231,7 @@ func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
return dockertypes.ImageBuildOptions{
Tags: options.Tags,
NoCache: options.NoCache,
Remove: true,
PullParent: options.Pull,
BuildArgs: toMapStringStringPtr(options.BuildArgs),
Labels: options.Labels,

View File

@@ -21,12 +21,16 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/pkg/errors"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/sanathkr/go-yaml"
@@ -36,19 +40,41 @@ import (
var Separator = "-"
// NewComposeService create a local implementation of the compose.Service API
func NewComposeService(apiClient client.APIClient, configFile *configfile.ConfigFile) api.Service {
func NewComposeService(dockerCli command.Cli) api.Service {
return &composeService{
apiClient: apiClient,
configFile: configFile,
dockerCli: dockerCli,
}
}
type composeService struct {
apiClient client.APIClient
configFile *configfile.ConfigFile
dockerCli command.Cli
}
func (s *composeService) apiClient() client.APIClient {
return s.dockerCli.Client()
}
func (s *composeService) configFile() *configfile.ConfigFile {
return s.dockerCli.ConfigFile()
}
func (s *composeService) stdout() *streams.Out {
return s.dockerCli.Out()
}
func (s *composeService) stdin() *streams.In {
return s.dockerCli.In()
}
func (s *composeService) stderr() io.Writer {
return s.dockerCli.Err()
}
func getCanonicalContainerName(c moby.Container) string {
if len(c.Names) == 0 {
// corner case, sometime happens on removal. return short ID as a safeguard value
return c.ID[:12]
}
// Names return container canonical name /foo + link aliases /linked_by/foo
for _, name := range c.Names {
if strings.LastIndex(name, "/") == 0 {
@@ -92,3 +118,79 @@ func escapeDollarSign(marshal []byte) []byte {
escDollar := []byte{'$', '$'}
return bytes.ReplaceAll(marshal, dollar, escDollar)
}
// projectFromName builds a types.Project based on actual resources with compose labels set
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
project := &types.Project{
Name: projectName,
}
if len(containers) == 0 {
return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
}
set := map[string]*types.ServiceConfig{}
for _, c := range containers {
serviceLabel := c.Labels[api.ServiceLabel]
_, ok := set[serviceLabel]
if !ok {
set[serviceLabel] = &types.ServiceConfig{
Name: serviceLabel,
Image: c.Image,
Labels: c.Labels,
}
}
set[serviceLabel].Scale++
}
for _, service := range set {
dependencies := service.Labels[api.DependenciesLabel]
if len(dependencies) > 0 {
service.DependsOn = types.DependsOnConfig{}
for _, dc := range strings.Split(dependencies, ",") {
dcArr := strings.Split(dc, ":")
condition := ServiceConditionRunningOrHealthy
dependency := dcArr[0]
// backward compatibility
if len(dcArr) > 1 {
condition = dcArr[1]
}
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition}
}
}
project.Services = append(project.Services, *service)
}
SERVICES:
for _, qs := range services {
for _, es := range project.Services {
if es.Name == qs {
continue SERVICES
}
}
return project, errors.New("no such service: " + qs)
}
err := project.ForServices(services)
if err != nil {
return project, err
}
return project, nil
}
// actualState list resources labelled by projectName to rebuild compose project model
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
var containers Containers
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return nil, nil, err
}
project, err := s.projectFromName(containers, projectName, services...)
if err != nil && !api.IsNotFoundError(err) {
return nil, nil, err
}
if len(services) > 0 {
containers = containers.filter(isService(services...))
}
return containers, project, nil
}

View File

@@ -19,6 +19,7 @@ package compose
import (
"context"
"sort"
"strconv"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
@@ -51,7 +52,7 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
f = append(f, oneOffFilter(false))
case oneOffInclude:
}
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(f...),
All: stopped,
})
@@ -86,6 +87,14 @@ func isNotOneOff(c moby.Container) bool {
return !ok || v == "False"
}
func indexed(index int) containerPredicate {
return func(c moby.Container) bool {
number := c.Labels[api.ContainerNumberLabel]
idx, err := strconv.Atoi(number)
return err == nil && index == idx
}
}
// filter return Containers with elements to match predicate
func (containers Containers) filter(predicate containerPredicate) Containers {
var filtered Containers

View File

@@ -180,11 +180,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
// Scale Down
container := container
eg.Go(func() error {
err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout)
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
if err != nil {
return err
}
return c.service.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
return c.service.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
})
continue
}
@@ -261,11 +261,33 @@ func getContainerProgressName(container moby.Container) string {
return "Container " + getCanonicalContainerName(container)
}
func containerEvents(containers Containers, eventFunc func(string) progress.Event) []progress.Event {
events := []progress.Event{}
for _, container := range containers {
events = append(events, eventFunc(getContainerProgressName(container)))
}
return events
}
// ServiceConditionRunningOrHealthy is a service condition on statys running or healthy
const ServiceConditionRunningOrHealthy = "running_or_healthy"
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
for dep, config := range dependencies {
if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil {
return err
} else if !shouldWait {
continue
}
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
if err != nil {
return err
}
w.Events(containerEvents(containers, progress.Waiting))
dep, config := dep, config
eg.Go(func() error {
ticker := time.NewTicker(500 * time.Millisecond)
@@ -279,6 +301,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
return err
}
if healthy {
w.Events(containerEvents(containers, progress.Healthy))
return nil
}
case types.ServiceConditionHealthy:
@@ -287,6 +310,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
return err
}
if healthy {
w.Events(containerEvents(containers, progress.Healthy))
return nil
}
case types.ServiceConditionCompletedSuccessfully:
@@ -295,14 +319,12 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
return err
}
if exited {
w.Events(containerEvents(containers, progress.Exited))
if code != 0 {
return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, code)
}
return nil
}
case types.ServiceConditionStarted:
// already managed by InDependencyOrder
return nil
default:
logrus.Warnf("unsupported depends_on condition: %s", config.Condition)
return nil
@@ -313,6 +335,20 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
return eg.Wait()
}
func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
if dependencyConfig.Condition == types.ServiceConditionStarted {
// already managed by InDependencyOrder
return false, nil
}
if service, err := project.GetService(serviceName); err != nil {
return false, err
} else if service.Scale == 0 {
// don't wait for the dependency which configured to have 0 containers running
return false, nil
}
return true, nil
}
func nextContainerNumber(containers []moby.Container) (int, error) {
max := 0
for _, c := range containers {
@@ -359,13 +395,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
var created moby.Container
w := progress.ContextWriter(ctx)
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
err := s.apiClient.ContainerStop(ctx, replaced.ID, timeout)
err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
if err != nil {
return created, err
}
name := getCanonicalContainerName(replaced)
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
err = s.apiClient.ContainerRename(ctx, replaced.ID, tmpName)
err = s.apiClient().ContainerRename(ctx, replaced.ID, tmpName)
if err != nil {
return created, err
}
@@ -383,7 +419,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
if err != nil {
return created, err
}
err = s.apiClient.ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
if err != nil {
return created, err
}
@@ -408,7 +444,7 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
func (s *composeService) startContainer(ctx context.Context, container moby.Container) error {
w := progress.ContextWriter(ctx)
w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Restart"))
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
if err != nil {
return err
}
@@ -432,11 +468,11 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
}
plat = &p
}
response, err := s.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
if err != nil {
return created, err
}
inspectedContainer, err := s.apiClient.ContainerInspect(ctx, response.ID)
inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
if err != nil {
return created, err
}
@@ -448,7 +484,10 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
Networks: inspectedContainer.NetworkSettings.Networks,
},
}
links := append(service.Links, service.ExternalLinks...)
links, err := s.getLinks(ctx, project.Name, service, number)
if err != nil {
return created, err
}
for _, netName := range service.NetworksByPriority() {
netwrk := project.Networks[netName]
cfg := service.Networks[netName]
@@ -463,7 +502,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
if shortIDAliasExists(created.ID, val.Aliases...) {
continue
}
err = s.apiClient.NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
if err != nil {
return created, err
}
@@ -476,6 +515,64 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
return created, err
}
// getLinks mimics V1 compose/service.py::Service::_get_links()
func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) {
var links []string
format := func(k, v string) string {
return fmt.Sprintf("%s:%s", k, v)
}
getServiceContainers := func(serviceName string) (Containers, error) {
return s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
}
for _, rawLink := range service.Links {
linkSplit := strings.Split(rawLink, ":")
linkServiceName := linkSplit[0]
linkName := linkServiceName
if len(linkSplit) == 2 {
linkName = linkSplit[1] // linkName if informed like in: "serviceName:linkName"
}
cnts, err := getServiceContainers(linkServiceName)
if err != nil {
return nil, err
}
for _, c := range cnts {
containerName := getCanonicalContainerName(c)
links = append(links,
format(containerName, linkName),
format(containerName, strings.Join([]string{linkServiceName, strconv.Itoa(number)}, Separator)),
format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, Separator)),
)
}
}
if service.Labels[api.OneoffLabel] == "True" {
cnts, err := getServiceContainers(service.Name)
if err != nil {
return nil, err
}
for _, c := range cnts {
containerName := getCanonicalContainerName(c)
links = append(links,
format(containerName, service.Name),
format(containerName, strings.TrimPrefix(containerName, projectName+Separator)),
format(containerName, containerName),
)
}
}
for _, rawExtLink := range service.ExternalLinks {
extLinkSplit := strings.Split(rawExtLink, ":")
externalLink := extLinkSplit[0]
linkName := externalLink
if len(extLinkSplit) == 2 {
linkName = extLinkSplit[1]
}
links = append(links, format(externalLink, linkName))
}
return links, nil
}
func shortIDAliasExists(containerID string, aliases ...string) bool {
for _, alias := range aliases {
if alias == containerID[:12] {
@@ -499,7 +596,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
IPv6Address: ipv6Address,
}
}
err := s.apiClient.NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
Aliases: aliases,
IPAddress: ipv4Address,
GlobalIPv6Address: ipv6Address,
@@ -522,7 +619,7 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
return false, nil
}
for _, c := range containers {
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil {
return false, err
}
@@ -534,8 +631,15 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
if container.State == nil || container.State.Health == nil {
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
}
if container.State.Health.Status != moby.Healthy {
switch container.State.Health.Status {
case moby.Healthy:
// Continue by checking the next container.
case moby.Unhealthy:
return false, fmt.Errorf("container for service %q is unhealthy", service)
case moby.Starting:
return false, nil
default:
return false, fmt.Errorf("container for service %q had unexpected health status %q", service, container.State.Health.Status)
}
}
return true, nil
@@ -547,7 +651,7 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
return false, 0, err
}
for _, c := range containers {
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil {
return false, 0, err
}
@@ -559,11 +663,15 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
}
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
return nil
}
err := s.waitDependencies(ctx, project, service.DependsOn)
if err != nil {
return err
}
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(project.Name),
serviceFilter(service.Name),
@@ -579,7 +687,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
if scale, err := getScale(service); err != nil && scale == 0 {
return nil
}
return fmt.Errorf("no containers to start")
return fmt.Errorf("service %q has no container to start", service.Name)
}
w := progress.ContextWriter(ctx)
@@ -592,7 +700,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
eg.Go(func() error {
eventName := getContainerProgressName(container)
w.Event(progress.StartingEvent(eventName))
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
if err == nil {
w.Event(progress.StartedEvent(eventName))
}

View File

@@ -17,10 +17,17 @@
package compose
import (
"context"
"fmt"
"strings"
"testing"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/golang/mock/gomock"
"gotest.tools/assert"
)
@@ -46,3 +53,177 @@ func TestContainerName(t *testing.T) {
_, err = getScale(s)
assert.Error(t, err, fmt.Sprintf(doubledContainerNameWarning, s.Name, s.ContainerName))
}
func TestServiceLinks(t *testing.T) {
const dbContainerName = "/" + testProject + "-db-1"
const webContainerName = "/" + testProject + "-web-1"
s := types.ServiceConfig{
Name: "web",
Scale: 1,
}
containerListOptions := moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(testProject),
serviceFilter("db"),
oneOffFilter(false),
),
All: true,
}
t.Run("service links default", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
s.Links = []string{"db"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
links, err := tested.getLinks(context.Background(), testProject, s, 1)
assert.NilError(t, err)
assert.Equal(t, len(links), 3)
assert.Equal(t, links[0], "testProject-db-1:db")
assert.Equal(t, links[1], "testProject-db-1:db-1")
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
})
t.Run("service links", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
s.Links = []string{"db:db"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
links, err := tested.getLinks(context.Background(), testProject, s, 1)
assert.NilError(t, err)
assert.Equal(t, len(links), 3)
assert.Equal(t, links[0], "testProject-db-1:db")
assert.Equal(t, links[1], "testProject-db-1:db-1")
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
})
t.Run("service links name", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
s.Links = []string{"db:dbname"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
links, err := tested.getLinks(context.Background(), testProject, s, 1)
assert.NilError(t, err)
assert.Equal(t, len(links), 3)
assert.Equal(t, links[0], "testProject-db-1:dbname")
assert.Equal(t, links[1], "testProject-db-1:db-1")
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
})
t.Run("service links external links", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
s.Links = []string{"db:dbname"}
s.ExternalLinks = []string{"db1:db2"}
c := testContainer("db", dbContainerName, false)
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
links, err := tested.getLinks(context.Background(), testProject, s, 1)
assert.NilError(t, err)
assert.Equal(t, len(links), 4)
assert.Equal(t, links[0], "testProject-db-1:dbname")
assert.Equal(t, links[1], "testProject-db-1:db-1")
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
// ExternalLink
assert.Equal(t, links[3], "db1:db2")
})
t.Run("service links itself oneoff", func(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
s.Links = []string{}
s.ExternalLinks = []string{}
s.Labels = s.Labels.Add(api.OneoffLabel, "True")
c := testContainer("web", webContainerName, true)
containerListOptionsOneOff := moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(testProject),
serviceFilter("web"),
oneOffFilter(false),
),
All: true,
}
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return([]moby.Container{c}, nil)
links, err := tested.getLinks(context.Background(), testProject, s, 1)
assert.NilError(t, err)
assert.Equal(t, len(links), 3)
assert.Equal(t, links[0], "testProject-web-1:web")
assert.Equal(t, links[1], "testProject-web-1:web-1")
assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
})
}
func TestWaitDependencies(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiClient := mocks.NewMockAPIClient(mockCtrl)
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(apiClient).AnyTimes()
t.Run("should skip dependencies with scale 0", func(t *testing.T) {
dbService := types.ServiceConfig{Name: "db", Scale: 0}
redisService := types.ServiceConfig{Name: "redis", Scale: 0}
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
dependencies := types.DependsOnConfig{
"db": {Condition: ServiceConditionRunningOrHealthy},
"redis": {Condition: ServiceConditionRunningOrHealthy},
}
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
})
t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
dbService := types.ServiceConfig{Name: "db", Scale: 1}
redisService := types.ServiceConfig{Name: "redis", Scale: 1}
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
dependencies := types.DependsOnConfig{
"db": {Condition: types.ServiceConditionStarted},
"redis": {Condition: types.ServiceConditionStarted},
}
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
})
}

View File

@@ -26,11 +26,9 @@ import (
"golang.org/x/sync/errgroup"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/system"
"github.com/pkg/errors"
@@ -44,7 +42,7 @@ const (
acrossServices = fromService | toService
)
func (s *composeService) Copy(ctx context.Context, project *types.Project, opts api.CopyOptions) error {
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
srcService, srcPath := splitCpArg(opts.Source)
destService, dstPath := splitCpArg(opts.Destination)
@@ -64,20 +62,17 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, opts
serviceName = destService
}
f := filters.NewArgs(
projectFilter(project.Name),
serviceFilter(serviceName),
)
if !opts.All {
f.Add("label", fmt.Sprintf("%s=%d", api.ContainerNumberLabel, opts.Index))
}
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{Filters: f})
containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
if err != nil {
return err
}
if len(containers) < 1 {
return fmt.Errorf("service %s not running", serviceName)
return fmt.Errorf("no container found for service %q", serviceName)
}
if !opts.All {
containers = containers.filter(indexed(opts.Index))
}
g := errgroup.Group{}
@@ -112,7 +107,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := s.apiClient.ContainerStatPath(ctx, containerID, dstPath)
dstStat, err := s.apiClient().ContainerStatPath(ctx, containerID, dstPath)
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
@@ -124,7 +119,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
}
dstInfo.Path = linkTarget
dstStat, err = s.apiClient.ContainerStatPath(ctx, containerID, linkTarget)
dstStat, err = s.apiClient().ContainerStatPath(ctx, containerID, linkTarget)
}
// Validate the destination path
@@ -148,7 +143,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
)
if srcPath == "-" {
content = os.Stdin
content = s.stdin()
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
@@ -192,7 +187,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
AllowOverwriteDirWithFile: false,
CopyUIDGID: opts.CopyUIDGID,
}
return s.apiClient.CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
return s.apiClient().CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
}
func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
@@ -212,7 +207,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if opts.FollowLink {
srcStat, err := s.apiClient.ContainerStatPath(ctx, containerID, srcPath)
srcStat, err := s.apiClient().ContainerStatPath(ctx, containerID, srcPath)
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
@@ -228,14 +223,14 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
}
}
content, stat, err := s.apiClient.CopyFromContainer(ctx, containerID, srcPath)
content, stat, err := s.apiClient().CopyFromContainer(ctx, containerID, srcPath)
if err != nil {
return err
}
defer content.Close() //nolint:errcheck
if dstPath == "-" {
_, err = io.Copy(os.Stdout, content)
_, err = io.Copy(s.stdout(), content)
return err
}

View File

@@ -229,7 +229,7 @@ func getImageName(service types.ServiceConfig, projectName string) string {
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig,
number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
labels, err := s.prepareLabels(p, service, number)
labels, err := s.prepareLabels(service, number)
if err != nil {
return nil, nil, nil, err
}
@@ -255,7 +255,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
return nil, nil, nil, err
}
proxyConfig := types.MappingWithEquals(s.configFile.ParseProxyConfig(s.apiClient.DaemonHost(), nil))
proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
env := proxyConfig.OverrideBy(service.Environment)
containerConfig := container.Config{
@@ -380,6 +380,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
Isolation: container.Isolation(service.Isolation),
Runtime: service.Runtime,
LogConfig: logConfig,
GroupAdd: service.GroupAdd,
}
return &containerConfig, &hostConfig, networkConfig, nil
@@ -413,45 +414,42 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
return securityOpts, nil
}
func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
func (s *composeService) prepareLabels(service types.ServiceConfig, number int) (map[string]string, error) {
labels := map[string]string{}
for k, v := range service.Labels {
labels[k] = v
}
labels[api.ProjectLabel] = p.Name
labels[api.ServiceLabel] = service.Name
labels[api.VersionLabel] = api.ComposeVersion
if _, ok := service.Labels[api.OneoffLabel]; !ok {
labels[api.OneoffLabel] = "False"
for k, v := range service.CustomLabels {
labels[k] = v
}
hash, err := ServiceHash(service)
if err != nil {
return nil, err
}
labels[api.ConfigHashLabel] = hash
labels[api.WorkingDirLabel] = p.WorkingDir
labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
var dependencies []string
for s := range service.DependsOn {
dependencies = append(dependencies, s)
for s, d := range service.DependsOn {
dependencies = append(dependencies, s+":"+d.Condition)
}
labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
return labels, nil
}
func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
mode := "none"
if len(project.Networks) > 0 {
for name := range getNetworksForService(service) {
mode = project.Networks[name].Name
break
}
if len(project.Networks) == 0 {
return "none"
}
return mode
if len(service.Networks) > 0 {
name := service.NetworksByPriority()[0]
return project.Networks[name].Name
}
return project.Networks["default"].Name
}
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
@@ -502,6 +500,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
CPUShares: s.CPUShares,
CPUPercent: int64(s.CPUS * 100),
CpusetCpus: s.CPUSet,
DeviceCgroupRules: s.DeviceCgroupRules,
}
if s.PidsLimit != 0 {
@@ -640,15 +639,11 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
bindings := nat.PortMap{}
for _, port := range s.Ports {
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
bind := bindings[p]
binding := nat.PortBinding{
HostIP: port.HostIP,
HostIP: port.HostIP,
HostPort: port.Published,
}
if port.Published > 0 {
binding.HostPort = fmt.Sprint(port.Published)
}
bind = append(bind, binding)
bindings[p] = bind
bindings[p] = append(bindings[p], binding)
}
return bindings
}
@@ -699,7 +694,7 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
var mounts = []mount.Mount{}
image := getImageName(service, p.Name)
imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
if err != nil {
return nil, nil, nil, err
}
@@ -718,11 +713,7 @@ MOUNTS:
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
for _, v := range service.Volumes {
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
mode := "rw"
if m.ReadOnly {
mode = "ro"
}
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
continue MOUNTS
}
}
@@ -732,6 +723,23 @@ MOUNTS:
return volumeMounts, binds, mounts, nil
}
func getBindMode(bind *types.ServiceVolumeBind, readOnly bool) string {
mode := "rw"
if readOnly {
mode = "ro"
}
switch bind.SELinux {
case types.SELinuxShared:
mode += ",z"
case types.SELinuxPrivate:
mode += ",Z"
}
return mode
}
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
var mounts = map[string]mount.Mount{}
if inherit != nil {
@@ -999,21 +1007,19 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
return aliases
}
func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
if len(s.Networks) > 0 {
return s.Networks
}
if s.NetworkMode != "" {
return nil
}
return map[string]*types.ServiceNetworkConfig{"default": nil}
}
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
_, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
if err != nil {
if errdefs.IsNotFound(err) {
if n.External.External {
if n.Driver == "overlay" {
// Swarm nodes do not register overlay networks that were
// created on a different node unless they're in use.
// Here we assume `driver` is relevant for a network we don't manage
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
// networkAttach will later fail anyway if network actually doesn't exists
return nil
}
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
}
var ipam *network.IPAM
@@ -1060,7 +1066,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
networkEventName := fmt.Sprintf("Network %s", n.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(networkEventName))
if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
w.Event(progress.ErrorEvent(networkEventName))
return errors.Wrapf(err, "failed to create network %s", n.Name)
}
@@ -1077,7 +1083,7 @@ func (s *composeService) removeNetwork(ctx context.Context, networkID string, ne
eventName := fmt.Sprintf("Network %s", networkName)
w.Event(progress.RemovingEvent(eventName))
if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
if err := s.apiClient().NetworkRemove(ctx, networkID); err != nil {
w.Event(progress.ErrorEvent(eventName))
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
}
@@ -1087,23 +1093,29 @@ func (s *composeService) removeNetwork(ctx context.Context, networkID string, ne
}
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
inspected, err := s.apiClient.VolumeInspect(ctx, volume.Name)
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
if volume.External.External {
return fmt.Errorf("external volume %q not found", volume.Name)
}
err := s.createVolume(ctx, volume)
return err
}
// Volume exists with name, but let's double check this is the expected one
// (better safe than sorry when it comes to user's data)
if volume.External.External {
return nil
}
// Volume exists with name, but let's double-check this is the expected one
p, ok := inspected.Labels[api.ProjectLabel]
if !ok {
return fmt.Errorf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
}
if p != project {
return fmt.Errorf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
if ok && p != project {
logrus.Warnf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
}
return nil
}
@@ -1112,7 +1124,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName))
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
Labels: volume.Labels,
Name: volume.Name,
Driver: volume.Driver,

View File

@@ -142,3 +142,83 @@ func TestBuildContainerMountOptions(t *testing.T) {
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
}
func TestGetBindMode(t *testing.T) {
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, false), "rw")
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, true), "ro")
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, false), "rw,z")
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, false), "rw,Z")
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, true), "ro,z")
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, true), "ro,Z")
}
func TestGetDefaultNetworkMode(t *testing.T) {
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
service := composetypes.ServiceConfig{
Name: "myService",
Networks: map[string]*composetypes.ServiceNetworkConfig{
"myNetwork1": {
Priority: 10,
},
"myNetwork2": {
Priority: 1000,
},
},
}
project := composetypes.Project{
Name: "myProject",
Services: []composetypes.ServiceConfig{
service,
},
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
"myNetwork1": {
Name: "myProject_myNetwork1",
},
"myNetwork2": {
Name: "myProject_myNetwork2",
},
}),
}
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_myNetwork2")
})
t.Run("returns default network when service has no networks", func(t *testing.T) {
service := composetypes.ServiceConfig{
Name: "myService",
}
project := composetypes.Project{
Name: "myProject",
Services: []composetypes.ServiceConfig{
service,
},
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
"myNetwork1": {
Name: "myProject_myNetwork1",
},
"myNetwork2": {
Name: "myProject_myNetwork2",
},
"default": {
Name: "myProject_default",
},
}),
}
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_default")
})
t.Run("returns none if project has no networks", func(t *testing.T) {
service := composetypes.ServiceConfig{
Name: "myService",
}
project := composetypes.Project{
Name: "myProject",
Services: []composetypes.ServiceConfig{
service,
},
}
assert.Equal(t, getDefaultNetworkMode(&project, service), "none")
})
}

View File

@@ -93,7 +93,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
for _, node := range nodes {
// Don't start this service yet if all of its children have
// not been started yet.
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
continue
}
@@ -104,7 +104,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
return err
}
graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
})

View File

@@ -41,6 +41,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
}
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
builtFromResources := options.Project == nil
w := progress.ContextWriter(ctx)
resourceToRemove := false
@@ -50,8 +51,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
return err
}
if options.Project == nil {
options.Project = s.projectFromContainerLabels(containers.filter(isNotOneOff), projectName)
if builtFromResources {
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
if err != nil {
return err
}
}
if len(containers) > 0 {
@@ -85,11 +89,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
}
if options.Volumes {
rm, err := s.ensureVolumesDown(ctx, projectName, w)
if err != nil {
return err
}
ops = append(ops, rm...)
ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
}
if !resourceToRemove && len(ops) == 0 {
@@ -103,19 +103,15 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
return eg.Wait()
}
func (s *composeService) ensureVolumesDown(ctx context.Context, projectName string, w progress.Writer) ([]downOp, error) {
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
var ops []downOp
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil {
return ops, err
}
for _, vol := range volumes.Volumes {
id := vol.Name
for _, vol := range project.Volumes {
volumeName := vol.Name
ops = append(ops, func() error {
return s.removeVolume(ctx, id, w)
return s.removeVolume(ctx, volumeName, w)
})
}
return ops, nil
return ops
}
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
@@ -131,7 +127,7 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
var ops []downOp
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
if err != nil {
return ops, err
}
@@ -163,7 +159,7 @@ func (s *composeService) getServiceImages(options api.DownOptions, projectName s
func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
id := fmt.Sprintf("Image %s", image)
w.Event(progress.NewEvent(id, progress.Working, "Removing"))
_, err := s.apiClient.ImageRemove(ctx, image, moby.ImageRemoveOptions{})
_, err := s.apiClient().ImageRemove(ctx, image, moby.ImageRemoveOptions{})
if err == nil {
w.Event(progress.NewEvent(id, progress.Done, "Removed"))
return nil
@@ -178,7 +174,7 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre
func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
resource := fmt.Sprintf("Volume %s", id)
w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
err := s.apiClient.VolumeRemove(ctx, id, true)
err := s.apiClient().VolumeRemove(ctx, id, true)
if err == nil {
w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
return nil
@@ -197,7 +193,7 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
eg.Go(func() error {
eventName := getContainerProgressName(container)
w.Event(progress.StoppingEvent(eventName))
err := s.apiClient.ContainerStop(ctx, container.ID, timeout)
err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err
@@ -222,7 +218,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
return err
}
w.Event(progress.RemovingEvent(eventName))
err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
err = s.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
Force: true,
RemoveVolumes: volumes,
})
@@ -237,31 +233,21 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
return eg.Wait()
}
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) *types.Project {
project := &types.Project{
Name: projectName,
func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
containers = containers.filter(isNotOneOff)
project, _ := s.projectFromName(containers, projectName)
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil {
return nil, err
}
if len(containers) == 0 {
return project
}
set := map[string]moby.Container{}
for _, c := range containers {
set[c.Labels[api.ServiceLabel]] = c
}
for s, c := range set {
service := types.ServiceConfig{
Name: s,
Image: c.Image,
Labels: c.Labels,
project.Volumes = types.Volumes{}
for _, vol := range volumes.Volumes {
project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
Name: vol.Name,
Driver: vol.Driver,
Labels: vol.Labels,
}
dependencies := c.Labels[api.DependenciesLabel]
if len(dependencies) > 0 {
service.DependsOn = types.DependsOnConfig{}
for _, d := range strings.Split(dependencies, ",") {
service.DependsOn[d] = types.ServiceDependency{}
}
}
project.Services = append(project.Services, service)
}
return project
return project, nil
}

View File

@@ -34,8 +34,11 @@ import (
func TestDown(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{
@@ -44,6 +47,8 @@ func TestDown(t *testing.T) {
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
@@ -65,8 +70,11 @@ func TestDown(t *testing.T) {
func TestDownRemoveOrphans(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{
@@ -74,6 +82,8 @@ func TestDownRemoveOrphans(t *testing.T) {
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
@@ -95,18 +105,24 @@ func TestDownRemoveOrphans(t *testing.T) {
func TestDownRemoveVolumes(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{testContainer("service1", "123", false)}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return(nil, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).Return(volume.VolumeListOKBody{Volumes: []*moby.Volume{{Name: "myProject_volume"}}}, nil)
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})

View File

@@ -30,7 +30,7 @@ import (
)
func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
events, errors := s.apiClient.Events(ctx, moby.EventsOptions{
events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
Filters: filters.NewArgs(projectFilter(project)),
})
for {

View File

@@ -21,7 +21,6 @@ import (
"fmt"
"io"
"github.com/docker/cli/cli/streams"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/stdcopy"
@@ -36,7 +35,7 @@ func (s *composeService) Exec(ctx context.Context, project string, opts api.RunO
return 0, err
}
exec, err := s.apiClient.ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
exec, err := s.apiClient().ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
Cmd: opts.Command,
Env: opts.Environment,
User: opts.User,
@@ -54,13 +53,13 @@ func (s *composeService) Exec(ctx context.Context, project string, opts api.RunO
}
if opts.Detach {
return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{
return 0, s.apiClient().ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{
Detach: true,
Tty: opts.Tty,
})
}
resp, err := s.apiClient.ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{
resp, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{
Tty: opts.Tty,
})
if err != nil {
@@ -69,7 +68,7 @@ func (s *composeService) Exec(ctx context.Context, project string, opts api.RunO
defer resp.Close() //nolint:errcheck
if opts.Tty {
s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
s.monitorTTySize(ctx, exec.ID, s.apiClient().ContainerExecResize)
if err != nil {
return 0, err
}
@@ -90,13 +89,13 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
stdout := ContainerStdout{HijackedResponse: resp}
stdin := ContainerStdin{HijackedResponse: resp}
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
r, err := s.getEscapeKeyProxy(s.stdin(), opts.Tty)
if err != nil {
return err
}
in := streams.NewIn(opts.Stdin)
if in.IsTerminal() {
in := s.stdin()
if in.IsTerminal() && opts.Tty {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return err
@@ -106,10 +105,10 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
go func() {
if opts.Tty {
_, err := io.Copy(opts.Stdout, stdout)
_, err := io.Copy(s.stdout(), stdout)
outputDone <- err
} else {
_, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout)
_, err := stdcopy.StdCopy(s.stdout(), s.stderr(), stdout)
outputDone <- err
}
stdout.Close() //nolint:errcheck
@@ -140,7 +139,7 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
}
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
serviceFilter(opts.Service),
@@ -158,7 +157,7 @@ func (s *composeService) getExecTarget(ctx context.Context, projectName string,
}
func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) {
resp, err := s.apiClient.ContainerExecInspect(ctx, execID)
resp, err := s.apiClient().ContainerExecInspect(ctx, execID)
if err != nil {
return 0, err
}

View File

@@ -24,7 +24,6 @@ import (
)
// ServiceHash compute configuration has for a service
// TODO move this to compose-go
func ServiceHash(o types.ServiceConfig) (string, error) {
// remove the Build config when generating the service hash
o.Build = nil

View File

@@ -32,7 +32,8 @@ import (
)
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
allContainers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
All: true,
Filters: filters.NewArgs(projectFilter(projectName)),
})
if err != nil {
@@ -82,7 +83,7 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
for _, img := range images {
img := img
eg.Go(func() error {
inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
if err != nil {
if errdefs.IsNotFound(err) {
return nil

View File

@@ -42,7 +42,7 @@ func (s *composeService) kill(ctx context.Context, project *types.Project, optio
}
var containers Containers
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, false, services...)
if err != nil {
return err
}
@@ -54,7 +54,7 @@ func (s *composeService) kill(ctx context.Context, project *types.Project, optio
eg.Go(func() error {
eventName := getContainerProgressName(container)
w.Event(progress.KillingEvent(eventName))
err := s.apiClient.ContainerKill(ctx, container.ID, options.Signal)
err := s.apiClient().ContainerKill(ctx, container.ID, options.Signal)
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Killing"))
return err

View File

@@ -39,13 +39,18 @@ var tested = composeService{}
func TestKillAll(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService("service1"), testService("service2")}}
ctx := context.Background()
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
api.EXPECT().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
}).Return(
[]moby.Container{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
@@ -59,14 +64,15 @@ func TestKillSignal(t *testing.T) {
const serviceName = "service1"
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
cli := mocks.NewMockCli(mockCtrl)
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService(serviceName)}}
listOptions := moby.ContainerListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)),
serviceFilter(serviceName)),
All: true,
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)), serviceFilter(serviceName)),
}
ctx := context.Background()

View File

@@ -34,36 +34,54 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
}
eg, ctx := errgroup.WithContext(ctx)
if options.Follow {
printer := newLogPrinter(consumer)
eg.Go(func() error {
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
return s.logContainers(ctx, consumer, c, options)
})
})
eg.Go(func() error {
_, err := printer.Run(ctx, false, "", nil)
return err
})
}
for _, c := range containers {
c := c
eg.Go(func() error {
return s.logContainers(ctx, consumer, c, options)
})
}
if options.Follow {
printer := newLogPrinter(consumer)
eg.Go(func() error {
for _, c := range containers {
printer.HandleEvent(api.ContainerEvent{
Type: api.ContainerEventAttach,
Container: getContainerNameWithoutProject(c),
Service: c.Labels[api.ServiceLabel],
})
}
return nil
})
eg.Go(func() error {
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
printer.HandleEvent(api.ContainerEvent{
Type: api.ContainerEventAttach,
Container: getContainerNameWithoutProject(c),
Service: c.Labels[api.ServiceLabel],
})
return s.logContainers(ctx, consumer, c, options)
})
})
eg.Go(func() error {
_, err := printer.Run(ctx, false, "", nil)
return err
})
}
return eg.Wait()
}
func (s *composeService) logContainers(ctx context.Context, consumer api.LogConsumer, c types.Container, options api.LogOptions) error {
cnt, err := s.apiClient.ContainerInspect(ctx, c.ID)
cnt, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil {
return err
}
service := c.Labels[api.ServiceLabel]
r, err := s.apiClient.ContainerLogs(ctx, cnt.ID, types.ContainerLogsOptions{
r, err := s.apiClient().ContainerLogs(ctx, cnt.ID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: options.Follow,

View File

@@ -20,15 +20,17 @@ import (
"context"
"fmt"
"sort"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)
func (s *composeService) List(ctx context.Context, opts api.ListOptions) ([]api.Stack, error) {
list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(hasProjectLabelFilter()),
All: opts.All,
})
@@ -46,15 +48,40 @@ func containersToStacks(containers []moby.Container) ([]api.Stack, error) {
}
var projects []api.Stack
for _, project := range keys {
configFiles, err := combinedConfigFiles(containersByLabel[project])
if err != nil {
return nil, err
}
projects = append(projects, api.Stack{
ID: project,
Name: project,
Status: combinedStatus(containerToState(containersByLabel[project])),
ID: project,
Name: project,
Status: combinedStatus(containerToState(containersByLabel[project])),
ConfigFiles: configFiles,
})
}
return projects, nil
}
func combinedConfigFiles(containers []moby.Container) (string, error) {
configFiles := []string{}
for _, c := range containers {
files, ok := c.Labels[api.ConfigFilesLabel]
if !ok {
return "", fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
}
for _, f := range strings.Split(files, ",") {
if !utils.StringContains(configFiles, f) {
configFiles = append(configFiles, f)
}
}
}
return strings.Join(configFiles, ","), nil
}
func containerToState(containers []moby.Container) []string {
statuses := []string{}
for _, c := range containers {

View File

@@ -17,6 +17,7 @@
package compose
import (
"fmt"
"testing"
"github.com/docker/compose/v2/pkg/api"
@@ -30,31 +31,33 @@ func TestContainersToStacks(t *testing.T) {
{
ID: "service1",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project1"},
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
},
{
ID: "service2",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project1"},
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
},
{
ID: "service3",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project2"},
Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
},
}
stacks, err := containersToStacks(containers)
assert.NilError(t, err)
assert.DeepEqual(t, stacks, []api.Stack{
{
ID: "project1",
Name: "project1",
Status: "running(2)",
ID: "project1",
Name: "project1",
Status: "running(2)",
ConfigFiles: "/home/docker-compose.yaml",
},
{
ID: "project2",
Name: "project2",
Status: "running(1)",
ID: "project2",
Name: "project2",
Status: "running(1)",
ConfigFiles: "/home/project2-docker-compose.yaml",
},
})
}
@@ -64,3 +67,56 @@ func TestStacksMixedStatus(t *testing.T) {
assert.Equal(t, combinedStatus([]string{"running", "running", "running"}), "running(3)")
assert.Equal(t, combinedStatus([]string{"running", "exited", "running"}), "exited(1), running(2)")
}
func TestCombinedConfigFiles(t *testing.T) {
containersByLabel := map[string][]moby.Container{
"project1": {
{
ID: "service1",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
},
{
ID: "service2",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
},
},
"project2": {
{
ID: "service3",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
},
},
"project3": {
{
ID: "service4",
State: "running",
Labels: map[string]string{api.ProjectLabel: "project3"},
},
},
}
testData := map[string]struct {
ConfigFiles string
Error error
}{
"project1": {ConfigFiles: "/home/docker-compose.yaml", Error: nil},
"project2": {ConfigFiles: "/home/project2-docker-compose.yaml", Error: nil},
"project3": {ConfigFiles: "", Error: fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
}
for project, containers := range containersByLabel {
configFiles, err := combinedConfigFiles(containers)
expected := testData[project]
if expected.Error != nil {
assert.Equal(t, err.Error(), expected.Error.Error())
} else {
assert.Equal(t, err, expected.Error)
}
assert.Equal(t, configFiles, expected.ConfigFiles)
}
}

Some files were not shown because too many files have changed in this diff Show More