Compare commits

...

208 Commits

Author SHA1 Message Date
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
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
Nicolas De Loof
10cd7e130f detect volume we didn't created and ask user to explicitely mark them as external
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-19 11:04:14 +01:00
Ulysses Souza
8f9dc2e7f8 Merge pull request #8888 from PierreAntoineGuillaume/v2
Fix typo in --wait option mechanism
2021-11-18 12:58:28 +01:00
Ulysses Souza
dfa93d834f Merge pull request #8938 from ndeloof/run_T
don't SetRawTerminal when run is ran with -T
2021-11-18 12:57:12 +01:00
Nicolas De Loof
f69a613e69 don't SetRawTerminal when run is ran with -T
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-18 12:40:08 +01:00
Ulysses Souza
a8fbbd9e5c Merge pull request #8936 from glours/upgrade-containerd
upgrade containerd version - security fix
2021-11-18 00:20:40 +01:00
Guillaume Lours
ba724576a1 upgrade containerd version - security fix
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-11-17 23:45:06 +01:00
Nicolas De Loof
3261b60fd1 --quiet implies --progress=quiet
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-17 13:08:30 +01:00
Nicolas De Loof
cb425a23c0 check supported --progress flag values
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-17 13:08:30 +01:00
Nicolas De Loof
29179840c3 restore support for compose build with a git URL
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-17 13:08:30 +01:00
Nicolas De Loof
7205d918ad interrupt printer when compose log is cancelled
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-17 12:34:52 +01:00
Guillaume Lours
413f46ade0 use Dockerfile directly when path is absolute otherwise join it with Context path
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-11-16 08:38:44 +01:00
Mathieu Champlon
8a9c4b52b4 Merge pull request #8923 from ulyssessouza/remove-env-flags-on-up
Remove unused flag
2021-11-14 09:06:33 +01:00
Ulysses Souza
865b82da6a Remove unused flag
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-12 14:56:27 +01:00
Ulysses Souza
e44222664a Merge pull request #8904 from ndeloof/volumeZ
type mismatch checking tmpfs conflicting options
2021-11-09 10:11:11 +01:00
Ulysses Souza
d4e3f191ac Merge pull request #8898 from ndeloof/container_restart
better detect container will restart
2021-11-09 10:10:57 +01:00
Nicolas De Loof
577bee955b type mismatch checking tmpfs conflicting options
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-09 08:05:42 +01:00
Nicolas De Loof
ed2395819d better detect container will restart
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-08 14:33:04 +01:00
Mathieu Champlon
e6599c7213 Merge pull request #8889 from ndeloof/build_args_classic
restore support for multiple build-args
2021-11-07 11:14:00 +01:00
Pierre-Antoine Guillaume
9c01e41adf Fix typo in --wait option mechanism
Signed-off-by: Pierre-Antoine Guillaume <pierreantoine.guillaume@gmail.com>
2021-11-05 21:42:50 +01:00
Nicolas De Loof
6df30f39f2 golang is so ridiculous with this for..loop pattern
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-05 21:11:04 +01:00
Ulysses Souza
a79346b978 Support inherited environment vars on exec
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-05 20:51:36 +01:00
Djordje Lukic
125752c127 Update golang to 1.17
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2021-11-04 09:15:46 +01:00
Stephen Thirlwall
95f0431127 Fix typo in destroy event comment
Signed-off-by: Stephen Thirlwall <sdt@dr.com>
2021-11-04 09:13:59 +01:00
Stephen Thirlwall
2bee75c3c4 Fix formatting with gofmt -s
Knew I'd forget something.

Signed-off-by: Stephen Thirlwall <sdt@dr.com>
2021-11-04 09:13:59 +01:00
Stephen Thirlwall
a1f7be7b5c Don't exit on container destroy events
Fixes #8747

When the event is a container destroy, calling ContainerInspect returns
an error, because the container no longer exists. This causes both
`docker-compose up` and `docker-compose logs -f` to exit when removing a
stopped container.

This container has already emitted its die event, and has already been
cleaned up. I believe all that needs doing in this case is to early-out.

Signed-off-by: Stephen Thirlwall <sdt@dr.com>
2021-11-04 09:13:59 +01:00
Nicolas De Loof
72e4519cbf introduce up --wait condition
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-03 18:22:29 +01:00
Mathieu Champlon
9e6f51d262 Merge pull request #8868 from rumpl/fix-maintainers
Fix the maintainers array in MAINTAINERS
2021-11-03 08:40:26 +01:00
Ulysses Souza
98b3353cbc Merge pull request #8870 from ulyssessouza/bump-compose-go-1.0.5
Bump compose-go to v1.0.5
2021-11-02 23:45:27 +00:00
Ulysses Souza
c756ff3d3e Bump compose-go to v1.0.5
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-03 00:38:05 +01:00
Djordje Lukic
fc827f295b Fix the maintainers array in MAINTAINERS
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
2021-11-02 22:39:51 +01:00
Ulysses Souza
f10c96a54a Merge pull request #8789 from Mygao/chore/fix-typo
Fix typo: netwok -> network
2021-11-02 10:49:31 +00:00
Ulysses Souza
06c5d8a902 Refactoring
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-02 10:56:03 +01:00
Ulysses Souza
0f3c214b48 Remove command.DockerCli dependency
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-02 10:56:03 +01:00
Ulysses Souza
058c779378 Add support for classic builder
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-02 10:56:03 +01:00
Mathieu Champlon
07a562aa2d Merge pull request #8838 from youssefeldakar/v2
Update README.md: typographical edit of "About update..."
2021-11-02 10:11:46 +01:00
Ulysses Souza
76fe903deb Merge pull request #8858 from ulyssessouza/avoid-test-flakyness-by-ordering-volumes
Avoid test flakyness by ordering volumes before checking
2021-10-31 01:31:23 +00:00
Ulysses Souza
284bad4411 Avoid test flakyness by ordering volumes before checking
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-31 02:25:10 +01:00
Mathieu Champlon
04d8212a88 Merge pull request #8851 from ndeloof/network_EnableIPv6
add support for EnableIPv6
2021-10-30 11:53:19 +02:00
Nicolas De Loof
d38f278f68 add support for EnableIPv6
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-28 19:32:04 +02:00
Ulysses Souza
4cb6ace1ce Merge pull request #8847 from ndeloof/compose_compatibility_env
COMPOSE_COMPATIBILITY can be set by .env file
2021-10-28 12:55:50 +01:00
Ulysses Souza
9d04f3ff73 Merge pull request #8848 from ndeloof/DOCKER_DEFAULT_PLATFORM
add support for DOCKER_DEFAULT_PLATFORM
2021-10-28 12:54:09 +01:00
Youssef Eldakar
cc5b72b11d Update README.md: typographical edit of "About update..."
Signed-off-by: Youssef Eldakar <youssefeldakar@gmail.com>
2021-10-28 13:18:12 +02:00
Nicolas De Loof
ba08d39187 add support for DOCKER_DEFAULT_PLATFORM
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-28 10:28:51 +02:00
Nicolas De Loof
c4cfaeb12a COMPOSE_COMPATIBILITY can be set by .env file
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-28 10:10:43 +02:00
Mathieu Champlon
b2d2c67032 Merge pull request #8824 from docker/mat007-patch-1
Update link to Docker Community Slack
2021-10-27 07:35:27 +02:00
Mathieu Champlon
9bc1dfe036 Update link to Docker Community Slack
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-10-27 07:33:57 +02:00
Mathieu Champlon
6476e10b93 Merge pull request #8819 from ulyssessouza/dotenv-wrkdir-order
Fix project settings' options order
2021-10-23 09:56:08 +02:00
Ulysses Souza
2530bd981a Fix project settings' options order
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-22 14:59:25 +02:00
Mathieu Champlon
d38a315798 Merge pull request #8816 from resios/patch-1
Actually fix #8811
2021-10-21 14:42:51 +02:00
Andreas Resios
e3204e7c4e Actually fix #8811
The initial PR had the wrong boolean check. This commit addressed it.

fixes #8811

Signed-off-by: Andreas Resios <andrei.resios@gmail.com>
2021-10-21 09:18:08 +00:00
Mathieu Champlon
27f5b8536b Merge pull request #8815 from resios/resios/issue8811
Compose exec cannot process more than 32KB of data
2021-10-21 06:01:59 +02:00
Andreas Resios
85ef72585d Compose exec cannot process more than 32KB of data
Fixes #8811

Signed-off-by: Andreas Resios <andrei.resios@gmail.com>
2021-10-20 15:54:18 +00:00
Ulysses Souza
c3a5eb2269 Merge pull request #8779 from ulyssessouza/build-dockerfile-relativepath
Make service>build>dockerfile a simple filename
2021-10-20 10:51:57 +02:00
Ulysses Souza
94379769e3 Make service>build>dockerfile a simple filename
- It makes it keep a simple filename instead of an absolute path

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-20 10:24:08 +02:00
Ulysses Souza
7d768e7c1d Fix index out of range on compose.buildContainerMountOptions
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-20 10:24:05 +02:00
Ulysses Souza
ea5b094a93 Merge pull request #8792 from ulyssessouza/fix-network-mode-service
Fix network_mode "service:x"
2021-10-20 10:21:52 +02:00
Mathieu Champlon
555b0ab0da Merge pull request #8788 from debdutdeb/8784-timeout-param
Fix compose down --timeout/-t flag
2021-10-19 05:43:45 +02:00
Ulysses Souza
c2dd40c161 Fix network_mode "service:x"
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-14 10:40:44 +02:00
Kyungsik Park
3260dcb121 Fix typo: netwok -> network
Signed-off-by: Kyungsik Park <kay.pak@naverlabs.com>
2021-10-13 13:50:48 +09:00
Debdut Chakraborty
55a214a45e Fix -t flag for compose up command
Signed-off-by: Debdut Chakraborty <debdut.chakraborty@rocket.chat>
2021-10-13 09:11:44 +05:30
Debdut Chakraborty
35b4d1de28 Fix checking -t flag
Signed-off-by: Debdut Chakraborty <debdut.chakraborty@rocket.chat>
2021-10-13 08:59:16 +05:30
Nicolas De Loof
d48068d6e1 pass runtime option to containerCreate
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-12 14:10:26 +02:00
Ulysses Souza
ef786f9245 Fix index out of range on compose.buildContainerMountOptions
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-10-11 13:27:37 +02:00
Ulysses Souza
0062703bea Merge pull request #8726 from ndeloof/log_follow_killed
`log --follow` must stop when container get killed
2021-10-06 18:46:45 +02:00
Mathieu Champlon
c55fde7080 Merge pull request #8755 from Yopadd/patch-2
Add step in README to install on linux
2021-10-06 14:44:29 +02:00
Yoann Bourdex
9ee66b8918 Add step rename binary in install to Linux
Signed-off-by: Yoann Bourdex <yoann.bourdex@gmail.com>
2021-10-06 13:58:20 +02:00
Shikachuu
fc8a433cee Remove the hidden flag from version, added --version and -v flags to root command.
Signed-off-by: Shikachuu <zcmate@gmail.com>
2021-10-04 18:15:43 +02:00
Mathieu Champlon
98fe57baac Make command descriptions consistent
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-10-04 15:10:37 +02:00
Ulysses Souza
fdb90ca179 Merge pull request #8732 from ndeloof/devies
fix support for devices
2021-10-02 14:16:52 +02:00
Nicolas De Loof
1b106f9133 fix support for devices
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-02 09:22:58 +02:00
Nicolas De Loof
4af04b23ec log --follow must stop when container get killed
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-10-01 14:39:14 +02:00
Ulysses Souza
0a81a98b7d Merge pull request #8718 from ulyssessouza/bump-compose-go-1.0.2
Bump compose-go 1.0.2
2021-09-30 17:57:29 +02:00
Ulysses Souza
8bba863935 Bump compose-go 1.0.2
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-09-30 17:23:37 +02:00
Matthias Dötsch
7e6af46af0 stop time.Ticker after use
Signed-off-by: Matthias Dötsch <matthias.doetsch@innogames.com>
2021-09-30 17:09:49 +02:00
Ulysses Souza
693732c4a7 Merge pull request #8710 from ndeloof/uname-m
{Proposal} use `uname -m` for cross platform suffixes
2021-09-30 15:23:17 +02:00
Ulysses Souza
435387c72c Merge pull request #8712 from ndeloof/ps-status-services
allow combination of --status and --services
2021-09-30 12:51:26 +02:00
Ulysses Souza
ecee21b5e5 Merge pull request #8708 from ulyssessouza/standalone
Add standalone capacility
2021-09-30 10:23:02 +02:00
Nicolas De Loof
7c0e865960 allow combination of --status and --services
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-30 09:59:11 +02:00
Nicolas De Loof
c65aed3f9d use uname -m for cross platform suffixes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-30 09:20:24 +02:00
Nicolas De Loof
1faa76ee4e use compose-switch Convert
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-30 09:15:56 +02:00
Kevin De Jong
7365917244 Fix build cache_from option
Signed-off-by: Kevin De Jong <kevin@tinymile.ai>
2021-09-30 08:27:54 +02:00
CrimsonGlory
d2af460d81 change title, v->V, split paragraph
Signed-off-by: CrimsonGlory <javierbassi@gmail.com>
2021-09-30 06:34:00 +02:00
CrimsonGlory
14e42df8f2 Update README.md: way bigger warning
Bigger warning about the v2 breaking changes. At least temporarily. [skip ci]

Signed-off-by: CrimsonGlory <javierbassi@gmail.com>
2021-09-30 06:34:00 +02:00
Ulysses Souza
17354fcc99 Add standalone capacility
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-09-30 02:16:55 +02:00
Ulysses Souza
769ff110f3 Merge pull request #8702 from ndeloof/multiple_status
make --status a multi-value flag
2021-09-29 22:22:14 +02:00
Amin Vakil
3dcbc13742 Add note about installing it system-wide
Signed-off-by: Amin Vakil <info@aminvakil.com>
2021-09-29 20:13:17 +02:00
Nicolas De Loof
7f31fe678f make --status a multi-flag
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-29 17:17:28 +02:00
Nicolas De Loof
6e9d9bf86d typo
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-29 17:01:30 +02:00
Nicolas De Loof
9e749fa03d better document need for compose-switch
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-29 17:00:26 +02:00
Ulysses Souza
436c588793 Bump containerd 1.5.5
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-09-28 20:10:38 +02:00
Ulysses Souza
fdf89e0e16 Merge pull request #8689 from mat007/fix-compose-up-in-readme
Fix compose up in readme
2021-09-28 17:41:41 +02:00
Mathieu Champlon
2b8fd90021 Fix compose up in readme
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-09-28 17:04:39 +02:00
Nicolas De Loof
17d845b3d2 compute sha256 checksums while releasing
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-09-28 16:16:18 +02:00
97 changed files with 3498 additions and 1093 deletions

View File

@@ -20,7 +20,7 @@ The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:
- Docker Support Forums - https://forums.docker.com
- Docker Community Slack - https://dockr.ly/community
- Docker Community Slack - https://dockr.ly/slack
- Post a question on StackOverflow, using the Docker tag
---------------------------------------------------

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

@@ -7,10 +7,10 @@ jobs:
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.16
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
id: go
- name: Checkout code into the Go module directory
@@ -55,4 +55,3 @@ jobs:
body: |
This PR can be tested using [binaries](https://github.com/docker/compose-cli/actions/runs/${{ github.run_id }}).
reactions: eyes

View File

@@ -13,10 +13,10 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.16
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
id: go
- name: Checkout code into the Go module directory
@@ -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:
@@ -40,10 +40,10 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.16
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
id: go
- name: Checkout code into the Go module directory
@@ -59,22 +59,18 @@ 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"
steps:
- name: Set up Go 1.16
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
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

@@ -4,17 +4,17 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'Release Tag'
description: "Release Tag"
required: true
jobs:
upload-release:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.16
- name: Set up Go 1.17
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
id: go
- name: Setup docker CLI
@@ -35,6 +35,9 @@ jobs:
- name: Build
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
- name: Compute checksums
run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
- name: License
run: cp packaging/* bin/

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

@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.16-alpine
ARG GO_VERSION=1.17-alpine
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
ARG PROTOC_GEN_GO_VERSION=v1.4.3

View File

@@ -24,8 +24,8 @@
people = [
"rumpl",
"gtardif",
"ndeloof"
"chris-crone"
"ndeloof",
"chris-crone",
"ulyssessouza"
]

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

@@ -9,7 +9,13 @@ defined using the [Compose file format](https://compose-spec.io).
A Compose file is used to define how the one or more containers that make up
your application are configured.
Once you have a Compose file, you can create and start your application with a
single command: `docker-compose up`.
single command: `docker compose up`.
# About update and backward compatibility
Docker Compose V2 is a major version bump release of Docker Compose. It has been completely rewritten from scratch in Golang (V1 was in Python). The installation instructions for Compose V2 differ from V1. V2 is not a standalone binary anymore, and installation scripts will have to be adjusted. Some commands are different.
For a smooth transition from legacy docker-compose 1.xx, please consider installing [compose-switch](https://github.com/docker/compose-switch) to translate `docker-compose ...` commands into Compose V2's `docker compose .... `. Also check V2's `--compatibility` flag.
# Where to get Docker Compose
@@ -24,9 +30,16 @@ for Windows and macOS.
You can download Docker Compose binaries from the
[release page](https://github.com/docker/compose/releases) on this repository.
Copy the relevant binary for your OS under `$HOME/.docker/cli-plugins/docker-compose`
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
Or copy it into one of these folders for installing it system-wide:
* `/usr/local/lib/docker/cli-plugins` OR `/usr/local/libexec/docker/cli-plugins`
* `/usr/lib/docker/cli-plugins` OR `/usr/libexec/docker/cli-plugins`
(might require to make the downloaded file executable with `chmod +x`)
Quick Start
-----------
@@ -35,7 +48,7 @@ Using Docker Compose is basically a three-step process:
reproduced anywhere.
2. Define the services that make up your app in `docker-compose.yml` so
they can be run together in an isolated environment.
3. Lastly, run `docker-compose up` and Compose will start and run your entire
3. Lastly, run `docker compose up` and Compose will start and run your entire
app.
A Compose file looks like this:

View File

@@ -46,14 +46,14 @@ compose-plugin:
.PHONY: cross
cross:
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-amd64 ./cmd
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-arm64 ./cmd
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
GOOS=linux GOARCH=s390x $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-s390x ./cmd
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-amd64 ./cmd
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-arm64 ./cmd
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-amd64.exe ./cmd
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-x86_64 ./cmd
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-aarch64 ./cmd
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-x86_64.exe ./cmd
.PHONY: test
test:

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

@@ -20,9 +20,12 @@ import (
"context"
"fmt"
"os"
"strings"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
buildx "github.com/docker/buildx/util/progress"
"github.com/docker/compose/v2/pkg/utils"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -39,6 +42,13 @@ type buildOptions struct {
memory string
}
var printerModes = []string{
buildx.PrinterModeAuto,
buildx.PrinterModeTty,
buildx.PrinterModePlain,
buildx.PrinterModeQuiet,
}
func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
opts := buildOptions{
projectOptions: p,
@@ -51,12 +61,16 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
}
if opts.quiet {
opts.progress = buildx.PrinterModeQuiet
devnull, err := os.Open(os.DevNull)
if err != nil {
return err
}
os.Stdout = devnull
}
if !utils.StringContains(printerModes, opts.progress) {
return fmt.Errorf("unsupported --progress value %q", opts.progress)
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
@@ -66,7 +80,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
cmd.Flags().StringVar(&opts.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "noTty")`)
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
cmd.Flags().MarkHidden("parallel") //nolint:errcheck

View File

@@ -25,15 +25,15 @@ import (
"strings"
"syscall"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/sirupsen/logrus"
"github.com/compose-spec/compose-go/cli"
"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/docker/compose/v2/pkg/utils"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -106,7 +106,7 @@ type ProjectFunc func(ctx context.Context, project *types.Project) error
// ProjectServicesFunc does stuff within a types.Project and a selection of services
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
return fn(ctx, project)
@@ -121,24 +121,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)
})
}
@@ -177,6 +159,29 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
return nil, compose.WrapComposeError(err)
}
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 {
@@ -200,15 +205,23 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
return cli.NewProjectOptions(o.ConfigPaths,
append(po,
cli.WithWorkingDirectory(o.ProjectDir),
cli.WithEnvFile(o.EnvFile),
cli.WithDotEnv,
cli.WithOsEnv,
cli.WithWorkingDirectory(o.ProjectDir),
cli.WithConfigFileEnv,
cli.WithDefaultConfigPath,
cli.WithName(o.ProjectName))...)
}
// 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
}
// RootCommand returns the compose command with its child commands
func RootCommand(backend api.Service) *cobra.Command {
opts := projectOptions{}
@@ -216,16 +229,20 @@ func RootCommand(backend api.Service) *cobra.Command {
ansi string
noAnsi bool
verbose bool
version bool
)
command := &cobra.Command{
Short: "Docker Compose",
Use: "compose",
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 {
if len(args) == 0 {
return cmd.Help()
}
if version {
return versionCommand().Execute()
}
_ = cmd.Help()
return dockercli.StatusError{
StatusCode: compose.CommandSyntaxFailure.ExitCode,
@@ -234,11 +251,13 @@ func RootCommand(backend api.Service) *cobra.Command {
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
parent := cmd.Root()
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
err := parentPrerun(cmd, args)
if err != nil {
return err
if parent != nil {
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
err := parentPrerun(cmd, args)
if err != nil {
return err
}
}
}
if noAnsi {
@@ -259,9 +278,6 @@ func RootCommand(backend api.Service) *cobra.Command {
opts.ProjectDir = opts.WorkDir
fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
}
if opts.Compatibility || os.Getenv("COMPOSE_COMPATIBILITY") == "true" {
compose.Separator = "_"
}
return nil
},
}
@@ -296,6 +312,8 @@ func RootCommand(backend api.Service) *cobra.Command {
command.Flags().SetInterspersed(false)
opts.addProjectFlags(command.Flags())
command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
command.Flags().MarkHidden("version") //nolint:errcheck
command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
command.Flags().MarkHidden("no-ansi") //nolint:errcheck
command.Flags().BoolVar(&verbose, "verbose", false, "Show more output")

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(false))
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"
)
@@ -43,10 +47,8 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
downCmd := &cobra.Command{
Use: "down",
Short: "Stop and remove containers, networks",
PreRun: func(cmd *cobra.Command, args []string) {
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.timeChanged = cmd.Flags().Changed("timeout")
},
PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.images != "" {
if opts.images != "all" && opts.images != "local" {
return fmt.Errorf("invalid value for --rmi: %q", opts.images)
@@ -60,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

@@ -21,11 +21,12 @@ import (
"fmt"
"os"
"github.com/compose-spec/compose-go/types"
"github.com/containerd/console"
"github.com/docker/cli/cli"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/spf13/cobra"
)
type execOpts struct {
@@ -69,23 +70,35 @@ 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
}
func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
project, err := opts.toProjectName()
projectName, err := opts.toProjectName()
if err != nil {
return err
}
projectOptions, err := opts.composeOptions.toProjectOptions()
if err != nil {
return err
}
lookupFn := func(k string) (string, bool) {
v, ok := projectOptions.Environment[k]
return v, ok
}
execOpts := api.RunOptions{
Service: opts.service,
Command: opts.command,
Environment: opts.environment,
Environment: compose.ToMobyEnv(types.NewMappingWithEquals(opts.environment).Resolve(lookupFn)),
Tty: !opts.noTty,
User: opts.user,
Privileged: opts.privileged,
@@ -113,7 +126,7 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
execOpts.Stdout = con
execOpts.Stderr = con
}
exitCode, err := backend.Exec(ctx, project, execOpts)
exitCode, err := backend.Exec(ctx, projectName, execOpts)
if exitCode != 0 {
errMsg := ""
if err != nil {

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

@@ -34,7 +34,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
cmd := &cobra.Command{
Use: "pause [SERVICE...]",
Short: "pause services",
Short: "Pause services",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPause(ctx, backend, opts, args)
}),
@@ -64,7 +64,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
cmd := &cobra.Command{
Use: "unpause [SERVICE...]",
Short: "unpause services",
Short: "Unpause services",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runUnPause(ctx, backend, opts, args)
}),

View File

@@ -26,13 +26,13 @@ import (
"strings"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/utils"
formatter2 "github.com/docker/cli/cli/command/formatter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
)
type psOptions struct {
@@ -42,7 +42,7 @@ type psOptions struct {
Quiet bool
Services bool
Filter string
Status string
Status []string
}
func (p *psOptions) parseFilter() error {
@@ -55,11 +55,11 @@ func (p *psOptions) parseFilter() error {
}
switch parts[0] {
case "status":
p.Status = parts[1]
p.Status = append(p.Status, parts[1])
case "source":
return api.ErrNotImplemented
default:
return fmt.Errorf("unknow filter %s", parts[0])
return fmt.Errorf("unknown filter %s", parts[0])
}
return nil
}
@@ -82,7 +82,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags := psCmd.Flags()
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property. Deprecated, use --status instead")
flags.StringVar(&opts.Status, "status", "", "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
flags.BoolVar(&opts.Services, "services", false, "Display services")
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
@@ -103,17 +103,6 @@ func runPs(ctx context.Context, backend api.Service, services []string, opts psO
return err
}
if opts.Services {
services := []string{}
for _, s := range containers {
if !utils.StringContains(services, s.Service) {
services = append(services, s.Service)
}
}
fmt.Println(strings.Join(services, "\n"))
return nil
}
SERVICES:
for _, s := range services {
for _, c := range containers {
@@ -124,7 +113,7 @@ SERVICES:
return fmt.Errorf("no such service: %s", s)
}
if opts.Status != "" {
if len(opts.Status) != 0 {
containers = filterByStatus(containers, opts.Status)
}
@@ -139,6 +128,17 @@ SERVICES:
return nil
}
if opts.Services {
services := []string{}
for _, s := range containers {
if !utils.StringContains(services, s.Service) {
services = append(services, s.Service)
}
}
fmt.Println(strings.Join(services, "\n"))
return nil
}
return formatter.Print(containers, opts.Format, os.Stdout,
writter(containers),
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
@@ -160,22 +160,25 @@ func writter(containers []api.ContainerSummary) func(w io.Writer) {
}
}
func filterByStatus(containers []api.ContainerSummary, status string) []api.ContainerSummary {
hasContainerWithState := map[string]struct{}{}
for _, c := range containers {
if c.State == status {
hasContainerWithState[c.Service] = struct{}{}
}
}
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
var filtered []api.ContainerSummary
for _, c := range containers {
if _, ok := hasContainerWithState[c.Service]; ok {
if hasStatus(c, statuses) {
filtered = append(filtered, c)
}
}
return filtered
}
func hasStatus(c api.ContainerSummary, statuses []string) bool {
for _, status := range statuses {
if c.State == status {
return true
}
}
return false
}
type portRange struct {
pStart int
pEnd int

View File

@@ -65,7 +65,7 @@ func runRemove(ctx context.Context, backend api.Service, opts removeOptions, ser
}
if opts.stop {
err := backend.Stop(ctx, project, api.StopOptions{
err := backend.Stop(ctx, project.Name, api.StopOptions{
Services: services,
})
if err != nil {

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

@@ -25,7 +25,6 @@ import (
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"
@@ -54,6 +53,7 @@ type runOptions struct {
servicePorts bool
name string
noDeps bool
quietPull bool
}
func (opts runOptions) apply(project *types.Project) error {
@@ -143,7 +143,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 +153,12 @@ 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().BoolP("interactive", "i", true, "Keep STDIN open even if not attached. DEPRECATED")
cmd.Flags().MarkHidden("interactive") //nolint:errcheck
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY. DEPRECATED")
cmd.Flags().MarkHidden("tty") //nolint:errcheck
flags.SetNormalizeFunc(normalizeRunFlags)
flags.SetInterspersed(false)
@@ -169,11 +175,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 {
@@ -215,6 +216,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
UseNetworkAliases: opts.useAliases,
NoDeps: opts.noDeps,
Index: 0,
QuietPull: opts.quietPull,
}
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
if exitCode != 0 {
@@ -243,5 +245,5 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
return err
}
return backend.Start(ctx, &project, api.StartOptions{})
return backend.Start(ctx, project.Name, api.StartOptions{})
}

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

@@ -40,7 +40,6 @@ type composeOptions struct {
type upOptions struct {
*composeOptions
Detach bool
Environment []string
noStart bool
noDeps bool
cascadeStop bool
@@ -50,6 +49,7 @@ type upOptions struct {
noPrefix bool
attachDependencies bool
attach []string
wait bool
}
func (opts upOptions) apply(project *types.Project, services []string) error {
@@ -98,30 +98,12 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
upCmd := &cobra.Command{
Use: "up [SERVICE...]",
Short: "Create and start containers",
PreRun: func(cmd *cobra.Command, args []string) {
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
create.timeChanged = cmd.Flags().Changed("timeout")
},
PreRunE: Adapt(func(ctx context.Context, args []string) error {
if up.exitCodeFrom != "" {
up.cascadeStop = true
}
if create.Build && create.noBuild {
return fmt.Errorf("--build and --no-build are incompatible")
}
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
if create.forceRecreate && create.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
}
if create.recreateDeps && create.noRecreate {
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
}
return nil
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")
}
@@ -130,7 +112,6 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
ValidArgsFunction: serviceCompletion(p),
}
flags := upCmd.Flags()
flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
@@ -150,10 +131,36 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
return upCmd
}
func validateFlags(up *upOptions, create *createOptions) error {
if up.exitCodeFrom != "" {
up.cascadeStop = true
}
if up.wait {
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
up.Detach = true
}
if create.Build && create.noBuild {
return fmt.Errorf("--build and --no-build are incompatible")
}
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
if create.forceRecreate && create.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
}
if create.recreateDeps && create.noRecreate {
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
}
return nil
}
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
if len(project.Services) == 0 {
return fmt.Errorf("no service selected")
@@ -201,6 +208,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
AttachTo: attachTo,
ExitCodeFrom: upOptions.exitCodeFrom,
CascadeStop: upOptions.cascadeStop,
Wait: upOptions.wait,
},
})
}

View File

@@ -18,6 +18,7 @@ package compose
import (
"fmt"
"strings"
"github.com/docker/compose/v2/cmd/formatter"
@@ -34,10 +35,9 @@ type versionOptions struct {
func versionCommand() *cobra.Command {
opts := versionOptions{}
cmd := &cobra.Command{
Use: "version",
Short: "Show the Docker Compose version information",
Args: cobra.MaximumNArgs(0),
Hidden: true,
Use: "version",
Short: "Show the Docker Compose version information",
Args: cobra.MaximumNArgs(0),
RunE: func(cmd *cobra.Command, _ []string) error {
runVersion(opts)
return nil
@@ -53,7 +53,7 @@ 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 {

View File

@@ -17,12 +17,15 @@
package main
import (
"os"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command"
"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"
@@ -34,7 +37,7 @@ func init() {
"To provide feedback or request new features please open issues at https://github.com/docker/compose"
}
func main() {
func pluginMain() {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
lazyInit := api.NewServiceProxy()
cmd := commands.RootCommand(lazyInit)
@@ -63,3 +66,10 @@ func main() {
Version: internal.Version,
})
}
func main() {
if commands.RunningAsStandalone() {
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
}
pluginMain()
}

View File

@@ -5,7 +5,7 @@ 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).
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.

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.

View File

@@ -32,7 +32,16 @@ func generateCliYaml(opts *options) error {
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.GenYamlTree(cmd)
}
func disableFlagsInUseLine(cmd *cobra.Command) {

143
go.mod
View File

@@ -1,47 +1,144 @@
module github.com/docker/compose/v2
go 1.16
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.1
github.com/containerd/console v1.0.2
github.com/containerd/containerd v1.5.4
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/buildx v0.7.1
github.com/docker/cli v20.10.12+incompatible
github.com/docker/cli-docs-tool v0.2.1
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/gofrs/flock v0.8.0 // indirect
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/kr/pty v1.1.8 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
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/sergi/go-diff v1.1.0 // indirect
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/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.0.3
k8s.io/client-go v0.21.0 // indirect
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.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.2 // indirect
github.com/cnabio/cnab-go v0.10.0-beta1 // 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.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 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/protobuf v1.5.2 // 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.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/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // 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.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.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.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.11.0 // indirect
github.com/prometheus/client_model v0.2.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-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.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-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.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
)

991
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -37,11 +37,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`
@@ -63,7 +63,7 @@ type Service interface {
// 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`
@@ -124,6 +124,8 @@ type StartOptions struct {
CascadeStop bool
// ExitCodeFrom return exit code from specified service
ExitCodeFrom string
// Wait won't return until containers reached the running|healthy state
Wait bool
}
// RestartOptions group options of the Restart API
@@ -225,6 +227,8 @@ type RunOptions struct {
Privileged bool
UseNetworkAliases bool
NoDeps bool
// QuietPull makes the pulling process quiet
QuietPull bool
// used by exec
Index int
}
@@ -400,10 +404,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
@@ -432,6 +437,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
@@ -41,7 +41,7 @@ type ServiceProxy struct {
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
RemoveFn func(ctx context.Context, project *types.Project, 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
@@ -269,13 +260,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

@@ -20,14 +20,16 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/compose-spec/compose-go/types"
"github.com/containerd/containerd/platforms"
"github.com/docker/buildx/build"
"github.com/docker/buildx/driver"
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
"github.com/docker/buildx/util/buildflags"
xprogress "github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/pkg/urlutil"
bclient "github.com/moby/buildkit/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
@@ -69,7 +71,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
buildOptions.Pull = options.Pull
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, args)
buildOptions.NoCache = options.NoCache
opts[imageName] = buildOptions
buildOptions.CacheFrom, err = buildflags.ParseCacheEntry(service.Build.CacheFrom)
if err != nil {
return err
@@ -81,6 +82,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
Attrs: map[string]string{"ref": image},
})
}
opts[imageName] = buildOptions
}
}
@@ -138,7 +141,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
}
}
@@ -189,64 +192,31 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
return images, nil
}
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
info, err := s.apiClient.Info(ctx)
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
ping, err := s.apiClient.Ping(ctx)
if err != nil {
return nil, err
return command.ServerInfo{}, err
}
serverInfo := command.ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
}
return serverInfo, err
}
if info.OSType == "windows" {
// no support yet for Windows container builds in Buildkit
// https://docs.docker.com/develop/develop-images/build_enhancements/#limitations
err := s.windowsBuild(opts, mode)
return nil, WrapCategorisedComposeError(err, BuildFailure)
}
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
if len(opts) == 0 {
return nil, nil
}
const drivername = "default"
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
serverInfo, err := s.serverInfo(ctx)
if err != nil {
return nil, err
}
driverInfo := []build.DriverInfo{
{
Name: "default",
Driver: d,
},
if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
return s.doBuildClassic(ctx, opts)
}
// Progress needs its own context that lives longer than the
// build one otherwise it won't read all the messages from
// build and will lock
progressCtx, cancel := context.WithCancel(context.Background())
defer cancel()
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)
errW := w.Wait()
if err == nil {
err = errW
}
if err != nil {
return nil, WrapCategorisedComposeError(err, BuildFailure)
}
imagesBuilt := map[string]string{}
for name, img := range response {
if img == nil || len(img.ExporterResponse) == 0 {
continue
}
digest, ok := img.ExporterResponse["containerimage.digest"]
if !ok {
continue
}
imagesBuilt[name] = digest
}
return imagesBuilt, err
return s.doBuildBuildkit(ctx, project, opts, mode)
}
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
@@ -259,6 +229,13 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
}))
var plats []specs.Platform
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
p, err := platforms.Parse(platform)
if err != nil {
return build.Options{}, err
}
plats = append(plats, p)
}
if service.Platform != "" {
p, err := platforms.Parse(service.Platform)
if err != nil {
@@ -270,7 +247,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
return build.Options{
Inputs: build.Inputs{
ContextPath: service.Build.Context,
DockerfilePath: service.Build.Dockerfile,
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
},
BuildArgs: buildArgs,
Tags: tags,
@@ -309,3 +286,10 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
}
return merged
}
func dockerFilePath(context string, dockerfile string) string {
if urlutil.IsGitURL(context) || filepath.IsAbs(dockerfile) {
return dockerfile
}
return filepath.Join(context, dockerfile)
}

View File

@@ -0,0 +1,73 @@
/*
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 compose
import (
"context"
"os"
"path/filepath"
"github.com/compose-spec/compose-go/types"
"github.com/docker/buildx/build"
"github.com/docker/buildx/driver"
xprogress "github.com/docker/buildx/util/progress"
)
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, nil, project.WorkingDir)
if err != nil {
return nil, err
}
driverInfo := []build.DriverInfo{
{
Name: drivername,
Driver: d,
},
}
// Progress needs its own context that lives longer than the
// build one otherwise it won't read all the messages from
// build and will lock
progressCtx, cancel := context.WithCancel(context.Background())
defer cancel()
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, filepath.Dir(s.configFile.Filename), w)
errW := w.Wait()
if err == nil {
err = errW
}
if err != nil {
return nil, WrapCategorisedComposeError(err, BuildFailure)
}
imagesBuilt := map[string]string{}
for name, img := range response {
if img == nil || len(img.ExporterResponse) == 0 {
continue
}
digest, ok := img.ExporterResponse["containerimage.digest"]
if !ok {
continue
}
imagesBuilt[name] = digest
}
return imagesBuilt, err
}

View File

@@ -0,0 +1,251 @@
/*
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 compose
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
buildx "github.com/docker/buildx/build"
"github.com/docker/cli/cli/command/image/build"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)
func (s *composeService) doBuildClassic(ctx context.Context, opts map[string]buildx.Options) (map[string]string, error) {
var nameDigests = make(map[string]string)
var errs error
for name, o := range opts {
digest, err := s.doBuildClassicSimpleImage(ctx, o)
if err != nil {
errs = multierror.Append(errs, err).ErrorOrNil()
}
nameDigests[name] = digest
}
return nameDigests, errs
}
// nolint: gocyclo
func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
var (
buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
contextDir string
tempDir string
relDockerfile string
err error
)
dockerfileName := options.Inputs.DockerfilePath
specifiedContext := options.Inputs.ContextPath
progBuff := os.Stdout
buildBuff := os.Stdout
if options.ImageIDFile != "" {
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
return "", errors.Wrap(err, "removing image ID file")
}
}
switch {
case isLocalDir(specifiedContext):
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName)
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
dockerfileCtx, err = os.Open(dockerfileName)
if err != nil {
return "", errors.Errorf("unable to open Dockerfile: %v", err)
}
defer dockerfileCtx.Close() // nolint:errcheck
}
case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, dockerfileName)
case urlutil.IsURL(specifiedContext):
buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, dockerfileName)
default:
return "", errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
}
if err != nil {
return "", errors.Errorf("unable to prepare context: %s", err)
}
if tempDir != "" {
defer os.RemoveAll(tempDir) // nolint:errcheck
contextDir = tempDir
}
// read from a directory into tar archive
if buildCtx == nil {
excludes, err := build.ReadDockerignore(contextDir)
if err != nil {
return "", err
}
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
return "", errors.Wrap(err, "checking context")
}
// And canonicalize dockerfile name to a platform-independent one
relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, false)
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
ExcludePatterns: excludes,
ChownOpts: &idtools.Identity{},
})
if err != nil {
return "", err
}
}
// replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
if dockerfileCtx != nil && buildCtx != nil {
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
if err != nil {
return "", err
}
}
buildCtx, err = build.Compress(buildCtx)
if err != nil {
return "", err
}
// if up to this point nothing has set the context then we must have another
// way for sending it(streaming) and set the context to the Dockerfile
if dockerfileCtx != nil && buildCtx == nil {
buildCtx = dockerfileCtx
}
progressOutput := streamformatter.NewProgressOutput(progBuff)
var body io.Reader
if buildCtx != nil {
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
}
configFile := s.configFile
creds, err := configFile.GetAllCredentials()
if err != nil {
return "", err
}
authConfigs := make(map[string]dockertypes.AuthConfig, len(creds))
for k, auth := range creds {
authConfigs[k] = dockertypes.AuthConfig(auth)
}
buildOptions := imageBuildOptions(options)
buildOptions.Version = dockertypes.BuilderV1
buildOptions.Dockerfile = relDockerfile
buildOptions.AuthConfigs = authConfigs
ctx, cancel := context.WithCancel(ctx)
defer cancel()
response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
if err != nil {
return "", err
}
defer response.Body.Close() // nolint:errcheck
imageID := ""
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)
} else {
imageID = result.ID
}
}
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
if jerr.Code == 0 {
jerr.Code = 1
}
return "", cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
return "", err
}
// Windows: show error message about modified file permissions if the
// 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 "+
"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 "+
"files and directories.")
}
if options.ImageIDFile != "" {
if imageID == "" {
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
}
if err := ioutil.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
return "", err
}
}
return imageID, nil
}
func isLocalDir(c string) bool {
_, err := os.Stat(c)
return err == nil
}
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,
NetworkMode: options.NetworkMode,
ExtraHosts: options.ExtraHosts,
Target: options.Target,
}
}
func toMapStringStringPtr(source map[string]string) map[string]*string {
dest := make(map[string]*string)
for k, v := range source {
v := v
dest[k] = &v
}
return dest
}

View File

@@ -24,6 +24,7 @@ import (
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/pkg/errors"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile"
@@ -32,6 +33,7 @@ import (
"github.com/sanathkr/go-yaml"
)
// Separator is used for naming components
var Separator = "-"
// NewComposeService create a local implementation of the compose.Service API
@@ -91,3 +93,59 @@ 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.New("no such project: " + 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
}

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"
@@ -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

@@ -109,56 +109,61 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
var mu sync.Mutex
// updateProject updates project after service converged, so dependent services relying on `service:xx` can refer to actual containers.
func (c *convergence) updateProject(project *types.Project, service string) {
containers := c.getObservedState(service)
if len(containers) == 0 {
return
}
container := containers[0]
func (c *convergence) updateProject(project *types.Project, serviceName string) {
// operation is protected by a Mutex so that we can safely update project.Services while running concurrent convergence on services
mu.Lock()
defer mu.Unlock()
cnts := c.getObservedState(serviceName)
for i, s := range project.Services {
if d := getDependentServiceFromMode(s.NetworkMode); d == service {
s.NetworkMode = types.NetworkModeContainerPrefix + container.ID
}
if d := getDependentServiceFromMode(s.Ipc); d == service {
s.Ipc = types.NetworkModeContainerPrefix + container.ID
}
if d := getDependentServiceFromMode(s.Pid); d == service {
s.Pid = types.NetworkModeContainerPrefix + container.ID
}
var links []string
for _, serviceLink := range s.Links {
parts := strings.Split(serviceLink, ":")
serviceName := serviceLink
serviceAlias := ""
if len(parts) == 2 {
serviceName = parts[0]
serviceAlias = parts[1]
}
if serviceName != service {
links = append(links, serviceLink)
continue
}
for _, container := range containers {
name := getCanonicalContainerName(container)
if serviceAlias != "" {
links = append(links,
fmt.Sprintf("%s:%s", name, serviceAlias))
}
links = append(links,
fmt.Sprintf("%s:%s", name, name),
fmt.Sprintf("%s:%s", name, getContainerNameWithoutProject(container)))
}
s.Links = links
}
updateServices(&s, cnts)
project.Services[i] = s
}
}
func updateServices(service *types.ServiceConfig, cnts Containers) {
if len(cnts) == 0 {
return
}
cnt := cnts[0]
serviceName := cnt.Labels[api.ServiceLabel]
if d := getDependentServiceFromMode(service.NetworkMode); d == serviceName {
service.NetworkMode = types.NetworkModeContainerPrefix + cnt.ID
}
if d := getDependentServiceFromMode(service.Ipc); d == serviceName {
service.Ipc = types.NetworkModeContainerPrefix + cnt.ID
}
if d := getDependentServiceFromMode(service.Pid); d == serviceName {
service.Pid = types.NetworkModeContainerPrefix + cnt.ID
}
var links []string
for _, serviceLink := range service.Links {
parts := strings.Split(serviceLink, ":")
serviceName := serviceLink
serviceAlias := ""
if len(parts) == 2 {
serviceName = parts[0]
serviceAlias = parts[1]
}
if serviceName != service.Name {
links = append(links, serviceLink)
continue
}
for _, container := range cnts {
name := getCanonicalContainerName(container)
if serviceAlias != "" {
links = append(links,
fmt.Sprintf("%s:%s", name, serviceAlias))
}
links = append(links,
fmt.Sprintf("%s:%s", name, name),
fmt.Sprintf("%s:%s", name, getContainerNameWithoutProject(container)))
}
service.Links = links
}
}
func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error {
expected, err := getScale(service)
if err != nil {
@@ -256,9 +261,33 @@ func getContainerProgressName(container moby.Container) string {
return "Container " + getCanonicalContainerName(container)
}
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
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)
for dep, config := range service.DependsOn {
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)
@@ -266,12 +295,22 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
for {
<-ticker.C
switch config.Condition {
case types.ServiceConditionHealthy:
healthy, err := s.isServiceHealthy(ctx, project, dep)
case ServiceConditionRunningOrHealthy:
healthy, err := s.isServiceHealthy(ctx, project, dep, true)
if err != nil {
return err
}
if healthy {
w.Events(containerEvents(containers, progress.Healthy))
return nil
}
case types.ServiceConditionHealthy:
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
if err != nil {
return err
}
if healthy {
w.Events(containerEvents(containers, progress.Healthy))
return nil
}
case types.ServiceConditionCompletedSuccessfully:
@@ -280,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
@@ -298,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 {
@@ -433,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]
@@ -461,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] {
@@ -497,7 +609,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
return nil
}
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string) (bool, error) {
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) {
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service)
if err != nil {
return false, err
@@ -511,11 +623,23 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
if err != nil {
return false, err
}
if container.Config.Healthcheck == nil && fallbackRunning {
// Container does not define a health check, but we can fall back to "running" state
return container.State != nil && container.State.Status == "running", nil
}
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
@@ -539,7 +663,11 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
}
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
err := s.waitDependencies(ctx, project, service)
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
}
@@ -559,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)

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,163 @@ 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)
tested.apiClient = apiClient
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)
tested.apiClient = apiClient
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)
tested.apiClient = apiClient
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)
tested.apiClient = apiClient
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)
tested.apiClient = apiClient
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()
api := mocks.NewMockAPIClient(mockCtrl)
tested.apiClient = api
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{}

View File

@@ -210,7 +210,7 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
err := s.ensureVolume(ctx, volume)
err := s.ensureVolume(ctx, volume, project.Name)
if err != nil {
return err
}
@@ -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
}
@@ -378,7 +378,9 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
PidMode: container.PidMode(service.Pid),
Tmpfs: tmpfs,
Isolation: container.Isolation(service.Isolation),
Runtime: service.Runtime,
LogConfig: logConfig,
GroupAdd: service.GroupAdd,
}
return &containerConfig, &hostConfig, networkConfig, nil
@@ -412,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 {
@@ -492,6 +491,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
MemorySwap: int64(s.MemSwapLimit),
MemorySwappiness: swappiness,
MemoryReservation: int64(s.MemReservation),
OomKillDisable: &s.OomKillDisable,
CPUCount: s.CPUCount,
CPUPeriod: s.CPUPeriod,
CPUQuota: s.CPUQuota,
@@ -502,6 +502,10 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
CpusetCpus: s.CPUSet,
}
if s.PidsLimit != 0 {
resources.PidsLimit = &s.PidsLimit
}
setBlkio(s.BlkioConfig, &resources)
if s.Deploy != nil {
@@ -525,6 +529,9 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
case 1:
src = arr[0]
}
if dst == "" {
dst = src
}
resources.Devices = append(resources.Devices, container.DeviceMapping{
PathOnHost: src,
PathInContainer: dst,
@@ -631,15 +638,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
}
@@ -709,11 +712,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
}
}
@@ -723,6 +722,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 {
@@ -747,24 +763,21 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby
}
}
}
for i, v := range s.Volumes {
if v.Target != m.Destination {
volumes := []types.ServiceVolumeConfig{}
for _, v := range s.Volumes {
if v.Target != m.Destination || v.Source != "" {
volumes = append(volumes, v)
continue
}
if v.Source == "" {
// inherit previous container's anonymous volume
mounts[m.Destination] = mount.Mount{
Type: m.Type,
Source: src,
Target: m.Destination,
ReadOnly: !m.RW,
}
// Avoid mount to be later re-defined
l := len(s.Volumes) - 1
s.Volumes[i] = s.Volumes[l]
s.Volumes = s.Volumes[:l]
// inherit previous container's anonymous volume
mounts[m.Destination] = mount.Mount{
Type: m.Type,
Source: src,
Target: m.Destination,
ReadOnly: !m.RW,
}
}
s.Volumes = volumes
}
}
@@ -946,8 +959,8 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
if volume.Bind != nil {
logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
}
if volume.Tmpfs != nil {
logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
if volume.Volume != nil {
logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
}
return nil, nil, buildTmpfsOptions(volume.Tmpfs)
}
@@ -980,7 +993,7 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
return nil
}
return &mount.TmpfsOptions{
SizeBytes: tmpfs.Size,
SizeBytes: int64(tmpfs.Size),
// Mode: , // FIXME missing from model ?
}
}
@@ -993,21 +1006,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{})
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
@@ -1034,6 +1045,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
Internal: n.Internal,
Attachable: n.Attachable,
IPAM: ipam,
EnableIPv6: n.EnableIPv6,
}
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
@@ -1079,27 +1091,48 @@ func (s *composeService) removeNetwork(ctx context.Context, networkID string, ne
return nil
}
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
// TODO could identify volume by label vs name
_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
inspected, err := s.apiClient.VolumeInspect(ctx, volume.Name)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName))
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
Labels: volume.Labels,
Name: volume.Name,
Driver: volume.Driver,
DriverOpts: volume.DriverOpts,
})
if err != nil {
w.Event(progress.ErrorEvent(eventName))
return err
if volume.External.External {
return fmt.Errorf("external volume %q not found", volume.Name)
}
w.Event(progress.CreatedEvent(eventName))
err := s.createVolume(ctx, volume)
return err
}
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 {
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
}
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
}
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName))
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
Labels: volume.Labels,
Name: volume.Name,
Driver: volume.Driver,
DriverOpts: volume.DriverOpts,
})
if err != nil {
w.Event(progress.ErrorEvent(eventName))
return err
}
w.Event(progress.CreatedEvent(eventName))
return nil
}

View File

@@ -19,12 +19,13 @@ package compose
import (
"os"
"path/filepath"
"sort"
"testing"
"github.com/docker/compose/v2/pkg/api"
"github.com/compose-spec/compose-go/types"
composetypes "github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
mountTypes "github.com/docker/docker/api/types/mount"
"gotest.tools/v3/assert"
)
@@ -81,3 +82,143 @@ func TestPrepareNetworkLabels(t *testing.T) {
"com.docker.compose.version": api.ComposeVersion,
}))
}
func TestBuildContainerMountOptions(t *testing.T) {
project := composetypes.Project{
Name: "myProject",
Services: []composetypes.ServiceConfig{
{
Name: "myService",
Volumes: []composetypes.ServiceVolumeConfig{
{
Type: composetypes.VolumeTypeVolume,
Target: "/var/myvolume1",
},
{
Type: composetypes.VolumeTypeVolume,
Target: "/var/myvolume2",
},
},
},
},
Volumes: composetypes.Volumes(map[string]composetypes.VolumeConfig{
"myVolume1": {
Name: "myProject_myVolume1",
},
"myVolume2": {
Name: "myProject_myVolume2",
},
}),
}
inherit := &moby.Container{
Mounts: []moby.MountPoint{
{
Type: composetypes.VolumeTypeVolume,
Destination: "/var/myvolume1",
},
{
Type: composetypes.VolumeTypeVolume,
Destination: "/var/myvolume2",
},
},
}
mounts, err := buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
sort.Slice(mounts, func(i, j int) bool {
return mounts[i].Target < mounts[j].Target
})
assert.NilError(t, err)
assert.Assert(t, len(mounts) == 2)
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
sort.Slice(mounts, func(i, j int) bool {
return mounts[i].Target < mounts[j].Target
})
assert.NilError(t, err)
assert.Assert(t, len(mounts) == 2)
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 {
@@ -75,7 +79,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
}
}
ops, err := s.ensureNetwoksDown(ctx, projectName)
ops, err := s.ensureNetworksDown(ctx, projectName)
if err != nil {
return err
}
@@ -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 {
@@ -129,7 +125,7 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
return ops
}
func (s *composeService) ensureNetwoksDown(ctx context.Context, projectName string) ([]downOp, error) {
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))})
if err != nil {
@@ -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

@@ -44,6 +44,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)
@@ -74,6 +76,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)
@@ -100,13 +104,16 @@ func TestDownRemoveVolumes(t *testing.T) {
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

@@ -90,13 +90,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)
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
if err != nil {
return err
}
in := streams.NewIn(opts.Stdin)
if in.IsTerminal() {
if in.IsTerminal() && opts.Tty {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return 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

@@ -33,6 +33,7 @@ import (
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
All: true,
Filters: filters.NewArgs(projectFilter(projectName)),
})
if err != 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
}

View File

@@ -45,7 +45,9 @@ func TestKillAll(t *testing.T) {
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)
@@ -64,9 +66,7 @@ func TestKillSignal(t *testing.T) {
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,21 +34,43 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
}
eg, ctx := errgroup.WithContext(ctx)
if options.Follow {
eg.Go(func() error {
printer := newLogPrinter(consumer)
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
return s.logContainers(ctx, consumer, c, options)
})
})
}
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()
}

View File

@@ -20,8 +20,10 @@ 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"
@@ -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)
}
}

View File

@@ -33,7 +33,7 @@ func (s *composeService) Pause(ctx context.Context, project string, options api.
}
func (s *composeService) pause(ctx context.Context, project string, options api.PauseOptions) error {
containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
if err != nil {
return err
}
@@ -61,7 +61,7 @@ func (s *composeService) UnPause(ctx context.Context, project string, options ap
}
func (s *composeService) unPause(ctx context.Context, project string, options api.PauseOptions) error {
containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
if err != nil {
return err
}

View File

@@ -17,6 +17,7 @@
package compose
import (
"context"
"fmt"
"github.com/docker/compose/v2/pkg/api"
@@ -27,7 +28,7 @@ import (
// logPrinter watch application containers an collect their logs
type logPrinter interface {
HandleEvent(event api.ContainerEvent)
Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
Cancel()
}
@@ -56,56 +57,61 @@ func (p *printer) HandleEvent(event api.ContainerEvent) {
p.queue <- event
}
func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
//nolint:gocyclo
func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
var (
aborting bool
exitCode int
)
containers := map[string]struct{}{}
for {
event := <-p.queue
container := event.Container
switch event.Type {
case api.UserCancel:
aborting = true
case api.ContainerEventAttach:
if _, ok := containers[container]; ok {
continue
}
containers[container] = struct{}{}
p.consumer.Register(container)
case api.ContainerEventExit:
if !event.Restarting {
delete(containers, container)
}
if !aborting {
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
}
if cascadeStop {
select {
case <-ctx.Done():
return exitCode, ctx.Err()
case event := <-p.queue:
container := event.Container
switch event.Type {
case api.UserCancel:
aborting = true
case api.ContainerEventAttach:
if _, ok := containers[container]; ok {
continue
}
containers[container] = struct{}{}
p.consumer.Register(container)
case api.ContainerEventExit, api.ContainerEventStopped:
if !event.Restarting {
delete(containers, container)
}
if !aborting {
aborting = true
fmt.Println("Aborting on container exit...")
err := stopFn()
if err != nil {
return 0, err
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
}
if cascadeStop {
if !aborting {
aborting = true
fmt.Println("Aborting on container exit...")
err := stopFn()
if err != nil {
return 0, err
}
}
if exitCodeFrom == "" {
exitCodeFrom = event.Service
}
if exitCodeFrom == event.Service {
logrus.Error(event.ExitCode)
exitCode = event.ExitCode
}
}
if exitCodeFrom == "" {
exitCodeFrom = event.Service
if len(containers) == 0 {
// Last container terminated, done
return exitCode, nil
}
if exitCodeFrom == event.Service {
logrus.Error(event.ExitCode)
exitCode = event.ExitCode
case api.ContainerEventLog:
if !aborting {
p.consumer.Log(container, event.Service, event.Line)
}
}
if len(containers) == 0 {
// Last container terminated, done
return exitCode, nil
}
case api.ContainerEventLog:
if !aborting {
p.consumer.Log(container, event.Service, event.Line)
}
}
}
}

View File

@@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api"
"golang.org/x/sync/errgroup"
@@ -27,14 +26,20 @@ import (
"github.com/docker/compose/v2/pkg/utils"
)
func (s *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.restart(ctx, project, options)
return s.restart(ctx, projectName, options)
})
}
func (s *composeService) restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {
observedState, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return err
}
project, err := s.projectFromName(observedState, projectName, options.Services...)
if err != nil {
return err
}

View File

@@ -51,7 +51,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
}
func (s *composeService) runInteractive(ctx context.Context, containerID string, opts api.RunOptions) (int, error) {
r, err := s.getEscapeKeyProxy(opts.Stdin)
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
if err != nil {
return 0, err
}
@@ -62,7 +62,7 @@ func (s *composeService) runInteractive(ctx context.Context, containerID string,
}
in := streams.NewIn(opts.Stdin)
if in.IsTerminal() {
if in.IsTerminal() && opts.Tty {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return 0, err
@@ -153,17 +153,25 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
if service.Deploy != nil {
service.Deploy.RestartPolicy = nil
}
service.Labels = service.Labels.Add(api.SlugLabel, slug)
service.Labels = service.Labels.Add(api.OneoffLabel, "True")
service.CustomLabels = service.CustomLabels.
Add(api.SlugLabel, slug).
Add(api.OneoffLabel, "True")
if err := s.ensureImagesExists(ctx, project, false); err != nil { // all dependencies already checked, but might miss service img
if err := s.ensureImagesExists(ctx, project, opts.QuietPull); err != nil { // all dependencies already checked, but might miss service img
return "", err
}
if !opts.NoDeps {
if err := s.waitDependencies(ctx, project, service); err != nil {
if err := s.waitDependencies(ctx, project, service.DependsOn); err != nil {
return "", err
}
}
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
if err != nil {
return "", err
}
updateServices(&service, observedState)
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.Detach && opts.AutoRemove, opts.UseNetworkAliases, true)
if err != nil {
return "", err
@@ -172,7 +180,10 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
return containerID, nil
}
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser) (io.ReadCloser, error) {
func (s *composeService) getEscapeKeyProxy(r io.ReadCloser, isTty bool) (io.ReadCloser, error) {
if !isTty {
return r, nil
}
var escapeKeys = []byte{16, 17}
if s.configFile.DetachKeys != "" {
customEscapeKeys, err := term.ToBytes(s.configFile.DetachKeys)

View File

@@ -28,15 +28,22 @@ import (
"github.com/docker/compose/v2/pkg/progress"
)
func (s *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error {
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.start(ctx, project, options, nil)
return s.start(ctx, projectName, options, nil)
})
}
func (s *composeService) start(ctx context.Context, project *types.Project, options api.StartOptions, listener api.ContainerEventListener) error {
if len(options.AttachTo) == 0 {
options.AttachTo = project.ServiceNames()
func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error {
var containers Containers
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
if err != nil {
return err
}
project, err := s.projectFromName(containers, projectName, options.AttachTo...)
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
@@ -53,16 +60,31 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
})
}
err := InDependencyOrder(ctx, project, func(c context.Context, name string) error {
err = InDependencyOrder(ctx, project, func(c context.Context, name string) error {
service, err := project.GetService(name)
if err != nil {
return err
}
return s.startService(ctx, project, service)
})
if err != nil {
return err
}
if options.Wait {
depends := types.DependsOnConfig{}
for _, s := range project.Services {
depends[s.Name] = types.ServiceDependency{
Condition: ServiceConditionRunningOrHealthy,
}
}
err = s.waitDependencies(ctx, project, depends)
if err != nil {
return err
}
}
return eg.Wait()
}
@@ -79,6 +101,12 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
err := s.Events(ctx, projectName, api.EventsOptions{
Services: services,
Consumer: func(event api.Event) error {
if event.Status == "destroy" {
// This container can't be inspected, because it's gone.
// It's already been removed from the watched map.
return nil
}
inspected, err := s.apiClient.ContainerInspect(ctx, event.Container)
if err != nil {
return err
@@ -90,11 +118,26 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
}
name := getContainerNameWithoutProject(container)
if event.Status == "stop" {
listener(api.ContainerEvent{
Type: api.ContainerEventStopped,
Container: name,
Service: container.Labels[api.ServiceLabel],
})
delete(watched, container.ID)
if len(watched) == 0 {
// all project containers stopped, we're done
stop()
}
return nil
}
if event.Status == "die" {
restarted := watched[container.ID]
watched[container.ID] = restarted + 1
// Container terminated.
willRestart := inspected.HostConfig.RestartPolicy.MaximumRetryCount > restarted
willRestart := willContainerRestart(inspected, restarted)
listener(api.ContainerEvent{
Type: api.ContainerEventExit,
@@ -141,3 +184,14 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
}
return err
}
func willContainerRestart(container moby.ContainerJSON, restarted int) bool {
policy := container.HostConfig.RestartPolicy
if policy.IsAlways() || policy.IsUnlessStopped() {
return true
}
if policy.IsOnFailure() {
return container.State.ExitCode != 0 && policy.MaximumRetryCount > restarted
}
return false
}

View File

@@ -21,25 +21,29 @@ import (
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
"github.com/compose-spec/compose-go/types"
)
func (s *composeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.stop(ctx, project, options)
return s.stop(ctx, projectName, options)
})
}
func (s *composeService) stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
w := progress.ContextWriter(ctx)
services := options.Services
if len(services) == 0 {
services = project.ServiceNames()
services = []string{}
}
var containers Containers
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true, services...)
if err != nil {
return err
}
project, err := s.projectFromName(containers, projectName, services...)
if err != nil {
return err
}

View File

@@ -25,7 +25,6 @@ import (
compose "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/golang/mock/gomock"
"gotest.tools/v3/assert"
@@ -50,13 +49,7 @@ func TestStopTimeout(t *testing.T) {
api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
err := tested.Stop(ctx, &types.Project{
Name: strings.ToLower(testProject),
Services: []types.ServiceConfig{
{Name: "service1"},
{Name: "service2"},
},
}, compose.StopOptions{
err := tested.Stop(ctx, strings.ToLower(testProject), compose.StopOptions{
Timeout: &timeout,
})
assert.NilError(t, err)

View File

@@ -38,7 +38,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return err
}
if options.Start.Attach == nil {
return s.start(ctx, project, options.Start, nil)
return s.start(ctx, project.Name, options.Start, nil)
}
return nil
})
@@ -65,7 +65,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
})
}()
return s.Stop(ctx, project, api.StopOptions{
return s.Stop(ctx, project.Name, api.StopOptions{
Services: options.Create.Services,
})
})
@@ -80,12 +80,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
var exitCode int
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
code, err := printer.Run(context.Background(), options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
exitCode = code
return err
})
err = s.start(ctx, project, options.Start, printer.HandleEvent)
err = s.start(ctx, project.Name, options.Start, printer.HandleEvent)
if err != nil {
return err
}

View File

@@ -36,7 +36,7 @@ func TestComposeCancel(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("metrics on cancel Compose build", func(t *testing.T) {
c.RunDockerCmd("compose", "ls")
c.RunDockerComposeCmd("ls")
buildProjectPath := "fixtures/build-infinite/compose.yaml"
// require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal.

View File

@@ -45,6 +45,6 @@ func TestCascadeStop(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}

View File

@@ -17,10 +17,7 @@
package e2e
import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -37,7 +34,7 @@ func TestLocalComposeBuild(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test_nginx")
c.RunDockerOrExitError("rmi", "custom-nginx")
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "build")
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
c.RunDockerCmd("image", "inspect", "build-test_nginx")
@@ -49,7 +46,7 @@ func TestLocalComposeBuild(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test_nginx")
c.RunDockerOrExitError("rmi", "custom-nginx")
c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
res := c.RunDockerCmd("image", "inspect", "build-test_nginx")
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
@@ -69,13 +66,26 @@ func TestLocalComposeBuild(t *testing.T) {
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
})
t.Run("build with multiple build-args ", func(t *testing.T) {
// ensure local test run does not reuse previously build image
c.RunDockerOrExitError("rmi", "-f", "multi-args_multiargs")
cmd := c.NewDockerCmd("compose", "--project-directory", "fixtures/build-test/multi-args", "build")
icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
})
res := c.RunDockerCmd("image", "inspect", "multi-args_multiargs")
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
})
t.Run("build as part of up", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test_nginx")
c.RunDockerOrExitError("rmi", "custom-nginx")
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "up", "-d")
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "up", "-d")
t.Cleanup(func() {
c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "down")
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "down")
})
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
@@ -89,59 +99,21 @@ func TestLocalComposeBuild(t *testing.T) {
})
t.Run("no rebuild when up again", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "up", "-d")
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "up", "-d")
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
})
t.Run("rebuild when up --build", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--workdir", "fixtures/build-test", "up", "-d", "--build")
res := c.RunDockerComposeCmd("--workdir", "fixtures/build-test", "up", "-d", "--build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
})
t.Run("cleanup build project", func(t *testing.T) {
c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "down")
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "down")
c.RunDockerCmd("rmi", "build-test_nginx")
c.RunDockerCmd("rmi", "custom-nginx")
})
}
func TestLocalComposeBuildStaticDockerfilePath(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("build ddev-style compose files", func(t *testing.T) {
dir, err := ioutil.TempDir("", "ddev")
assert.NilError(t, err)
defer os.RemoveAll(dir) //nolint:errcheck
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "compose.yaml"), []byte(`services:
service1:
build:
context: `+dir+`/service1
dockerfile: Dockerfile
service2:
build:
context: `+dir+`/service2
dockerfile: `+dir+`/service2/Dockerfile
`), 0644))
assert.NilError(t, os.Mkdir(filepath.Join(dir, "service1"), 0700))
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "service1", "Dockerfile"), []byte(`FROM alpine
RUN echo "hello"
`), 0644))
assert.NilError(t, os.Mkdir(filepath.Join(dir, "service2"), 0700))
assert.NilError(t, ioutil.WriteFile(filepath.Join(dir, "service2", "Dockerfile"), []byte(`FROM alpine
RUN echo "world"
`), 0644))
res := c.RunDockerCmd("compose", "-f", filepath.Join(dir, "compose.yaml"), "build")
res.Assert(t, icmd.Expected{Out: `RUN echo "hello"`})
res.Assert(t, icmd.Expected{Out: `RUN echo "world"`})
c.RunDockerCmd("compose", "-f", filepath.Join(dir, "compose.yaml"), "down", "--rmi", "all")
})
}

View File

@@ -29,7 +29,7 @@ func TestLocalComposeExec(t *testing.T) {
const projectName = "compose-e2e-exec"
c.RunDockerCmd("compose", "--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
t.Run("exec true", func(t *testing.T) {
res := c.RunDockerOrExitError("exec", "compose-e2e-exec-simple-1", "/bin/true")

View File

@@ -29,11 +29,11 @@ func TestLocalComposeRun(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("compose run", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "back")
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "back")
lines := Lines(res.Stdout())
assert.Equal(t, lines[len(lines)-1], "Hello there!!", res.Stdout())
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
res = c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello one more time")
res = c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello one more time")
lines = Lines(res.Stdout())
assert.Equal(t, lines[len(lines)-1], "Hello one more time", res.Stdout())
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
@@ -68,7 +68,7 @@ func TestLocalComposeRun(t *testing.T) {
})
t.Run("compose run --rm", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo", "Hello again")
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo", "Hello again")
lines := Lines(res.Stdout())
assert.Equal(t, lines[len(lines)-1], "Hello again", res.Stdout())
@@ -77,7 +77,7 @@ func TestLocalComposeRun(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "down")
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
res := c.RunDockerCmd("ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
})
@@ -85,7 +85,7 @@ func TestLocalComposeRun(t *testing.T) {
t.Run("compose run --volumes", func(t *testing.T) {
wd, err := os.Getwd()
assert.NilError(t, err)
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo")
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo")
res.Assert(t, icmd.Expected{Out: "compose_run_test.go"})
res = c.RunDockerCmd("ps", "--all")
@@ -93,13 +93,13 @@ func TestLocalComposeRun(t *testing.T) {
})
t.Run("compose run --publish", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
res := c.RunDockerCmd("ps")
assert.Assert(t, strings.Contains(res.Stdout(), "8081->80/tcp"), res.Stdout())
})
t.Run("down", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "down")
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
res := c.RunDockerCmd("ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
})

View File

@@ -33,22 +33,17 @@ import (
var binDir string
func TestMain(m *testing.M) {
exitCode := m.Run()
os.Exit(exitCode)
}
func TestLocalComposeUp(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
const projectName = "compose-e2e-demo"
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("check accessing running app", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
res := c.RunDockerComposeCmd("-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `web`})
endpoint := "http://localhost:90"
@@ -60,7 +55,7 @@ func TestLocalComposeUp(t *testing.T) {
})
t.Run("top", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "top")
res := c.RunDockerComposeCmd("-p", projectName, "top")
output := res.Stdout()
head := []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}
for _, h := range head {
@@ -98,21 +93,21 @@ func TestLocalComposeUp(t *testing.T) {
StdoutContains(`"Name":"compose-e2e-demo-web-1","Command":"/dispatcher","Project":"compose-e2e-demo","Service":"web","State":"running","Health":"healthy"`),
5*time.Second, 1*time.Second)
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
res := c.RunDockerComposeCmd("-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `NAME COMMAND SERVICE STATUS PORTS`})
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-web-1 "/dispatcher" web running (healthy) 0.0.0.0:90->80/tcp, :::90->80/tcp`})
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-db-1 "docker-entrypoint.s…" db running 5432/tcp`})
})
t.Run("images", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "images")
res := c.RunDockerComposeCmd("-p", projectName, "images")
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-db-1 gtardif/sentences-db latest`})
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-web-1 gtardif/sentences-web latest`})
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-words-1 gtardif/sentences-api latest`})
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
t.Run("check containers after down", func(t *testing.T) {
@@ -137,7 +132,6 @@ func TestComposePull(t *testing.T) {
}
func TestDownComposefileInParentFolder(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
tmpFolder, err := ioutil.TempDir("fixtures/simple-composefile", "test-tmp")
@@ -145,10 +139,10 @@ func TestDownComposefileInParentFolder(t *testing.T) {
defer os.Remove(tmpFolder) // nolint: errcheck
projectName := filepath.Base(tmpFolder)
res := c.RunDockerCmd("compose", "--project-directory", tmpFolder, "up", "-d")
res := c.RunDockerComposeCmd("--project-directory", tmpFolder, "up", "-d")
res.Assert(t, icmd.Expected{Err: "Started", ExitCode: 0})
res = c.RunDockerCmd("compose", "-p", projectName, "down")
res = c.RunDockerComposeCmd("-p", projectName, "down")
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
}
@@ -181,11 +175,11 @@ func TestRm(t *testing.T) {
const projectName = "compose-e2e-rm"
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
})
t.Run("rm -sf", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
})
@@ -195,7 +189,7 @@ func TestRm(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
c.RunDockerCmd("compose", "-p", projectName, "down")
c.RunDockerComposeCmd("-p", projectName, "down")
})
}
@@ -205,7 +199,7 @@ func TestCompatibility(t *testing.T) {
const projectName = "compose-e2e-compatibility"
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("check container names", func(t *testing.T) {
@@ -216,6 +210,28 @@ func TestCompatibility(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
c.RunDockerCmd("compose", "-p", projectName, "down")
c.RunDockerComposeCmd("-p", projectName, "down")
})
}
func TestConvert(t *testing.T) {
const projectName = "compose-e2e-convert"
c := NewParallelE2eCLI(t, binDir)
wd, err := os.Getwd()
assert.NilError(t, err)
t.Run("up", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
nginx:
build:
context: %s
dockerfile: Dockerfile
networks:
default: null
networks:
default:
name: compose-e2e-convert_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
})
}

View File

@@ -31,7 +31,7 @@ func TestCopy(t *testing.T) {
const projectName = "copy_e2e"
t.Cleanup(func() {
c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "down")
c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "down")
os.Remove("./fixtures/cp-test/from-default.txt") //nolint:errcheck
os.Remove("./fixtures/cp-test/from-indexed.txt") //nolint:errcheck
@@ -39,16 +39,16 @@ func TestCopy(t *testing.T) {
})
t.Run("start service", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "up", "--scale", "nginx=5", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "up", "--scale", "nginx=5", "-d")
})
t.Run("make sure service is running", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
res := c.RunDockerComposeCmd("-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `nginx running`})
})
t.Run("copy to container copies the file to the first container by default", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/default.txt")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/default.txt")
res.Assert(t, icmd.Expected{ExitCode: 0})
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/default.txt").Stdout()
@@ -59,7 +59,7 @@ func TestCopy(t *testing.T) {
})
t.Run("copy to container with a given index copies the file to the given container", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/indexed.txt")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/indexed.txt")
res.Assert(t, icmd.Expected{ExitCode: 0})
output := c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/indexed.txt").Stdout()
@@ -70,7 +70,7 @@ func TestCopy(t *testing.T) {
})
t.Run("copy to container with the all flag copies the file to all containers", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--all", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/all.txt")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--all", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/all.txt")
res.Assert(t, icmd.Expected{ExitCode: 0})
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/all.txt").Stdout()
@@ -84,7 +84,7 @@ func TestCopy(t *testing.T) {
})
t.Run("copy from a container copies the file to the host from the first container by default", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/default.txt", "./fixtures/cp-test/from-default.txt")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/default.txt", "./fixtures/cp-test/from-default.txt")
res.Assert(t, icmd.Expected{ExitCode: 0})
data, err := os.ReadFile("./fixtures/cp-test/from-default.txt")
@@ -93,7 +93,7 @@ func TestCopy(t *testing.T) {
})
t.Run("copy from a container with a given index copies the file to host", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "nginx:/tmp/indexed.txt", "./fixtures/cp-test/from-indexed.txt")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "nginx:/tmp/indexed.txt", "./fixtures/cp-test/from-indexed.txt")
res.Assert(t, icmd.Expected{ExitCode: 0})
data, err := os.ReadFile("./fixtures/cp-test/from-indexed.txt")
@@ -102,13 +102,13 @@ func TestCopy(t *testing.T) {
})
t.Run("copy to and from a container also work with folder", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-folder", "nginx:/tmp")
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-folder", "nginx:/tmp")
res.Assert(t, icmd.Expected{ExitCode: 0})
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/cp-folder/cp-me.txt").Stdout()
assert.Assert(t, strings.Contains(output, `hello world from folder`), output)
res = c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/cp-folder", "./fixtures/cp-test/cp-folder2")
res = c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/cp-folder", "./fixtures/cp-test/cp-folder2")
res.Assert(t, icmd.Expected{ExitCode: 0})
data, err := os.ReadFile("./fixtures/cp-test/cp-folder2/cp-me.txt")

View File

@@ -0,0 +1,21 @@
//go:build !standalone
/*
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 e2e
const composeStandaloneMode = false

View File

@@ -0,0 +1,21 @@
//go:build standalone
/*
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 e2e
const composeStandaloneMode = true

View File

@@ -0,0 +1,19 @@
# 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.
ARG IMAGE=666
ARG TAG=666
FROM ${IMAGE}:${TAG}
RUN echo "SUCCESS"

View File

@@ -0,0 +1,9 @@
services:
multiargs:
build:
context: .
args:
IMAGE: alpine
TAG: latest
labels:
- RESULT=SUCCESS

View File

@@ -1,3 +1,5 @@
services:
nginx:
build: nginx-build
build:
context: nginx-build
dockerfile: Dockerfile

View File

@@ -0,0 +1,14 @@
services:
fail:
image: alpine
command: sleep infinity
healthcheck:
test: "false"
interval: 1s
retries: 3
depends:
image: alpine
command: sleep infinity
depends_on:
fail:
condition: service_healthy

View File

@@ -29,6 +29,7 @@ import (
"testing"
"time"
"github.com/docker/compose/v2/cmd/compose"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@@ -39,11 +40,19 @@ import (
var (
// DockerExecutableName is the OS dependent Docker CLI binary name
DockerExecutableName = "docker"
// DockerComposeExecutableName is the OS dependent Docker CLI binary name
DockerComposeExecutableName = "docker-" + compose.PluginName
// DockerScanExecutableName is the OS dependent Docker CLI binary name
DockerScanExecutableName = "docker-scan"
)
func init() {
if runtime.GOOS == "windows" {
DockerExecutableName = DockerExecutableName + ".exe"
DockerComposeExecutableName = DockerComposeExecutableName + ".exe"
DockerScanExecutableName = DockerScanExecutableName + ".exe"
}
}
@@ -78,23 +87,17 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
})
_ = os.MkdirAll(filepath.Join(d, "cli-plugins"), 0755)
composePluginFile := "docker-compose"
scanPluginFile := "docker-scan"
if runtime.GOOS == "windows" {
composePluginFile += ".exe"
scanPluginFile += ".exe"
}
composePlugin, err := findExecutable(composePluginFile, []string{"../../bin", "../../../bin"})
composePlugin, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
if os.IsNotExist(err) {
fmt.Println("WARNING: docker-compose cli-plugin not found")
}
if err == nil {
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", composePluginFile))
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerComposeExecutableName))
if err != nil {
panic(err)
}
// We don't need a functional scan plugin, but a valid plugin binary
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", scanPluginFile))
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerScanExecutableName))
if err != nil {
panic(err)
}
@@ -104,9 +107,9 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
}
func dirContents(dir string) []string {
res := []string{}
var res []string
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
res = append(res, filepath.Join(dir, path))
res = append(res, path)
return nil
})
return res
@@ -191,11 +194,29 @@ func (c *E2eCLI) RunCmd(args ...string) *icmd.Result {
// RunDockerCmd runs a docker command, expects no error and returns a result
func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
if len(args) > 0 && args[0] == compose.PluginName {
c.test.Fatal("This test called 'RunDockerCmd' for 'compose'. Please prefer 'RunDockerComposeCmd' to be able to test as a plugin and standalone")
}
res := c.RunDockerOrExitError(args...)
res.Assert(c.test, icmd.Success)
return res
}
// RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
if composeStandaloneMode {
composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
assert.NilError(c.test, err)
res := icmd.RunCmd(c.NewCmd(composeBinary, args...))
res.Assert(c.test, icmd.Success)
return res
}
args = append([]string{"compose"}, args...)
res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
res.Assert(c.test, icmd.Success)
return res
}
// StdoutContains returns a predicate on command result expecting a string in stdout
func StdoutContains(expected string) func(*icmd.Result) bool {
return func(res *icmd.Result) bool {
@@ -230,7 +251,7 @@ func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
}
//Lines split output into lines
// Lines split output into lines
func Lines(output string) []string {
return strings.Split(strings.TrimSpace(output), "\n")
}

View File

@@ -35,11 +35,11 @@ func TestIPC(t *testing.T) {
})
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("check running project", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
res := c.RunDockerComposeCmd("-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `shareable`})
})
@@ -55,7 +55,7 @@ func TestIPC(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
t.Run("remove ipc mode container", func(t *testing.T) {
_ = c.RunDockerCmd("rm", "-f", "ipc_mode_container")

View File

@@ -31,28 +31,28 @@ func TestLocalComposeLogs(t *testing.T) {
const projectName = "compose-e2e-logs"
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("logs", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--project-name", projectName, "logs")
res := c.RunDockerComposeCmd("--project-name", projectName, "logs")
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
res.Assert(t, icmd.Expected{Out: `hello`})
})
t.Run("logs ping", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--project-name", projectName, "logs", "ping")
res := c.RunDockerComposeCmd("--project-name", projectName, "logs", "ping")
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
assert.Assert(t, !strings.Contains(res.Stdout(), "hello"))
})
t.Run("logs hello", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--project-name", projectName, "logs", "hello", "ping")
res := c.RunDockerComposeCmd("--project-name", projectName, "logs", "hello", "ping")
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
res.Assert(t, icmd.Expected{Out: `hello`})
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}

View File

@@ -14,15 +14,14 @@
limitations under the License.
*/
package compose
package e2e
import (
"github.com/docker/buildx/build"
"github.com/docker/compose/v2/pkg/api"
"os"
"testing"
)
func (s *composeService) windowsBuild(opts map[string]build.Options, mode string) error {
// FIXME copy/paste or reuse code from https://github.com/docker/cli/blob/master/cli/command/image/build.go
return api.ErrNotImplemented
func TestMain(m *testing.M) {
exitCode := m.Run()
os.Exit(exitCode)
}

View File

@@ -36,7 +36,7 @@ func TestComposeMetrics(t *testing.T) {
res = c.RunDockerOrExitError("compose", "-f", "fixtures/wrong-composefile/compose.yaml", "up", "-d")
res.Assert(t, icmd.Expected{ExitCode: 15, Err: "services.simple Additional property wrongField is not allowed"})
res = c.RunDockerOrExitError("compose", "up")
res.Assert(t, icmd.Expected{ExitCode: 14, Err: "can't find a suitable configuration file in this directory or any parent: not found"})
res.Assert(t, icmd.Expected{ExitCode: 14, Err: "no configuration file provided: not found"})
res = c.RunDockerOrExitError("compose", "up", "-f", "fixtures/wrong-composefile/compose.yaml")
res.Assert(t, icmd.Expected{ExitCode: 16, Err: "unknown shorthand flag: 'f' in -f"})
res = c.RunDockerOrExitError("compose", "up", "--file", "fixtures/wrong-composefile/compose.yaml")

View File

@@ -37,11 +37,11 @@ func TestNetworks(t *testing.T) {
})
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("check running project", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
res := c.RunDockerComposeCmd("-p", projectName, "ps")
res.Assert(t, icmd.Expected{Out: `web`})
endpoint := "http://localhost:80"
@@ -54,12 +54,12 @@ func TestNetworks(t *testing.T) {
})
t.Run("port", func(t *testing.T) {
res := c.RunDockerCmd("compose", "--project-name", projectName, "port", "words", "8080")
res := c.RunDockerComposeCmd("--project-name", projectName, "port", "words", "8080")
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
t.Run("check networks after down", func(t *testing.T) {
@@ -75,21 +75,21 @@ func TestNetworkAliassesAndLinks(t *testing.T) {
const projectName = "network_alias_e2e"
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("curl alias", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://alias-of-container2/")
res := c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://alias-of-container2/")
assert.Assert(t, strings.Contains(res.Stdout(), "Welcome to nginx!"), res.Stdout())
})
t.Run("curl links", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://container/")
res := c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://container/")
assert.Assert(t, strings.Contains(res.Stdout(), "Welcome to nginx!"), res.Stdout())
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}
@@ -103,7 +103,7 @@ func TestIPAMConfig(t *testing.T) {
})
t.Run("up", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
})
t.Run("ensure service get fixed IP assigned", func(t *testing.T) {
@@ -112,6 +112,22 @@ func TestIPAMConfig(t *testing.T) {
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}
func TestNetworkModes(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
const projectName = "network_mode_service_run"
t.Run("run with service mode dependency", func(t *testing.T) {
res := c.RunDockerOrExitError("compose", "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "run", "-T", "mydb", "echo", "success")
res.Assert(t, icmd.Expected{Out: "success"})
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}

View File

@@ -36,18 +36,18 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
c.RunDockerOrExitError("scan", "--help")
t.Run("display on compose build", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-compose-build", "build")
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-compose-build", "build")
defer c.RunDockerOrExitError("rmi", "-f", "scan-msg-test-compose-build_nginx")
res.Assert(t, icmd.Expected{Err: utils.ScanSuggestMsg})
})
t.Run("do not display on compose build with quiet flag", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-quiet", "build", "--quiet")
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-quiet", "build", "--quiet")
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
res = c.RunDockerCmd("rmi", "-f", "scan-msg-test-quiet_nginx")
assert.Assert(t, !strings.Contains(res.Combined(), "No such image"))
res = c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-q", "build", "-q")
res = c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-q", "build", "-q")
defer c.RunDockerOrExitError("rmi", "-f", "scan-msg-test-q_nginx")
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
})
@@ -55,13 +55,13 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
_ = c.RunDockerOrExitError("rmi", "scan-msg-test_nginx")
t.Run("display on compose up if image is built", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
defer c.RunDockerOrExitError("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "down")
res.Assert(t, icmd.Expected{Err: utils.ScanSuggestMsg})
})
t.Run("do not display on compose up if no image built", func(t *testing.T) { // re-run the same Compose aproject
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
defer c.RunDockerOrExitError("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "down", "--rmi", "all")
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
})

View File

@@ -0,0 +1,33 @@
/*
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 e2e
import (
"testing"
"gotest.tools/v3/icmd"
)
func TestStartFail(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
const projectName = "e2e-start-fail"
res := c.RunDockerOrExitError("compose", "-f", "fixtures/start-fail/compose.yaml", "--project-name", projectName, "up", "-d")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container for service "fail" is unhealthy`})
c.RunDockerComposeCmd("--project-name", projectName, "down")
}

View File

@@ -42,56 +42,56 @@ func TestStartStop(t *testing.T) {
}
t.Run("Up a project", func(t *testing.T) {
res := c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "up", "-d")
res := c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "up", "-d")
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-simple-1 Started"), res.Combined())
res = c.RunDockerCmd("compose", "ls", "--all")
res = c.RunDockerComposeCmd("ls", "--all")
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps")
res = c.RunDockerComposeCmd("--project-name", projectName, "ps")
testify.Regexp(t, getServiceRegx("simple", "running"), res.Stdout())
testify.Regexp(t, getServiceRegx("another", "running"), res.Stdout())
})
t.Run("stop project", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop")
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop")
res := c.RunDockerCmd("compose", "ls")
res := c.RunDockerComposeCmd("ls")
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop"), res.Combined())
res = c.RunDockerCmd("compose", "ls", "--all")
res = c.RunDockerComposeCmd("ls", "--all")
testify.Regexp(t, getProjectRegx("exited"), res.Stdout())
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps")
res = c.RunDockerComposeCmd("--project-name", projectName, "ps")
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-words-1"), res.Combined())
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps", "--all")
res = c.RunDockerComposeCmd("--project-name", projectName, "ps", "--all")
testify.Regexp(t, getServiceRegx("simple", "exited"), res.Stdout())
testify.Regexp(t, getServiceRegx("another", "exited"), res.Stdout())
})
t.Run("start project", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "start")
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "start")
res := c.RunDockerCmd("compose", "ls")
res := c.RunDockerComposeCmd("ls")
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
})
t.Run("pause project", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "pause")
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "pause")
res := c.RunDockerCmd("compose", "ls", "--all")
res := c.RunDockerComposeCmd("ls", "--all")
testify.Regexp(t, getProjectRegx("paused"), res.Stdout())
})
t.Run("unpause project", func(t *testing.T) {
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "unpause")
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "unpause")
res := c.RunDockerCmd("compose", "ls")
res := c.RunDockerComposeCmd("ls")
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
})
}

View File

@@ -35,7 +35,7 @@ func TestLocalComposeVolume(t *testing.T) {
c.RunDockerOrExitError("rmi", "compose-e2e-volume_nginx")
c.RunDockerOrExitError("volume", "rm", projectName+"_staticVol")
c.RunDockerOrExitError("volume", "rm", "myvolume")
c.RunDockerCmd("compose", "--project-directory", "fixtures/volume-test", "--project-name", projectName, "up", "-d")
c.RunDockerComposeCmd("--project-directory", "fixtures/volume-test", "--project-name", projectName, "up", "-d")
})
t.Run("access bind mount data", func(t *testing.T) {
@@ -82,9 +82,9 @@ func TestLocalComposeVolume(t *testing.T) {
})
t.Run("cleanup volume project", func(t *testing.T) {
c.RunDockerCmd("compose", "--project-name", projectName, "down", "--volumes")
res := c.RunDockerCmd("volume", "ls")
assert.Assert(t, !strings.Contains(res.Stdout(), projectName+"_staticVol"))
assert.Assert(t, !strings.Contains(res.Stdout(), "myvolume"))
c.RunDockerComposeCmd("--project-name", projectName, "down", "--volumes")
ls := c.RunDockerCmd("volume", "ls").Stdout()
assert.Assert(t, !strings.Contains(ls, projectName+"_staticVol"))
assert.Assert(t, !strings.Contains(ls, "myvolume"))
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -68,6 +68,21 @@ func StartedEvent(ID string) Event {
return NewEvent(ID, Done, "Started")
}
// Waiting creates a new waiting event
func Waiting(ID string) Event {
return NewEvent(ID, Working, "Waiting")
}
// Healthy creates a new healthy event
func Healthy(ID string) Event {
return NewEvent(ID, Done, "Healthy")
}
// Exited creates a new exited event
func Exited(ID string) Event {
return NewEvent(ID, Done, "Exited")
}
// RestartingEvent creates a new Restarting in progress Event
func RestartingEvent(ID string) Event {
return NewEvent(ID, Working, "Restarting")

View File

@@ -27,7 +27,10 @@ func (p *noopWriter) Start(ctx context.Context) error {
return nil
}
func (p *noopWriter) Event(e Event) {
func (p *noopWriter) Event(Event) {
}
func (p *noopWriter) Events([]Event) {
}
func (p *noopWriter) TailMsgf(_ string, _ ...interface{}) {

View File

@@ -40,6 +40,12 @@ func (p *plainWriter) Event(e Event) {
fmt.Fprintln(p.out, e.ID, e.Text, e.StatusText)
}
func (p *plainWriter) Events(events []Event) {
for _, e := range events {
p.Event(e)
}
}
func (p *plainWriter) TailMsgf(m string, args ...interface{}) {
fmt.Fprintln(p.out, append([]interface{}{m}, args...)...)
}

View File

@@ -38,12 +38,13 @@ type ttyWriter struct {
repeated bool
numLines int
done chan bool
mtx *sync.RWMutex
mtx *sync.Mutex
tailEvents []string
}
func (w *ttyWriter) Start(ctx context.Context) error {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
@@ -94,6 +95,12 @@ func (w *ttyWriter) Event(e Event) {
}
}
func (w *ttyWriter) Events(events []Event) {
for _, e := range events {
w.Event(e)
}
}
func (w *ttyWriter) TailMsgf(msg string, args ...interface{}) {
w.mtx.Lock()
defer w.mtx.Unlock()
@@ -188,7 +195,7 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
padding = 0
}
// calculate the max length for the status text, on errors it
// is 2-3 lines long and breaks the line formating
// is 2-3 lines long and breaks the line formatting
maxStatusLen := terminalWidth - textLen - statusPadding - 15
status := event.StatusText
// in some cases (debugging under VS Code), terminalWidth is set to zero by goterm.Width() ; ensuring we don't tweak strings with negative char index

View File

@@ -78,7 +78,7 @@ func TestLineTextSingleEvent(t *testing.T) {
func TestErrorEvent(t *testing.T) {
w := &ttyWriter{
events: map[string]Event{},
mtx: &sync.RWMutex{},
mtx: &sync.Mutex{},
}
e := Event{
ID: "id",

View File

@@ -31,6 +31,7 @@ type Writer interface {
Start(context.Context) error
Stop()
Event(Event)
Events([]Event)
TailMsgf(string, ...interface{})
}
@@ -105,7 +106,7 @@ func NewWriter(out console.File) (Writer, error) {
events: map[string]Event{},
repeated: false,
done: make(chan bool),
mtx: &sync.RWMutex{},
mtx: &sync.Mutex{},
}, nil
}

View File

@@ -16,6 +16,11 @@
package utils
import (
"strconv"
"strings"
)
// StringContains check if an array contains a specific value
func StringContains(array []string, needle string) bool {
for _, val := range array {
@@ -25,3 +30,9 @@ func StringContains(array []string, needle string) bool {
}
return false
}
// StringToBool converts a string to a boolean ignoring errors
func StringToBool(s string) bool {
b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s)))
return b
}