Compare commits

...

94 Commits

Author SHA1 Message Date
Guillaume Lours
6756732fe4 Merge pull request #9512 from ulyssessouza/import-dotenv-to-osenv
Import dotenv file to os environment
2022-05-30 18:00:45 +02:00
Ulysses Souza
67c13cf821 Import dotenv file to os environment
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-05-30 17:36:41 +02:00
Guillaume Lours
dbafb02377 Merge pull request #9499 from glours/add-envs-e2e-tests
add e2e tests to verify env variables priority
2022-05-30 17:19:46 +02:00
Guillaume Lours
a1b3f95709 add e2e tests to verify env variables priority
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-30 17:11:49 +02:00
Guillaume Lours
5b6b674da9 Merge pull request #9495 from maxcleme/chore/bump_compose_go
bump compose-go to 1.2.6
2022-05-23 15:01:05 +02:00
Maxime CLEMENT
31d9490a0b bump compose-go to 1.2.6
Signed-off-by: Maxime CLEMENT <maxime.clement@docker.com>
2022-05-23 14:51:09 +02:00
Guillaume Lours
6b71073ae2 Merge pull request #9453 from glours/go-18
update golang version to 1.18
2022-05-23 10:41:03 +02:00
Guillaume Lours
e806acce88 Merge pull request #9481 from glours/add-tags-to-build
add tags property to build section
2022-05-23 10:23:40 +02:00
Guillaume Lours
71600a52bf update golang version to 1.18
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-20 22:13:55 +02:00
Guillaume Lours
285a9c94f7 Merge pull request #9476 from maxcleme/9469-fix-flickering-prompt
fix: prevent flickering prompt when pulling same image from N services
2022-05-20 22:09:02 +02:00
Guillaume Lours
22194f6ef7 Merge pull request #9493 from ulyssessouza/fix-local-e2e-compose-standalone
Fix local run of `make e2e-compose-standalone`
2022-05-20 21:41:42 +02:00
Ulysses Souza
e51fd0a844 Fix local run of make e2e-compose-standalone
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-05-20 21:31:16 +02:00
Ulysses Souza
b961d49859 Merge pull request #9033 from ulyssessouza/add-e2e-ddev
Add ddev's e2e test
2022-05-20 20:02:58 +02:00
Guillaume Lours
9cae9eb0fe Merge pull request #9488 from ndeloof/attach_profiles
attach _only_ to services declared by project applying profiles
2022-05-20 14:38:08 +02:00
Nicolas De Loof
8d03e29994 attach _only_ to services declared by project applying profiles
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-05-20 10:36:06 +02:00
Guillaume Lours
7ee7becd01 fix TestLocalComposeUp which fail locally
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-20 07:36:34 +02:00
Randy Fay
97d46a14ef Fix problems with ddev e2e test and minor cleanup, add tmate (#27)
* Add tmate for debugging
* Use -parallel=1 for standaone tests

Signed-off-by: Randy Fay <randy@randyfay.com>
2022-05-19 14:14:03 +02:00
Ulysses Souza
a5a1c5f2f1 Add ddev's e2e test
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-05-19 14:14:03 +02:00
Guillaume Lours
a2770b66ff add tags property to build section
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-18 14:43:54 +02:00
Maxime CLEMENT
48b150beff fix: prevent flickering prompt when pulling same image from N services
Signed-off-by: Maxime CLEMENT <maxime.clement@docker.com>
2022-05-18 08:58:06 +02:00
Guillaume Lours
7e3564b7ad Merge pull request #9475 from ndeloof/bump-compose-go
bump compose-go to 1.2.5
2022-05-17 17:21:00 +02:00
Nicolas De Loof
65b827d08f bump compose-go to 1.2.5
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-05-17 17:11:32 +02:00
Jan Vitturi
84f2168f80 Fix extra space printed with --no-log-prefix option
Signed-off-by: Jan Vitturi <vitturi.jan@gmail.com>
2022-05-17 15:20:21 +02:00
Guillaume Lours
a603e27117 cp command from service to host: use the first container found to copy source on the host
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-10 10:50:40 +02:00
Guillaume Lours
6d9d75406c update usage of the index flag of the cp command
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-10 10:50:40 +02:00
Guillaume Lours
a964d5587b align cp command index management with exec command
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-10 10:50:40 +02:00
Guillaume Lours
a983cf551d cp command: copy to all containers of a service as default behaviour
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-05-10 10:50:40 +02:00
Guillaume Lours
2f47e4582c Merge pull request #9440 from ndeloof/down_error
compose down exit=0 if nothing to remove
2022-05-06 10:42:26 +02:00
Nicolas De Loof
78b06764a1 compose down exit=0 if nothing to remove
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-05-04 13:51:21 +02:00
Guillaume Lours
4cebef1bf1 Merge pull request #9423 from quite/clarify-workdir
Clarify what default work dir is when multiple compose files
2022-05-03 14:02:03 +02:00
Daniel Lublin
d89c143c39 Clarify what default work dir is when multiple compose files
Signed-off-by: Daniel Lublin <daniel@lublin.se>
2022-05-03 13:14:34 +02:00
Guillaume Lours
028cb4dd89 Merge pull request #9158 from Jille/compose-down-noargs
down: Reject all arguments
2022-05-02 15:37:01 +02:00
Jille Timmermans
147c2d8fae down: Reject all arguments
The down command silently ignored all arguments, which might cause
confusion and/or outages if someone expects `docker-compose down
$service` to be the opposite of `docker-compose up $service`, rather
than turning down everything.

Signed-off-by: Jille Timmermans <jille@quis.cx>
2022-05-02 15:28:30 +02:00
Ulysses Souza
69e21d89f0 Fix relative paths on envfile label
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-04-30 10:14:39 +02:00
Eric Fan
41b3967cb5 Fix cannot setup IPAM gateway
Signed-off-by: Eric Fan <ericfan@qnap.com>
2022-04-24 17:30:27 +02:00
Nicolas De Loof
00fd1c1530 inspect image ID after pull to se com.docker.compose.image
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-21 22:42:29 +02:00
Guillaume Lours
14ca112862 Merge pull request #9392 from snehakpersistent/ppc-support
add support for ppc64le for docker compose
2022-04-14 17:43:44 +02:00
Sneha Kanekar1
e0286360a8 add support for ppc64le
Signed-off-by: Sneha Kanekar1 <sneha.kanekar1@ibm.com>
2022-04-14 17:25:01 +05:30
MaxPeal
5d809a2e89 create also a checksums.txt file, add --binary
create also a checksums.txt file
and switch shasum to --binary,
to Fix problems with the verification on different OS systems
fixes https://github.com/docker/compose/issues/9388

Signed-off-by: MaxPeal <30347730+MaxPeal@users.noreply.github.com>
2022-04-14 09:06:35 +02:00
Nicolas De Loof
0dffd5ba9f add support for build.secrets
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-13 22:45:53 +02:00
Nicolas De Loof
e01687430e when using bind API, use compose-go to (re)build volume string
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-13 22:45:53 +02:00
Guillaume Lours
a32fdff979 Merge pull request #9385 from ndeloof/lowercase_project_name
project name MUST be lowercase
2022-04-13 10:08:53 +02:00
Nicolas De Loof
9668f600d1 project name MUST be lowercase
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-13 09:49:01 +02:00
Nicolas De Loof
672e2367fb don't ignore error
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-12 16:00:36 +02:00
Nicolas De Loof
625a48dcf7 pull to respect pull_policy
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-12 07:59:58 +02:00
Guillaume Lours
03aadcc85a Merge pull request #9368 from ndeloof/links_dependencies
include services declared by `links` as implicit dependencies
2022-04-11 11:59:25 +02:00
Nicolas De Loof
500555b834 linter
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-08 11:41:46 +02:00
Nicolas De Loof
e766352d81 include services declared by links as implicit dependencies
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-08 10:32:36 +02:00
Nicolas De Loof
db698562a9 use project we just created to start services
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-07 13:38:30 +02:00
Guillaume Lours
7fea9f7e8b use project instead of DownOptions.project to list service images in pkg.compose.down
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-04-05 08:24:25 +02:00
Eric Freese
d871cb98e5 Fix search/replace typo in --no-TTY documentation
Commit abbba74b27 looks to have
accidentally replaced `pseudo-tty` with `pseudo-noTty` in several
places. In other places, it looks like it replaced `pseudo-tty` with
`pseudo-TTY`, so I used the uppercased version in these places as well.

For example, running version 2.3.3, I get this output:

```
% docker-compose run --help

...

Options:
  ...
  -T, --no-TTY                Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
  ...
```

Signed-off-by: Eric Freese <ericdfreese@gmail.com>
2022-04-04 19:06:03 +02:00
Guillaume Lours
8d815ff587 Merge pull request #9348 from ndeloof/attach_tty
get Tty from container to know adequate way to attach to
2022-04-04 14:44:07 +02:00
Nicolas De Loof
c9fbb9ba9c inspect container before attachment to use the adequate logic regarding TTY
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-04-04 14:39:38 +02:00
Guillaume Lours
f2d9acd3d4 use ssh config when building from compose up
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-04-04 10:16:28 +02:00
Guillaume Lours
fcff36fc8a now we use directly the Docker CLI to run autoremove flag should be pass as is
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-04-02 08:45:51 +02:00
Guillaume Lours
804ef4af5b Merge pull request #9309 from thaJeztah/vendor_containerd_1.6.2
vendor: github.com/containerd/containerd v1.6.2
2022-03-31 19:02:47 +02:00
Guillaume Lours
0309a735a9 Merge pull request #9336 from ulyssessouza/fix-empty-down
Fix down command without any resource to delete
2022-03-31 18:34:56 +02:00
Ulysses Souza
56e48f8360 Fix down command without any resource to delete
A command down without any resource to delete was
trying to remove a default network that doesn't exist

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-31 18:30:29 +02:00
Ulysses Souza
c7473c68ae Merge pull request #9335 from ulyssessouza/remove-deadcode
Remove dead warning code
2022-03-31 17:04:29 +02:00
Ulysses Souza
5e63f12ae8 Merge pull request #9332 from ulyssessouza/fix-project-name
Takes COMPOSE_PROJECT_NAME into consideration on commands
2022-03-31 17:04:12 +02:00
Ulysses Souza
60363c36df Remove dead warning code
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-31 16:42:04 +02:00
Guillaume Lours
e700f0a5d7 Merge pull request #9334 from mschoettle/ssh-option-fix-typo
fix typo in ssh option description
2022-03-31 16:35:47 +02:00
Matthias Schoettle
6dbd6ffe11 fix typo in ssh option description
Signed-off-by: Matthias Schoettle <git@mattsch.com>
2022-03-31 10:14:13 -04:00
Ulysses Souza
eee0e8bed9 Takes COMPOSE_PROJECT_NAME into consideration on commands
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-31 14:28:38 +02:00
Guillaume Lours
934b596e00 add e2e tests to check usage of ssh build property
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-31 12:47:15 +02:00
Guillaume Lours
ff73827a6f Add support of ssh authentications defined in compose file or via cli flags
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-31 12:47:15 +02:00
Guillaume Lours
3c12b94519 Merge pull request #9331 from glours/e2e-start-stop-race
fix race condition on start-stop e2e tests running in parrallel
2022-03-31 11:47:10 +02:00
Guillaume Lours
9b02add4df fix race condition on start-stop e2e tests running in parrallel
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-31 11:16:18 +02:00
Guillaume Lours
a73a2c9240 Merge pull request #9222 from ndeloof/cache
support cache_from|to|no|pull
2022-03-28 09:52:30 +02:00
Nicolas De Loof
890b6808a2 support cache_from|to|no|pull
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-28 09:36:24 +02:00
Sebastiaan van Stijn
950cb1af0d go.mod: github.com/docker/buildx v0.8.1
Fix possible panic on handling build context scanning errors

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-28 09:09:05 +02:00
Sebastiaan van Stijn
d078e30642 go.mod: pin k8s dependencies to the same version as in buildx
Buildx uses replace rules to pin these dependencies to the version
that's used in buildkit. Newer versions of these dependencies are
incompatible with some go versions, and we do not need the newer
versions for where it's used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-28 09:09:05 +02:00
Guillaume Lours
b7fd6eb7d8 pass interactive flag to ExecOptions for the RunExec command
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-26 07:23:31 +01:00
Sebastiaan van Stijn
5ace5bdeda vendor: github.com/containerd/containerd v1.6.2
includes a fix for CVE-2022-24769 (but doesn't affect the code we use)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-24 04:13:02 +01:00
Guillaume Lours
be187bae64 use a temp directory to generate doc to be sure working tree is clean
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-18 10:42:05 +01:00
Guillaume Lours
099715fb6f update run no-TTY flag description as auto-detected by default
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-18 10:42:05 +01:00
Guillaume Lours
09e0cca9a7 update CI github actions comment to mention docs validation
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-18 10:42:05 +01:00
Guillaume Lours
bf26cbd498 contenairized documetation generation
add docs validation (using same process a BuildX project)

Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-18 10:42:05 +01:00
Guillaume Lours
35ba6f68e5 generate reference api
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2022-03-18 10:42:05 +01:00
Nicolas De Loof
c843d373de restore TTY auto-detection using dockerCli.Out.IsTerminal
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-16 16:34:26 +01:00
Nicolas De Loof
1d4b4e3c8e use docker/cli RunExec and RunStart to handle all the interactive/tty/* terminal logic
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-16 16:34:26 +01:00
Guillaume Lours
d999c230a5 Merge pull request #9281 from ndeloof/down_volume_external
don't remove external volumes/networks
2022-03-14 17:21:04 +01:00
Nicolas De Loof
e7f545907b don't remove external volumes/networks
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-14 15:24:49 +01:00
Guillaume Lours
730609b359 Merge pull request #9280 from ndeloof/kill
kill only need project name
2022-03-14 10:33:21 +01:00
Nicolas De Loof
cd8074d501 kill only need project name
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-14 10:26:36 +01:00
Nicolas De Loof
283f7a1ec5 Bump buildx to v0.8.0
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-11 14:05:55 +01:00
Ulysses Souza
6ce57ea2f4 Remove DEPRECATED text, since it's just the default
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-03-11 13:01:55 +01:00
dependabot[bot]
2cf917a330 Bump github.com/spf13/cobra from 1.3.0 to 1.4.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.3.0 to 1.4.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.3.0...v1.4.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>
2022-03-11 13:01:21 +01:00
Nicolas De loof
fbcd7ee896 Update BUILDING.md 2022-03-11 12:47:50 +01:00
Sebastiaan van Stijn
2d32d7450c ps: un-deprecate --filter, and enhance docs
Compose currently only supports a single filter options on --filter,
for which reason the --status flag was added, which is more convenient
to use.

However, the `--filter` flag is common among various docker commands, and
it's possible that additional filters get added at some point (which may
be less "commonly" used, and not warrant a dedicated flag).

This PR removes the "deprecated" mention from the flag, to keep consistency
with other commands, but adds documentation to explain how they relate to
eachother.

Also added a short example for the `--format` flag.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-03-10 20:39:19 +01:00
Guillaume Lours
dc6097d1f0 Merge pull request #9261 from ndeloof/recreate_on_image_updated
recreate container after image has been rebuilt/pulled
2022-03-10 12:11:02 +01:00
Nicolas De Loof
85a4d04096 recreate container after image has been rebuilt/pulled
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-10 11:02:48 +01:00
Guillaume Lours
4d163f35f3 Merge pull request #9256 from ndeloof/rm_empty
don't fail trying to remove container with no candidate
2022-03-09 16:36:13 +01:00
Nicolas De Loof
5e8040ea18 don't fail trying to remove container with no candidate
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2022-03-09 16:24:16 +01:00
97 changed files with 1764 additions and 1053 deletions

View File

@@ -1,3 +1,2 @@
.git/
bin/
dist/

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.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Checkout code into the Go module directory
@@ -42,6 +42,12 @@ jobs:
name: docker-compose-linux-amd64
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
- name: Upload linux-ppc64le binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-linux-ppc64le
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
- name: Upload windows-amd64 binary
uses: actions/upload-artifact@v2
with:

View File

@@ -5,6 +5,12 @@ on:
branches:
- v2
pull_request:
workflow_dispatch:
inputs:
debug_enabled:
description: 'To run with tmate enter "debug_enabled"'
required: false
default: "false"
jobs:
lint:
@@ -13,16 +19,16 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- name: Validate go-mod is up-to-date and license headers
- name: Validate go-mod, license headers and docs are up-to-date
run: make validate
- name: Run golangci-lint
@@ -40,10 +46,10 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Checkout code into the Go module directory
@@ -65,10 +71,10 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Setup docker CLI
@@ -90,7 +96,7 @@ jobs:
- name: Build for local E2E
env:
BUILD_TAGS: e2e
run: make -f builder.Makefile compose-plugin
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
- name: E2E Test in plugin mode
run: make e2e-compose
@@ -101,10 +107,10 @@ jobs:
env:
GO111MODULE: "on"
steps:
- name: Set up Go 1.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Setup docker CLI
@@ -123,7 +129,17 @@ jobs:
- name: Build for local E2E
env:
BUILD_TAGS: e2e
run: make -f builder.Makefile compose-plugin
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
github-token: ${{ secrets.GITHUB_TOKEN }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
- name: E2E Test in standalone mode
run: make e2e-compose-standalone
run: |
rm -f /usr/local/bin/docker-compose
cp bin/docker-compose /usr/local/bin
make e2e-compose-standalone

View File

@@ -11,10 +11,10 @@ jobs:
upload-release:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.17
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.18.2
id: go
- name: Setup docker CLI
@@ -36,7 +36,7 @@ jobs:
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
run: cd bin; for f in *; do shasum --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
- name: License
run: cp packaging/* bin/

View File

@@ -8,7 +8,7 @@
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
* make
* Linux:
* [Docker 19.03 or later](https://docs.docker.com/engine/install/)
* [Docker 20.10 or later](https://docs.docker.com/engine/install/)
* make
### Building the CLI

View File

@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.17-alpine
ARG GO_VERSION=1.18.2-alpine
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
ARG PROTOC_GEN_GO_VERSION=v1.4.3
@@ -88,7 +88,7 @@ RUN --mount=target=. \
make -f builder.Makefile test
FROM base AS check-license-headers
RUN go get -u github.com/kunalkushwaha/ltag
RUN go install github.com/kunalkushwaha/ltag@latest
RUN --mount=target=. \
make -f builder.Makefile check-license-headers

View File

@@ -43,12 +43,18 @@ compose-plugin: ## Compile the compose cli-plugin
.PHONY: e2e-compose
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
docker compose version
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
docker-compose version
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
.PHONY: mocks
mocks:
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
.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
@@ -78,6 +84,23 @@ lint: ## run linter(s)
--build-arg GIT_TAG=$(GIT_TAG) \
--target lint
.PHONY: docs
docs: ## generate documentation
$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
docker build . \
--output type=local,dest=$($@_TMP_OUT) \
-f ./docs/docs.Dockerfile \
--target update
rm -rf ./docs/internal
cp -R "$($@_TMP_OUT)"/out/* ./docs/
rm -rf "$($@_TMP_OUT)"/*
.PHONY: validate-docs
validate-docs: ## validate the doc does not change
@docker build . \
-f ./docs/docs.Dockerfile \
--target validate
.PHONY: check-dependencies
check-dependencies: ## check dependency updates
go list -u -m -f '{{if not .Indirect}}{{if .Update}}{{.}}{{end}}{{end}}' all
@@ -94,7 +117,7 @@ go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and g
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
@docker build . --target check-go-mod
validate: validate-go-mod validate-headers ## Validate sources
validate: validate-go-mod validate-headers validate-docs ## Validate sources
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose

View File

@@ -47,6 +47,7 @@ compose-plugin:
.PHONY: cross
cross:
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
GOOS=linux GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./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
@@ -70,7 +71,3 @@ check-license-headers:
.PHONY: check-go-mod
check-go-mod:
./scripts/validate/check-go-mod
.PHONY: yamldocs
yamldocs:
go run docs/yaml/main/generate.go

View File

@@ -23,6 +23,7 @@ import (
"strings"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types"
buildx "github.com/docker/buildx/util/progress"
"github.com/docker/compose/v2/pkg/utils"
@@ -40,6 +41,28 @@ type buildOptions struct {
args []string
noCache bool
memory string
ssh string
}
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
var SSHKeys []types.SSHKey
var err error
if opts.ssh != "" {
SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
if err != nil {
return api.BuildOptions{}, err
}
}
return api.BuildOptions{
Pull: opts.pull,
Progress: opts.progress,
Args: types.NewMappingWithEquals(opts.args),
NoCache: opts.noCache,
Quiet: opts.quiet,
Services: services,
SSHs: SSHKeys,
}, nil
}
var printerModes = []string{
@@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
opts.ssh = "default"
}
return runBuild(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
@@ -82,6 +108,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
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().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
@@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
return err
}
return backend.Build(ctx, project, api.BuildOptions{
Pull: opts.pull,
Progress: opts.progress,
Args: types.NewMappingWithEquals(opts.args),
NoCache: opts.noCache,
Quiet: opts.quiet,
Services: services,
})
apiBuildOptions, err := opts.toAPIBuildOptions(services)
if err != nil {
return err
}
return backend.Build(ctx, project, apiBuildOptions)
}

View File

@@ -27,8 +27,10 @@ import (
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
composegoutils "github.com/compose-spec/compose-go/utils"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -88,9 +90,6 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
})
}
// Warning is a global warning to be displayed to user on command failure
var Warning string
type projectOptions struct {
ProjectName string
Profiles []string
@@ -131,8 +130,8 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose file)")
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)")
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
_ = f.MarkHidden("workdir")
}
@@ -142,6 +141,11 @@ func (o *projectOptions) toProjectName() (string, error) {
return o.ProjectName, nil
}
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
if envProjectName != "" {
return envProjectName, nil
}
project, err := o.toProject(nil)
if err != nil {
return "", err
@@ -166,7 +170,10 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
ef := o.EnvFile
if ef != "" && !filepath.IsAbs(ef) {
ef = filepath.Join(project.WorkingDir, o.EnvFile)
ef, err = filepath.Abs(ef)
if err != nil {
return nil, err
}
}
for i, s := range project.Services {
s.CustomLabels = map[string]string{
@@ -207,9 +214,9 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
return cli.NewProjectOptions(o.ConfigPaths,
append(po,
cli.WithWorkingDirectory(o.ProjectDir),
cli.WithOsEnv,
cli.WithEnvFile(o.EnvFile),
cli.WithDotEnv,
cli.WithOsEnv,
cli.WithConfigFileEnv,
cli.WithDefaultConfigPath,
cli.WithName(o.ProjectName))...)
@@ -224,7 +231,7 @@ func RunningAsStandalone() bool {
}
// RootCommand returns the compose command with its child commands
func RootCommand(backend api.Service) *cobra.Command {
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := projectOptions{}
var (
ansi string
@@ -251,6 +258,10 @@ func RootCommand(backend api.Service) *cobra.Command {
}
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
err := setEnvWithDotEnv(&opts)
if err != nil {
return err
}
parent := cmd.Root()
if parent != nil {
parentPrerun := parent.PersistentPreRunE
@@ -300,9 +311,9 @@ func RootCommand(backend api.Service) *cobra.Command {
logsCommand(&opts, backend),
convertCommand(&opts, backend),
killCommand(&opts, backend),
runCommand(&opts, backend),
runCommand(&opts, dockerCli, backend),
removeCommand(&opts, backend),
execCommand(&opts, backend),
execCommand(&opts, dockerCli, backend),
pauseCommand(&opts, backend),
unpauseCommand(&opts, backend),
topCommand(&opts, backend),
@@ -327,3 +338,27 @@ func RootCommand(backend api.Service) *cobra.Command {
command.Flags().MarkHidden("verbose") //nolint:errcheck
return command
}
func setEnvWithDotEnv(prjOpts *projectOptions) error {
options, err := prjOpts.toProjectOptions()
if err != nil {
return compose.WrapComposeError(err)
}
workingDir, err := options.GetWorkingDir()
if err != nil {
return err
}
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFile)
if err != nil {
return err
}
for k, v := range envFromFile {
if _, ok := os.LookupEnv(k); !ok {
if err = os.Setenv(k, v); err != nil {
return err
}
}
}
return nil
}

View File

@@ -55,7 +55,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.source = args[0]
opts.destination = args[1]
return runCopy(ctx, backend, opts)
@@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
flags := copyCmd.Flags()
flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
flags.MarkHidden("all") //nolint:errcheck
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")

View File

@@ -59,6 +59,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runDown(ctx, backend, opts)
}),
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion(),
}
flags := downCmd.Flags()

View File

@@ -21,6 +21,7 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/spf13/cobra"
@@ -34,14 +35,15 @@ type execOpts struct {
environment []string
workingDir string
noTty bool
user string
detach bool
index int
privileged bool
noTty bool
user string
detach bool
index int
privileged bool
interactive bool
}
func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := execOpts{
composeOptions: &composeOptions{
projectOptions: p,
@@ -67,12 +69,12 @@ 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", false, "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "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().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY. DEPRECATED")
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
runCmd.Flags().SetInterspersed(false)
@@ -102,6 +104,7 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
Index: opts.index,
Detach: opts.detach,
WorkingDir: opts.workingDir,
Interactive: opts.interactive,
}
exitCode, err := backend.Exec(ctx, projectName, execOpts)

View File

@@ -19,25 +19,44 @@ package compose
import (
"context"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
)
type killOptions struct {
*projectOptions
signal string
}
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
var opts api.KillOptions
opts := killOptions{
projectOptions: p,
}
cmd := &cobra.Command{
Use: "kill [options] [SERVICE...]",
Short: "Force stop service containers.",
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
return backend.Kill(ctx, project, opts)
RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
}
flags := cmd.Flags()
flags.StringVarP(&opts.Signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
return cmd
}
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
return backend.Kill(ctx, projectName, api.KillOptions{
Services: services,
Signal: opts.signal,
})
}

View File

@@ -81,12 +81,11 @@ 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.Filter, "filter", "", "Filter services by a property (supported filters: status).")
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)")
flags.Lookup("filter").Hidden = true
return psCmd
}

View File

@@ -24,6 +24,7 @@ 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/docker/cli/cli/command"
"github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -62,6 +63,9 @@ func (opts runOptions) apply(project *types.Project) error {
if err != nil {
return err
}
target.Tty = !opts.noTty
target.StdinOpen = opts.interactive
if !opts.servicePorts {
target.Ports = []types.ServicePortConfig{}
}
@@ -103,7 +107,7 @@ func (opts runOptions) apply(project *types.Project) error {
return nil
}
func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := runOptions{
composeOptions: &composeOptions{
projectOptions: p,
@@ -146,7 +150,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", false, "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
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")
@@ -207,6 +211,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
Detach: opts.Detach,
AutoRemove: opts.Remove,
Tty: !opts.noTty,
Interactive: opts.interactive,
WorkingDir: opts.workdir,
User: opts.user,
Environment: opts.environment,

View File

@@ -185,6 +185,9 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
if upOptions.attachDependencies {
attachTo = project.ServiceNames()
}
if len(attachTo) == 0 {
attachTo = project.ServiceNames()
}
create := api.CreateOptions{
Services: services,
@@ -204,6 +207,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
return backend.Up(ctx, project, api.UpOptions{
Create: create,
Start: api.StartOptions{
Project: project,
Attach: consumer,
AttachTo: attachTo,
ExitCodeFrom: upOptions.exitCodeFrom,

View File

@@ -79,7 +79,7 @@ func (l *logConsumer) Log(container, service, message string) {
}
p := l.getPresenter(container)
for _, line := range strings.Split(message, "\n") {
fmt.Fprintf(l.writer, "%s %s\n", p.prefix, line) // nolint:errcheck
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
}
}
@@ -118,5 +118,5 @@ type presenter struct {
}
func (p *presenter) setPrefix(width int) {
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s |", p.name))
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
}

View File

@@ -32,15 +32,10 @@ import (
"github.com/docker/compose/v2/pkg/compose"
)
func init() {
commands.Warning = "The new 'docker compose' command is currently experimental. " +
"To provide feedback or request new features please open issues at https://github.com/docker/compose"
}
func pluginMain() {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
lazyInit := api.NewServiceProxy()
cmd := commands.RootCommand(lazyInit)
cmd := commands.RootCommand(dockerCli, lazyInit)
originalPreRun := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
@@ -68,7 +63,7 @@ func pluginMain() {
}
func main() {
if commands.RunningAsStandalone() {
if plugin.RunningStandalone() {
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
}
pluginMain()

57
docs/docs.Dockerfile Normal file
View File

@@ -0,0 +1,57 @@
# syntax=docker/dockerfile:1.3-labs
# 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 GO_VERSION=1.18.2
ARG FORMATS=md,yaml
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
WORKDIR /src
RUN --mount=target=. \
--mount=target=/root/.cache,type=cache \
go build -o /out/docsgen ./docs/yaml/main/generate.go
FROM --platform=${BUILDPLATFORM} alpine AS gen
RUN apk add --no-cache rsync git
WORKDIR /src
COPY --from=docsgen /out/docsgen /usr/bin
ARG FORMATS
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
docsgen --formats "$FORMATS" --source "docs/reference"
mkdir /out
cp -r docs/reference /out
EOT
FROM scratch AS update
COPY --from=gen /out /out
FROM gen AS validate
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
git add -A
rm -rf docs/reference/*
cp -rf /out/* ./docs/
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
git status --porcelain -- docs/reference
exit 1
fi
EOT

View File

@@ -44,7 +44,7 @@ Docker Compose
| `-f`, `--file` | `stringArray` | | Compose configuration files |
| `--profile` | `stringArray` | | Specify a profile to enable |
| `--project-directory` | `string` | | Specify an alternate working directory
(default: the path of the Compose file) |
(default: the path of the, first specified, Compose file) |
| `-p`, `--project-name` | `string` | | Project name |

View File

@@ -12,6 +12,7 @@ Build or rebuild services
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
| `--pull` | | | Always attempt to pull a newer version of the image. |
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
<!---MARKER_GEN_END-->

View File

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

View File

@@ -8,10 +8,11 @@ List containers
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json] |
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
| [`--format`](#format) | `string` | `pretty` | Format the output. Values: [pretty \| json] |
| `-q`, `--quiet` | | | Only display IDs |
| `--services` | | | Display services |
| `--status` | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
<!---MARKER_GEN_END-->
@@ -19,10 +20,98 @@ List containers
## Description
Lists containers for a Compose project, with current status and exposed ports.
By default, both running and stopped containers are shown:
```console
$ docker compose ps
NAME SERVICE STATUS PORTS
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
example_bar_1 bar exited (1)
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
```
## Examples
### <a name="format"></a> Format the output (--format)
By default, the `docker compose ps` command uses a table ("pretty") format to
show the containers. The `--format` flag allows you to specify alternative
presentations for the output. Currently supported options are `pretty` (default),
and `json`, which outputs information about the containers as a JSON array:
```console
$ docker compose ps --format json
[{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
```
The JSON output allows you to use the information in other tools for further
processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/){:target="_blank" rel="noopener" class="_"}
to pretty-print the JSON:
```console
$ docker compose ps --format json | jq .
[
{
"ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
"Name": "example-bar-1",
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
"Project": "example",
"Service": "bar",
"State": "exited",
"Health": "",
"ExitCode": 0,
"Publishers": null
},
{
"ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
"Name": "example-foo-1",
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
"Project": "example",
"Service": "foo",
"State": "running",
"Health": "",
"ExitCode": 0,
"Publishers": [
{
"URL": "0.0.0.0",
"TargetPort": 80,
"PublishedPort": 8080,
"Protocol": "tcp"
}
]
}
]
```
### <a name="status"></a> Filter containers by status (--status)
Use the `--status` flag to filter the list of containers by status. For example,
to show only containers that are running, or only containers that have exited:
```console
$ docker compose ps --status=running
NAME COMMAND SERVICE STATUS PORTS
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
$ docker compose ps --status=exited
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
```
### <a name="filter"></a> Filter containers by status (--filter)
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
flag. The example below is the equivalent to the example from the previous section,
this time using the `--filter` flag:
```console
$ docker compose ps --filter status=running
NAME COMMAND SERVICE STATUS PORTS
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
$ docker compose ps --filter status=running
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
```
The `docker compose ps` command currently only supports the `--filter status=<status>`
option, but additional filter options may be added in future.

View File

@@ -13,7 +13,7 @@ Run a one-off command on a service.
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
| `-l`, `--label` | `stringArray` | | Add or override a label |
| `--name` | `string` | | Assign a name to the container |
| `-T`, `--no-TTY` | | | Disable pseudo-noTty allocation. By default docker compose run allocates a TTY |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
| `--no-deps` | | | Don't start linked services. |
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
| `--quiet-pull` | | | Pull without printing progress information. |

View File

@@ -222,7 +222,7 @@ options:
value_type: string
description: |-
Specify an alternate working directory
(default: the path of the Compose file)
(default: the path of the, first specified, Compose file)
deprecated: false
hidden: false
experimental: false
@@ -265,7 +265,7 @@ options:
description: |-
DEPRECATED! USE --project-directory INSTEAD.
Specify an alternate working directory
(default: the path of the Compose file)
(default: the path of the, first specified, Compose file)
deprecated: false
hidden: true
experimental: false

View File

@@ -117,6 +117,16 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: ssh
value_type: string
description: |
Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
experimental: false
experimentalcli: false

View File

@@ -10,8 +10,8 @@ options:
value_type: bool
default_value: "false"
description: Copy to all the containers of the service.
deprecated: false
hidden: false
deprecated: true
hidden: true
experimental: false
experimentalcli: false
kubernetes: false
@@ -40,9 +40,9 @@ options:
swarm: false
- option: index
value_type: int
default_value: "1"
default_value: "0"
description: |
Index of the container if there are multiple instances of a service [default: 1].
Index of the container if there are multiple instances of a service .
deprecated: false
hidden: false
experimental: false

View File

@@ -46,7 +46,7 @@ options:
shorthand: i
value_type: bool
default_value: "true"
description: Keep STDIN open even if not attached. DEPRECATED
description: Keep STDIN open even if not attached.
deprecated: false
hidden: true
experimental: false
@@ -56,7 +56,7 @@ options:
- option: no-TTY
shorthand: T
value_type: bool
default_value: "false"
default_value: "true"
description: |
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
deprecated: false
@@ -79,7 +79,7 @@ options:
shorthand: t
value_type: bool
default_value: "true"
description: Allocate a pseudo-TTY. DEPRECATED
description: Allocate a pseudo-TTY.
deprecated: false
hidden: true
experimental: false

View File

@@ -2,12 +2,13 @@ command: docker compose ps
short: List containers
long: |-
Lists containers for a Compose project, with current status and exposed ports.
By default, both running and stopped containers are shown:
```console
$ docker compose ps
NAME SERVICE STATUS PORTS
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
example_bar_1 bar exited (1)
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
```
usage: docker compose ps [SERVICE...]
pname: docker compose
@@ -27,9 +28,10 @@ options:
swarm: false
- option: filter
value_type: string
description: Filter services by a property. Deprecated, use --status instead
description: 'Filter services by a property (supported filters: status).'
details_url: '#filter'
deprecated: false
hidden: true
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
@@ -38,6 +40,7 @@ options:
value_type: string
default_value: pretty
description: 'Format the output. Values: [pretty | json]'
details_url: '#format'
deprecated: false
hidden: false
experimental: false
@@ -70,12 +73,98 @@ options:
default_value: '[]'
description: |
Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
details_url: '#status'
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
examples: |-
### Format the output (--format) {#format}
By default, the `docker compose ps` command uses a table ("pretty") format to
show the containers. The `--format` flag allows you to specify alternative
presentations for the output. Currently supported options are `pretty` (default),
and `json`, which outputs information about the containers as a JSON array:
```console
$ docker compose ps --format json
[{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
```
The JSON output allows you to use the information in other tools for further
processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/){:target="_blank" rel="noopener" class="_"}
to pretty-print the JSON:
```console
$ docker compose ps --format json | jq .
[
{
"ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
"Name": "example-bar-1",
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
"Project": "example",
"Service": "bar",
"State": "exited",
"Health": "",
"ExitCode": 0,
"Publishers": null
},
{
"ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
"Name": "example-foo-1",
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
"Project": "example",
"Service": "foo",
"State": "running",
"Health": "",
"ExitCode": 0,
"Publishers": [
{
"URL": "0.0.0.0",
"TargetPort": 80,
"PublishedPort": 8080,
"Protocol": "tcp"
}
]
}
]
```
### Filter containers by status (--status) {#status}
Use the `--status` flag to filter the list of containers by status. For example,
to show only containers that are running, or only containers that have exited:
```console
$ docker compose ps --status=running
NAME COMMAND SERVICE STATUS PORTS
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
$ docker compose ps --status=exited
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
```
### Filter containers by status (--filter) {#filter}
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
flag. The example below is the equivalent to the example from the previous section,
this time using the `--filter` flag:
```console
$ docker compose ps --filter status=running
NAME COMMAND SERVICE STATUS PORTS
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
$ docker compose ps --filter status=running
NAME COMMAND SERVICE STATUS PORTS
example-bar-1 "/docker-entrypoint.…" bar exited (0)
```
The `docker compose ps` command currently only supports the `--filter status=<status>`
option, but additional filter options may be added in future.
deprecated: false
experimental: false
experimentalcli: false

View File

@@ -124,9 +124,8 @@ options:
- option: no-TTY
shorthand: T
value_type: bool
default_value: "false"
description: |
Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
default_value: "true"
description: 'Disable pseudo-TTY allocation (default: auto-detected).'
deprecated: false
hidden: false
experimental: false

View File

@@ -22,16 +22,21 @@ import (
"path/filepath"
clidocstool "github.com/docker/cli-docs-tool"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/compose"
"github.com/spf13/cobra"
)
func generateDocs(opts *options) error {
dockerCLI, err := command.NewDockerCli()
if err != nil {
return err
}
cmd := &cobra.Command{
Use: "docker",
DisableAutoGenTag: true,
}
cmd.AddCommand(compose.RootCommand(nil))
cmd.AddCommand(compose.RootCommand(dockerCLI, nil))
disableFlagsInUseLine(cmd)
tool, err := clidocstool.New(clidocstool.Options{

87
go.mod
View File

@@ -1,16 +1,16 @@
module github.com/docker/compose/v2
go 1.17
go 1.18
require (
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.1.0
github.com/compose-spec/compose-go v1.2.7
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.1
github.com/containerd/containerd v1.6.2
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
github.com/docker/buildx v0.7.1
github.com/docker/buildx v0.8.1 // when updating, also update the replace rules accordingly
github.com/docker/cli v20.10.12+incompatible
github.com/docker/cli-docs-tool v0.4.0
github.com/docker/docker v20.10.7+incompatible
@@ -21,7 +21,7 @@ require (
github.com/hashicorp/go-version v1.3.0
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-shellwords v1.0.12
github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
@@ -29,13 +29,13 @@ require (
github.com/pkg/errors v0.9.1
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.3.0
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/theupdateframework/notary v0.6.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.1.0
gotest.tools/v3 v3.2.0
)
require (
@@ -47,6 +47,7 @@ require (
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/ttrpc v1.1.0 // 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
@@ -58,29 +59,27 @@ require (
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/googleapis v1.4.1 // 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/go-cmp v0.5.7 // 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/imdario/mergo v0.3.13 // 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/klauspost/compress v1.15.0 // 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/mitchellh/mapstructure v1.5.0 // 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
@@ -88,57 +87,61 @@ require (
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_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/common v0.32.1 // 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/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // 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
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
go.opentelemetry.io/otel v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
go.opentelemetry.io/otel/metric v0.27.0 // indirect
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
go.opentelemetry.io/otel/trace v1.4.1 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // 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/sys v0.0.0-20220114195835-da31bd327af9 // 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/grpc v1.44.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
gopkg.in/yaml.v3 v3.0.0 // indirect
k8s.io/apimachinery v0.23.4 // indirect; see replace for the actual version used
k8s.io/client-go v0.23.4 // indirect; see replace for the actual version used
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
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
// (for buildx)
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
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
// For k8s dependencies, we use a replace directive, to prevent them being
// upgraded to the version specified in containerd, which is not relevant to the
// version needed.
// See https://github.com/docker/buildx/pull/948 for details.
// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
k8s.io/api => k8s.io/api v0.22.4
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
k8s.io/client-go => k8s.io/client-go v0.22.4
)

374
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -32,9 +32,9 @@ type Service interface {
// Push executes the equivalent ot a `compose push`
Push(ctx context.Context, project *types.Project, options PushOptions) error
// Pull executes the equivalent of a `compose pull`
Pull(ctx context.Context, project *types.Project, opts PullOptions) error
Pull(ctx context.Context, project *types.Project, options PullOptions) error
// Create executes the equivalent to a `compose create`
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
Create(ctx context.Context, project *types.Project, options CreateOptions) error
// Start executes the equivalent to a `compose start`
Start(ctx context.Context, projectName string, options StartOptions) error
// Restart restarts containers
@@ -54,25 +54,25 @@ type Service interface {
// Convert translate compose model into backend's native format
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
// Kill executes the equivalent to a `compose kill`
Kill(ctx context.Context, project *types.Project, options KillOptions) error
Kill(ctx context.Context, projectName string, options KillOptions) error
// RunOneOffContainer creates a service oneoff container and starts its dependencies
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
// Remove executes the equivalent to a `compose rm`
Remove(ctx context.Context, project string, options RemoveOptions) error
Remove(ctx context.Context, projectName string, options RemoveOptions) error
// Exec executes a command in a running service container
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
Exec(ctx context.Context, projectName string, options RunOptions) (int, error)
// Copy copies a file/folder between a service container and the local filesystem
Copy(ctx context.Context, project string, options CopyOptions) error
Copy(ctx context.Context, projectName string, options CopyOptions) error
// Pause executes the equivalent to a `compose pause`
Pause(ctx context.Context, project string, options PauseOptions) error
Pause(ctx context.Context, projectName string, options PauseOptions) error
// UnPause executes the equivalent to a `compose unpause`
UnPause(ctx context.Context, project string, options PauseOptions) error
UnPause(ctx context.Context, projectName string, options PauseOptions) error
// Top executes the equivalent to a `compose top`
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
// Events executes the equivalent to a `compose events`
Events(ctx context.Context, project string, options EventsOptions) error
Events(ctx context.Context, projectName string, options EventsOptions) error
// Port executes the equivalent to a `compose port`
Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
// Images executes the equivalent of a `compose images`
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
}
@@ -91,6 +91,8 @@ type BuildOptions struct {
Quiet bool
// Services passed in the command line to be built
Services []string
// Ssh authentications passed in the command line
SSHs []types.SSHKey
}
// CreateOptions group options of the Create API
@@ -115,6 +117,8 @@ type CreateOptions struct {
// StartOptions group options of the Start API
type StartOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
Project *types.Project
// Attach to container and forward logs if not nil
Attach LogConsumer
// AttachTo set the services to attach to
@@ -216,6 +220,7 @@ type RunOptions struct {
Detach bool
AutoRemove bool
Tty bool
Interactive bool
WorkingDir string
User string
Environment []string

View File

@@ -37,7 +37,7 @@ type ServiceProxy struct {
PsFn func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
ListFn func(ctx context.Context, options ListOptions) ([]Stack, error)
ConvertFn func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
KillFn func(ctx context.Context, project *types.Project, options KillOptions) error
KillFn func(ctx context.Context, project string, options KillOptions) error
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
RemoveFn func(ctx context.Context, project string, options RemoveOptions) error
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
@@ -219,14 +219,11 @@ func (s *ServiceProxy) Convert(ctx context.Context, project *types.Project, opti
}
// Kill implements Service interface
func (s *ServiceProxy) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
func (s *ServiceProxy) Kill(ctx context.Context, projectName string, options KillOptions) error {
if s.KillFn == nil {
return ErrNotImplemented
}
for _, i := range s.interceptors {
i(ctx, project)
}
return s.KillFn(ctx, project, options)
return s.KillFn(ctx, projectName, options)
}
// RunOneOffContainer implements Service interface
@@ -241,43 +238,43 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
}
// Remove implements Service interface
func (s *ServiceProxy) Remove(ctx context.Context, project string, options RemoveOptions) error {
func (s *ServiceProxy) Remove(ctx context.Context, projectName string, options RemoveOptions) error {
if s.RemoveFn == nil {
return ErrNotImplemented
}
return s.RemoveFn(ctx, project, options)
return s.RemoveFn(ctx, projectName, options)
}
// Exec implements Service interface
func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOptions) (int, error) {
func (s *ServiceProxy) Exec(ctx context.Context, projectName string, options RunOptions) (int, error) {
if s.ExecFn == nil {
return 0, ErrNotImplemented
}
return s.ExecFn(ctx, project, options)
return s.ExecFn(ctx, projectName, options)
}
// Copy implements Service interface
func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
func (s *ServiceProxy) Copy(ctx context.Context, projectName string, options CopyOptions) error {
if s.CopyFn == nil {
return ErrNotImplemented
}
return s.CopyFn(ctx, project, options)
return s.CopyFn(ctx, projectName, options)
}
// Pause implements Service interface
func (s *ServiceProxy) Pause(ctx context.Context, project string, options PauseOptions) error {
func (s *ServiceProxy) Pause(ctx context.Context, projectName string, options PauseOptions) error {
if s.PauseFn == nil {
return ErrNotImplemented
}
return s.PauseFn(ctx, project, options)
return s.PauseFn(ctx, projectName, options)
}
// UnPause implements Service interface
func (s *ServiceProxy) UnPause(ctx context.Context, project string, options PauseOptions) error {
func (s *ServiceProxy) UnPause(ctx context.Context, projectName string, options PauseOptions) error {
if s.UnPauseFn == nil {
return ErrNotImplemented
}
return s.UnPauseFn(ctx, project, options)
return s.UnPauseFn(ctx, projectName, options)
}
// Top implements Service interface
@@ -289,19 +286,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
}
// Events implements Service interface
func (s *ServiceProxy) Events(ctx context.Context, project string, options EventsOptions) error {
func (s *ServiceProxy) Events(ctx context.Context, projectName string, options EventsOptions) error {
if s.EventsFn == nil {
return ErrNotImplemented
}
return s.EventsFn(ctx, project, options)
return s.EventsFn(ctx, projectName, options)
}
// Port implements Service interface
func (s *ServiceProxy) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
if s.PortFn == nil {
return "", 0, ErrNotImplemented
}
return s.PortFn(ctx, project, service, port, options)
return s.PortFn(ctx, projectName, service, port, options)
}
// Images implements Service interface

View File

@@ -48,7 +48,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
for _, container := range containers {
err := s.attachContainer(ctx, container, listener, project)
err := s.attachContainer(ctx, container, listener)
if err != nil {
return nil, err
}
@@ -56,13 +56,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
return containers, err
}
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener, project *types.Project) error {
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener) error {
serviceName := container.Labels[api.ServiceLabel]
containerName := getContainerNameWithoutProject(container)
service, err := project.GetService(serviceName)
if err != nil {
return err
}
listener(api.ContainerEvent{
Type: api.ContainerEventAttach,
@@ -78,7 +74,13 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
Line: line,
})
})
_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w, w)
inspect, err := s.dockerCli.Client().ContainerInspect(ctx, container.ID)
if err != nil {
return err
}
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, w, w)
return err
}

View File

@@ -27,11 +27,12 @@ import (
_ "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"
"github.com/moby/buildkit/session/secrets/secretsprovider"
"github.com/moby/buildkit/session/sshforward/sshprovider"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker/compose/v2/pkg/api"
@@ -63,7 +64,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if service.Build != nil {
imageName := getImageName(service, project.Name)
imagesToBuild = append(imagesToBuild, imageName)
buildOptions, err := s.toBuildOptions(project, service, imageName)
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
if err != nil {
return err
}
@@ -81,7 +82,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
Attrs: map[string]string{"ref": image},
})
}
opts[imageName] = buildOptions
}
}
@@ -160,7 +160,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
continue
}
opt, err := s.toBuildOptions(project, service, imageName)
opt, err := s.toBuildOptions(project, service, imageName, []types.SSHKey{})
if err != nil {
return nil, err
}
@@ -188,37 +188,29 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
for name, info := range imgs {
images[name] = info.ID
}
return images, nil
}
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
ping, err := s.apiClient().Ping(ctx)
if err != nil {
return command.ServerInfo{}, err
for _, s := range project.Services {
imgName := getImageName(s, project.Name)
digest, ok := images[imgName]
if ok {
s.CustomLabels[api.ImageDigestLabel] = digest
}
}
serverInfo := command.ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
}
return serverInfo, err
return images, nil
}
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
}
serverInfo, err := s.serverInfo(ctx)
if err != nil {
return nil, err
}
if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
return s.doBuildClassic(ctx, opts)
}
return s.doBuildBuildkit(ctx, project, opts, mode)
}
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
var tags []string
tags = append(tags, imageTag)
@@ -243,11 +235,59 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
plats = append(plats, p)
}
cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
if err != nil {
return build.Options{}, err
}
cacheTo, err := buildflags.ParseCacheEntry(service.Build.CacheTo)
if err != nil {
return build.Options{}, err
}
sessionConfig := []session.Attachable{
authprovider.NewDockerAuthProvider(s.stderr()),
}
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
if err != nil {
return build.Options{}, err
}
sessionConfig = append(sessionConfig, sshAgentProvider)
}
if len(service.Build.Secrets) > 0 {
var sources []secretsprovider.Source
for _, secret := range service.Build.Secrets {
config := project.Secrets[secret.Source]
if config.File == "" {
return build.Options{}, fmt.Errorf("build.secrets only supports file-based secrets: %q", secret.Source)
}
sources = append(sources, secretsprovider.Source{
ID: secret.Source,
FilePath: config.File,
})
}
store, err := secretsprovider.NewStore(sources)
if err != nil {
return build.Options{}, err
}
p := secretsprovider.NewSecretProvider(store)
sessionConfig = append(sessionConfig, p)
}
if len(service.Build.Tags) > 0 {
tags = append(tags, service.Build.Tags...)
}
return build.Options{
Inputs: build.Inputs{
ContextPath: service.Build.Context,
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
},
CacheFrom: cacheFrom,
CacheTo: cacheTo,
NoCache: service.Build.NoCache,
Pull: service.Build.Pull,
BuildArgs: buildArgs,
Tags: tags,
Target: service.Build.Target,
@@ -255,10 +295,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
Platforms: plats,
Labels: service.Build.Labels,
NetworkMode: service.Build.Network,
ExtraHosts: service.Build.ExtraHosts,
Session: []session.Attachable{
authprovider.NewDockerAuthProvider(s.stderr()),
},
ExtraHosts: service.Build.ExtraHosts.AsList(),
Session: sessionConfig,
}, nil
}
@@ -292,3 +330,14 @@ func dockerFilePath(context string, dockerfile string) string {
}
return filepath.Join(context, dockerfile)
}
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
for _, sshKey := range sshKeys {
sshConfig = append(sshConfig, sshprovider.AgentConfig{
ID: sshKey.ID,
Paths: []string{sshKey.Path},
})
}
return sshprovider.NewSSHAgentProvider(sshConfig)
}

View File

@@ -45,7 +45,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
// build and will lock
progressCtx, cancel := context.WithCancel(context.Background())
defer cancel()
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
w := xprogress.NewPrinter(progressCtx, s.stdout(), 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)

View File

@@ -143,17 +143,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
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")
}
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
configFile := s.configFile()
creds, err := configFile.GetAllCredentials()

View File

@@ -24,15 +24,15 @@ import (
"io"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/pkg/errors"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/streams"
"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/client"
"github.com/pkg/errors"
"github.com/sanathkr/go-yaml"
)
@@ -165,7 +165,7 @@ SERVICES:
continue SERVICES
}
}
return project, errors.New("no such service: " + qs)
return project, errors.Wrapf(api.ErrNotFound, "no such service: %q", qs)
}
err := project.ForServices(services)
if err != nil {
@@ -194,3 +194,39 @@ func (s *composeService) actualState(ctx context.Context, projectName string, se
}
return containers, project, nil
}
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil {
return nil, err
}
actual := types.Volumes{}
for _, vol := range volumes.Volumes {
actual[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
Name: vol.Name,
Driver: vol.Driver,
Labels: vol.Labels,
}
}
return actual, nil
}
func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) {
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
Filters: filters.NewArgs(projectFilter(projectName)),
})
if err != nil {
return nil, err
}
actual := types.Networks{}
for _, net := range networks {
actual[net.Labels[api.NetworkLabel]] = types.NetworkConfig{
Name: net.Name,
Driver: net.Driver,
Labels: net.Labels,
}
}
return actual, nil
}

View File

@@ -18,14 +18,13 @@ package compose
import (
"context"
"fmt"
"sort"
"strconv"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"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"
)
// Containers is a set of moby Container
@@ -41,17 +40,7 @@ const (
func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
var containers Containers
f := []filters.KeyValuePair{projectFilter(project)}
if len(selectedServices) == 1 {
f = append(f, serviceFilter(selectedServices[0]))
}
switch oneOff {
case oneOffOnly:
f = append(f, oneOffFilter(true))
case oneOffExclude:
f = append(f, oneOffFilter(false))
case oneOffInclude:
}
f := getDefaultFilters(project, oneOff, selectedServices...)
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(f...),
All: stopped,
@@ -65,6 +54,40 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
return containers, nil
}
func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) []filters.KeyValuePair {
f := []filters.KeyValuePair{projectFilter(projectName)}
if len(selectedServices) == 1 {
f = append(f, serviceFilter(selectedServices[0]))
}
switch oneOff {
case oneOffOnly:
f = append(f, oneOffFilter(true))
case oneOffExclude:
f = append(f, oneOffFilter(false))
case oneOffInclude:
}
return f
}
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
defaultFilters...,
),
All: stopped,
})
if err != nil {
return moby.Container{}, err
}
if len(containers) < 1 {
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
}
container := containers[0]
return container, nil
}
// containerPredicate define a predicate we want container to satisfy for filtering operations
type containerPredicate func(c moby.Container) bool
@@ -87,14 +110,6 @@ 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

@@ -189,17 +189,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
continue
}
if recreate == api.RecreateNever {
continue
}
// Re-create diverged containers
configHash, err := ServiceHash(service)
mustRecreate, err := mustRecreate(service, container, recreate)
if err != nil {
return err
}
name := getContainerProgressName(container)
diverged := container.Labels[api.ConfigHashLabel] != configHash
if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
if mustRecreate {
i, container := i, container
eg.Go(func() error {
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
@@ -211,6 +205,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
// Enforce non-diverged containers are running
w := progress.ContextWriter(ctx)
name := getContainerProgressName(container)
switch container.State {
case ContainerRunning:
w.Event(progress.RunningEvent(name))
@@ -249,6 +244,22 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
return err
}
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
if policy == api.RecreateNever {
return false, nil
}
if policy == api.RecreateForce || expected.Extensions[extLifecycle] == forceRecreate {
return true, nil
}
configHash, err := ServiceHash(expected)
if err != nil {
return false, err
}
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
return configChanged || imageUpdated, nil
}
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
if service.ContainerName != "" {

View File

@@ -42,59 +42,80 @@ const (
acrossServices = fromService | toService
)
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
srcService, srcPath := splitCpArg(opts.Source)
destService, dstPath := splitCpArg(opts.Destination)
func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
projectName = strings.ToLower(projectName)
srcService, srcPath := splitCpArg(options.Source)
destService, dstPath := splitCpArg(options.Destination)
var direction copyDirection
var serviceName string
var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
if srcService != "" {
direction |= fromService
serviceName = srcService
copyFunc = s.copyFromContainer
// copying from multiple containers of a services doesn't make sense.
if opts.All {
if options.All {
return errors.New("cannot use the --all flag when copying from a service")
}
}
if destService != "" {
direction |= toService
serviceName = destService
copyFunc = s.copyToContainer
}
if direction == acrossServices {
return errors.New("copying between services is not supported")
}
containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
if direction == 0 {
return errors.New("unknown copy direction")
}
containers, err := s.listContainersTargetedForCopy(ctx, projectName, options.Index, direction, serviceName)
if err != nil {
return err
}
if len(containers) < 1 {
return fmt.Errorf("no container found for service %q", serviceName)
}
if !opts.All {
containers = containers.filter(indexed(opts.Index))
}
g := errgroup.Group{}
for _, container := range containers {
containerID := container.ID
g.Go(func() error {
switch direction {
case fromService:
return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts)
case toService:
return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts)
case acrossServices:
return errors.New("copying between services is not supported")
default:
return errors.New("unknown copy direction")
}
return copyFunc(ctx, containerID, srcPath, dstPath, options)
})
}
return g.Wait()
}
func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
var containers Containers
var err error
switch {
case index > 0:
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
if err != nil {
return nil, err
}
return append(containers, container), nil
default:
containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
if err != nil {
return nil, err
}
if len(containers) < 1 {
return nil, fmt.Errorf("no container found for service %q", serviceName)
}
if direction == fromService {
return containers[:1], err
}
return containers, err
}
}
func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
var err error
if srcPath != "-" {

View File

@@ -173,6 +173,10 @@ func prepareServicesDependsOn(p *types.Project) error {
dependencies = append(dependencies, spec[0])
}
for _, link := range service.Links {
dependencies = append(dependencies, strings.Split(link, ":")[0])
}
if len(dependencies) == 0 {
continue
}
@@ -371,7 +375,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
DNS: service.DNS,
DNSSearch: service.DNSSearch,
DNSOptions: service.DNSOpts,
ExtraHosts: service.ExtraHosts,
ExtraHosts: service.ExtraHosts.AsList(),
SecurityOpt: securityOpts,
UsernsMode: container.UsernsMode(service.UserNSMode),
Privileged: service.Privileged,
@@ -709,11 +713,13 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
MOUNTS:
for _, m := range mountOptions {
volumeMounts[m.Target] = struct{}{}
// `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
// `Mount` is preferred but does not offer option to created host path if missing
// so `Bind` API is used here with raw volume string
// see https://github.com/moby/moby/issues/43483
for _, v := range service.Volumes {
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
binds = append(binds, v.String())
continue MOUNTS
}
}
@@ -723,23 +729,6 @@ 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 {
@@ -1059,7 +1048,10 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
for _, ipamConfig := range n.Ipam.Config {
config := network.IPAMConfig{
Subnet: ipamConfig.Subnet,
Subnet: ipamConfig.Subnet,
IPRange: ipamConfig.IPRange,
Gateway: ipamConfig.Gateway,
AuxAddress: ipamConfig.AuxiliaryAddresses,
}
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
}
@@ -1078,14 +1070,13 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
return nil
}
func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error {
w := progress.ContextWriter(ctx)
eventName := fmt.Sprintf("Network %s", networkName)
func (s *composeService) removeNetwork(ctx context.Context, network string, w progress.Writer) error {
eventName := fmt.Sprintf("Network %s", network)
w.Event(progress.RemovingEvent(eventName))
if err := s.apiClient().NetworkRemove(ctx, networkID); err != nil {
if err := s.apiClient().NetworkRemove(ctx, network); err != nil {
w.Event(progress.ErrorEvent(eventName))
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", network))
}
w.Event(progress.RemovedEvent(eventName))

View File

@@ -143,15 +143,6 @@ func TestBuildContainerMountOptions(t *testing.T) {
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{

View File

@@ -23,8 +23,8 @@ import (
"time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/registry/client"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs"
"golang.org/x/sync/errgroup"
@@ -41,7 +41,6 @@ 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
@@ -51,8 +50,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
return err
}
if builtFromResources {
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
project := options.Project
if project == nil {
project, err = s.getProjectWithResources(ctx, containers, projectName)
if err != nil {
return err
}
@@ -62,7 +62,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
resourceToRemove = true
}
err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service string) error {
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
serviceContainers := containers.filter(isService(service))
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
return err
@@ -71,7 +71,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
return err
}
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
orphans := containers.filter(isNotService(project.ServiceNames()...))
if options.RemoveOrphans && len(orphans) > 0 {
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
if err != nil {
@@ -79,21 +79,18 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
}
}
ops, err := s.ensureNetworksDown(ctx, projectName)
if err != nil {
return err
}
ops := s.ensureNetworksDown(ctx, project, w)
if options.Images != "" {
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
}
if options.Volumes {
ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
}
if !resourceToRemove && len(ops) == 0 {
w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
fmt.Fprintf(s.stderr(), "Warning: No resource found to remove for project %q.\n", projectName)
}
eg, _ := errgroup.WithContext(ctx)
@@ -106,6 +103,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
var ops []downOp
for _, vol := range project.Volumes {
if vol.External.External {
continue
}
volumeName := vol.Name
ops = append(ops, func() error {
return s.removeVolume(ctx, volumeName, w)
@@ -114,9 +114,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
return ops
}
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
var ops []downOp
for image := range s.getServiceImages(options, projectName) {
for image := range s.getServiceImages(options, project) {
image := image
ops = append(ops, func() error {
return s.removeImage(ctx, image, w)
@@ -125,31 +125,34 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
return ops
}
func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
var ops []downOp
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
if err != nil {
return ops, err
}
for _, n := range networks {
networkID := n.ID
for _, n := range project.Networks {
if n.External.External {
continue
}
networkName := n.Name
_, err := s.apiClient().NetworkInspect(ctx, networkName, moby.NetworkInspectOptions{})
if client.IsNotFound(err) {
return nil
}
ops = append(ops, func() error {
return s.removeNetwork(ctx, networkID, networkName)
return s.removeNetwork(ctx, networkName, w)
})
}
return ops, nil
return ops
}
func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
images := map[string]struct{}{}
for _, service := range options.Project.Services {
for _, service := range project.Services {
image := service.Image
if options.Images == "local" && image != "" {
continue
}
if image == "" {
image = getImageName(service, projectName)
image = getImageName(service, project.Name)
}
images[image] = struct{}{}
}
@@ -233,21 +236,23 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
return eg.Wait()
}
func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
func (s *composeService) getProjectWithResources(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 {
project, err := s.projectFromName(containers, projectName)
if err != nil && !api.IsNotFoundError(err) {
return nil, err
}
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,
}
volumes, err := s.actualVolumes(ctx, projectName)
if err != nil {
return nil, err
}
project.Volumes = volumes
networks, err := s.actualNetworks(ctx, projectName)
if err != nil {
return nil, err
}
project.Networks = networks
return project, nil
}

View File

@@ -23,7 +23,6 @@ import (
compose "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/docker/docker/api/types/volume"
@@ -49,6 +48,8 @@ func TestDown(t *testing.T) {
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
@@ -58,9 +59,7 @@ func TestDown(t *testing.T) {
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
nil)
api.EXPECT().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
@@ -84,6 +83,8 @@ func TestDownRemoveOrphans(t *testing.T) {
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
@@ -93,9 +94,7 @@ func TestDownRemoveOrphans(t *testing.T) {
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
nil)
api.EXPECT().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, nil)
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
@@ -117,12 +116,12 @@ func TestDownRemoveVolumes(t *testing.T) {
Return(volume.VolumeListOKBody{
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return(nil, 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().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})

View File

@@ -29,9 +29,10 @@ import (
"github.com/docker/compose/v2/pkg/utils"
)
func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
projectName = strings.ToLower(projectName)
events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
Filters: filters.NewArgs(projectFilter(project)),
Filters: filters.NewArgs(projectFilter(projectName)),
})
for {
select {

View File

@@ -18,148 +18,44 @@ package compose
import (
"context"
"fmt"
"io"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/stdcopy"
"github.com/moby/term"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command/container"
"github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
)
func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) {
container, err := s.getExecTarget(ctx, project, opts)
func (s *composeService) Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) {
projectName = strings.ToLower(projectName)
target, err := s.getExecTarget(ctx, projectName, options)
if err != nil {
return 0, err
}
exec, err := s.apiClient().ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
Cmd: opts.Command,
Env: opts.Environment,
User: opts.User,
Privileged: opts.Privileged,
Tty: opts.Tty,
Detach: opts.Detach,
WorkingDir: opts.WorkingDir,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
})
if err != nil {
return 0, err
}
if opts.Detach {
return 0, s.apiClient().ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{
Detach: true,
Tty: opts.Tty,
})
}
resp, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{
Tty: opts.Tty,
})
if err != nil {
return 0, err
}
defer resp.Close() //nolint:errcheck
if opts.Tty {
s.monitorTTySize(ctx, exec.ID, s.apiClient().ContainerExecResize)
exec := container.NewExecOptions()
exec.Interactive = options.Interactive
exec.TTY = options.Tty
exec.Detach = options.Detach
exec.User = options.User
exec.Privileged = options.Privileged
exec.Workdir = options.WorkingDir
exec.Container = target.ID
exec.Command = options.Command
for _, v := range options.Environment {
err := exec.Env.Set(v)
if err != nil {
return 0, err
}
}
err = s.interactiveExec(ctx, opts, resp)
if err != nil {
return 0, err
}
return s.getExecExitStatus(ctx, exec.ID)
}
// inspired by https://github.com/docker/cli/blob/master/cli/command/container/exec.go#L116
func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOptions, resp moby.HijackedResponse) error {
outputDone := make(chan error)
inputDone := make(chan error)
stdout := ContainerStdout{HijackedResponse: resp}
stdin := ContainerStdin{HijackedResponse: resp}
r, err := s.getEscapeKeyProxy(s.stdin(), opts.Tty)
if err != nil {
return err
}
in := s.stdin()
if in.IsTerminal() && opts.Tty {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return err
}
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
}
go func() {
if opts.Tty {
_, err := io.Copy(s.stdout(), stdout)
outputDone <- err
} else {
_, err := stdcopy.StdCopy(s.stdout(), s.stderr(), stdout)
outputDone <- err
}
stdout.Close() //nolint:errcheck
}()
go func() {
_, err := io.Copy(stdin, r)
inputDone <- err
stdin.Close() //nolint:errcheck
}()
for {
select {
case err := <-outputDone:
return err
case err := <-inputDone:
if _, ok := err.(term.EscapeError); ok {
return nil
}
if err != nil {
return err
}
// Wait for output to complete streaming
case <-ctx.Done():
return ctx.Err()
}
err = container.RunExec(s.dockerCli, exec)
if sterr, ok := err.(cli.StatusError); ok {
return sterr.StatusCode, nil
}
return 0, err
}
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
serviceFilter(opts.Service),
containerNumberFilter(opts.Index),
),
})
if err != nil {
return moby.Container{}, err
}
if len(containers) < 1 {
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", opts.Service, opts.Index)
}
container := containers[0]
return container, nil
}
func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) {
resp, err := s.apiClient().ContainerExecInspect(ctx, execID)
if err != nil {
return 0, err
}
return resp.ExitCode, nil
return s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, opts.Service, opts.Index)
}

View File

@@ -32,6 +32,7 @@ import (
)
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
projectName = strings.ToLower(projectName)
allContainers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
All: true,
Filters: filters.NewArgs(projectFilter(projectName)),
@@ -93,7 +94,6 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
tag := ""
repository := ""
if len(inspect.RepoTags) > 0 {
repotag := strings.Split(inspect.RepoTags[0], ":")
repository = repotag[0]
if len(repotag) > 1 {

View File

@@ -18,8 +18,9 @@ package compose
import (
"context"
"fmt"
"strings"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"golang.org/x/sync/errgroup"
@@ -27,29 +28,29 @@ import (
"github.com/docker/compose/v2/pkg/progress"
)
func (s *composeService) Kill(ctx context.Context, project *types.Project, options api.KillOptions) error {
func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.kill(ctx, project, options)
return s.kill(ctx, strings.ToLower(projectName), options)
})
}
func (s *composeService) kill(ctx context.Context, project *types.Project, options api.KillOptions) error {
func (s *composeService) kill(ctx context.Context, projectName string, options api.KillOptions) error {
w := progress.ContextWriter(ctx)
services := options.Services
if len(services) == 0 {
services = project.ServiceNames()
}
var containers Containers
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, false, services...)
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, services...)
if err != nil {
return err
}
if len(containers) == 0 {
fmt.Fprintf(s.stderr(), "no container to kill")
}
eg, ctx := errgroup.WithContext(ctx)
containers.
filter(isService(project.ServiceNames()...)).
forEach(func(container moby.Container) {
eg.Go(func() error {
eventName := getContainerProgressName(container)

View File

@@ -22,7 +22,6 @@ import (
"strings"
"testing"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/golang/mock/gomock"
@@ -45,18 +44,18 @@ func TestKillAll(t *testing.T) {
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService("service1"), testService("service2")}}
name := strings.ToLower(testProject)
ctx := context.Background()
api.EXPECT().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
Filters: filters.NewArgs(projectFilter(name)),
}).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)
api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
err := tested.kill(ctx, &project, compose.KillOptions{})
err := tested.kill(ctx, name, compose.KillOptions{})
assert.NilError(t, err)
}
@@ -70,23 +69,19 @@ func TestKillSignal(t *testing.T) {
tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes()
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService(serviceName)}}
name := strings.ToLower(testProject)
listOptions := moby.ContainerListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)), serviceFilter(serviceName)),
Filters: filters.NewArgs(projectFilter(name), serviceFilter(serviceName)),
}
ctx := context.Background()
api.EXPECT().ContainerList(ctx, listOptions).Return([]moby.Container{testContainer(serviceName, "123", false)}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
err := tested.kill(ctx, &project, compose.KillOptions{Services: []string{serviceName}, Signal: "SIGTERM"})
err := tested.kill(ctx, name, compose.KillOptions{Services: []string{serviceName}, Signal: "SIGTERM"})
assert.NilError(t, err)
}
func testService(name string) types.ServiceConfig {
return types.ServiceConfig{Name: name}
}
func testContainer(service string, id string, oneOff bool) moby.Container {
return moby.Container{
ID: id,

View File

@@ -19,6 +19,7 @@ package compose
import (
"context"
"io"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
@@ -28,6 +29,7 @@ import (
)
func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
projectName = strings.ToLower(projectName)
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...)
if err != nil {
return err

View File

@@ -18,6 +18,7 @@ package compose
import (
"context"
"strings"
moby "github.com/docker/docker/api/types"
"golang.org/x/sync/errgroup"
@@ -26,9 +27,9 @@ import (
"github.com/docker/compose/v2/pkg/progress"
)
func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error {
func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.pause(ctx, project, options)
return s.pause(ctx, strings.ToLower(projectName), options)
})
}
@@ -54,14 +55,14 @@ func (s *composeService) pause(ctx context.Context, project string, options api.
return eg.Wait()
}
func (s *composeService) UnPause(ctx context.Context, project string, options api.PauseOptions) error {
func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.unPause(ctx, project, options)
return s.unPause(ctx, strings.ToLower(projectName), options)
})
}
func (s *composeService) unPause(ctx context.Context, project string, options api.PauseOptions) error {
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
func (s *composeService) unPause(ctx context.Context, projectName string, options api.PauseOptions) error {
containers, err := s.getContainers(ctx, projectName, oneOffExclude, false, options.Services...)
if err != nil {
return err
}

View File

@@ -19,6 +19,7 @@ package compose
import (
"context"
"fmt"
"strings"
"github.com/docker/compose/v2/pkg/api"
@@ -26,10 +27,11 @@ import (
"github.com/docker/docker/api/types/filters"
)
func (s *composeService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) {
func (s *composeService) Port(ctx context.Context, projectName string, service string, port int, options api.PortOptions) (string, int, error) {
projectName = strings.ToLower(projectName)
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(project),
projectFilter(projectName),
serviceFilter(service),
containerNumberFilter(options.Index),
),

View File

@@ -19,6 +19,7 @@ package compose
import (
"context"
"sort"
"strings"
"golang.org/x/sync/errgroup"
@@ -26,6 +27,7 @@ import (
)
func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
projectName = strings.ToLower(projectName)
oneOff := oneOffExclude
if options.All {
oneOff = oneOffInclude

View File

@@ -36,12 +36,12 @@ import (
"github.com/docker/compose/v2/pkg/progress"
)
func (s *composeService) Pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
if opts.Quiet {
return s.pull(ctx, project, opts)
func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
if options.Quiet {
return s.pull(ctx, project, options)
}
return progress.Run(ctx, func(ctx context.Context) error {
return s.pull(ctx, project, opts)
return s.pull(ctx, project, options)
})
}
@@ -55,6 +55,11 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
info.IndexServerAddress = registry.IndexServer
}
images, err := s.getLocalImagesDigests(ctx, project)
if err != nil {
return err
}
w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx)
@@ -69,8 +74,28 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
})
continue
}
switch service.PullPolicy {
case types.PullPolicyNever, types.PullPolicyBuild:
w.Event(progress.Event{
ID: service.Name,
Status: progress.Done,
Text: "Skipped",
})
continue
case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
if _, ok := images[service.Image]; ok {
w.Event(progress.Event{
ID: service.Name,
Status: progress.Done,
Text: "Exists",
})
continue
}
}
eg.Go(func() error {
err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
if err != nil {
if !opts.IgnoreFailures {
if service.Build != nil {
@@ -93,7 +118,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
return err
}
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) error {
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) (string, error) {
w.Event(progress.Event{
ID: service.Name,
Status: progress.Working,
@@ -101,12 +126,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
})
ref, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return err
return "", err
}
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
return "", err
}
key := repoInfo.Index.Name
@@ -116,12 +141,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
authConfig, err := configFile.GetAuthConfig(key)
if err != nil {
return err
return "", err
}
buf, err := json.Marshal(authConfig)
if err != nil {
return err
return "", err
}
stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
@@ -134,7 +159,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
Status: progress.Error,
Text: "Error",
})
return WrapCategorisedComposeError(err, PullFailure)
return "", WrapCategorisedComposeError(err, PullFailure)
}
dec := json.NewDecoder(stream)
@@ -144,10 +169,10 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
if err == io.EOF {
break
}
return WrapCategorisedComposeError(err, PullFailure)
return "", WrapCategorisedComposeError(err, PullFailure)
}
if jm.Error != nil {
return WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
return "", WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
}
if !quietPull {
toPullProgressEvent(service.Name, jm, w)
@@ -158,7 +183,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
Status: progress.Done,
Text: "Pulled",
})
return nil
inspected, _, err := s.dockerCli.Client().ImageInspectWithRaw(ctx, service.Image)
if err != nil {
return "", err
}
return inspected.ID, nil
}
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
@@ -195,10 +225,12 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
return progress.Run(ctx, func(ctx context.Context) error {
w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx)
for _, service := range needPull {
service := service
pulledImages := make([]string, len(needPull))
for i, service := range needPull {
i, service := i, service
eg.Go(func() error {
err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
pulledImages[i] = id
if err != nil && service.Build != nil {
// image can be built, so we can ignore pull failure
return nil
@@ -206,7 +238,16 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
return err
})
}
return eg.Wait()
for i, service := range needPull {
if pulledImages[i] != "" {
images[service.Image] = pulledImages[i]
}
}
err := eg.Wait()
if err != nil {
return err
}
return err
})
}

View File

@@ -30,8 +30,13 @@ import (
)
func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
projectName = strings.ToLower(projectName)
containers, _, err := s.actualState(ctx, projectName, options.Services)
if err != nil {
if api.IsNotFoundError(err) {
fmt.Fprintln(s.stderr(), "No stopped containers")
return nil
}
return err
}
@@ -45,7 +50,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
})
if len(names) == 0 {
fmt.Println("No stopped containers")
fmt.Fprintln(s.stderr(), "No stopped containers")
return nil
}
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))

View File

@@ -1,74 +0,0 @@
/*
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"
gosignal "os/signal"
"runtime"
"time"
"github.com/buger/goterm"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/signal"
)
func (s *composeService) monitorTTySize(ctx context.Context, container string, resize func(context.Context, string, moby.ResizeOptions) error) {
err := resize(ctx, container, moby.ResizeOptions{ // nolint:errcheck
Height: uint(goterm.Height()),
Width: uint(goterm.Width()),
})
if err != nil {
return
}
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
if runtime.GOOS == "windows" {
// Windows has no SIGWINCH support, so we have to poll tty size ¯\_(ツ)_/¯
go func() {
prevH := goterm.Height()
prevW := goterm.Width()
for {
time.Sleep(time.Millisecond * 250)
h := goterm.Height()
w := goterm.Width()
if prevW != w || prevH != h {
sigchan <- signal.SIGWINCH
}
prevH = h
prevW = w
}
}()
}
go func() {
for {
select {
case <-sigchan:
resize(ctx, container, moby.ResizeOptions{ // nolint:errcheck
Height: uint(goterm.Height()),
Width: uint(goterm.Width()),
})
case <-ctx.Done():
return
}
}
}()
}

View File

@@ -18,6 +18,7 @@ package compose
import (
"context"
"strings"
"github.com/docker/compose/v2/pkg/api"
"golang.org/x/sync/errgroup"
@@ -28,7 +29,7 @@ import (
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, projectName, options)
return s.restart(ctx, strings.ToLower(projectName), options)
})
}

View File

@@ -19,16 +19,12 @@ package compose
import (
"context"
"fmt"
"io"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli"
cmd "github.com/docker/cli/cli/command/container"
"github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/stringid"
"github.com/moby/term"
)
func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) {
@@ -37,98 +33,16 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
return 0, err
}
if opts.Detach {
err := s.apiClient().ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
fmt.Fprintln(s.stdout(), containerID)
return 0, nil
start := cmd.NewStartOptions()
start.OpenStdin = !opts.Detach && opts.Interactive
start.Attach = !opts.Detach
start.Containers = []string{containerID}
err = cmd.RunStart(s.dockerCli, &start)
if sterr, ok := err.(cli.StatusError); ok {
return sterr.StatusCode, nil
}
return s.runInteractive(ctx, containerID, opts)
}
func (s *composeService) runInteractive(ctx context.Context, containerID string, opts api.RunOptions) (int, error) {
in := s.stdin()
r, err := s.getEscapeKeyProxy(in, opts.Tty)
if err != nil {
return 0, err
}
stdin, stdout, err := s.getContainerStreams(ctx, containerID)
if err != nil {
return 0, err
}
if in.IsTerminal() && opts.Tty {
state, err := term.SetRawTerminal(in.FD())
if err != nil {
return 0, err
}
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
}
outputDone := make(chan error)
inputDone := make(chan error)
go func() {
if opts.Tty {
_, err := io.Copy(s.stdout(), stdout) //nolint:errcheck
outputDone <- err
} else {
_, err := stdcopy.StdCopy(s.stdout(), s.stderr(), stdout) //nolint:errcheck
outputDone <- err
}
stdout.Close() //nolint:errcheck
}()
go func() {
_, err := io.Copy(stdin, r)
inputDone <- err
stdin.Close() //nolint:errcheck
}()
err = s.apiClient().ContainerStart(ctx, containerID, moby.ContainerStartOptions{})
if err != nil {
return 0, err
}
s.monitorTTySize(ctx, containerID, s.apiClient().ContainerResize)
for {
select {
case err := <-outputDone:
if err != nil {
return 0, err
}
return s.terminateRun(ctx, containerID, opts)
case err := <-inputDone:
if _, ok := err.(term.EscapeError); ok {
return 0, nil
}
if err != nil {
return 0, err
}
// Wait for output to complete streaming
case <-ctx.Done():
return 0, ctx.Err()
}
}
}
func (s *composeService) terminateRun(ctx context.Context, containerID string, opts api.RunOptions) (exitCode int, err error) {
exitCh, errCh := s.apiClient().ContainerWait(ctx, containerID, container.WaitConditionNotRunning)
select {
case exit := <-exitCh:
exitCode = int(exit.StatusCode)
case err = <-errCh:
return
}
if opts.AutoRemove {
err = s.apiClient().ContainerRemove(ctx, containerID, moby.ContainerRemoveOptions{})
}
return
return 0, err
}
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
@@ -142,12 +56,15 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
applyRunOptions(project, &service, opts)
if err := s.dockerCli.In().CheckTty(opts.Interactive, service.Tty); err != nil {
return "", err
}
slug := stringid.GenerateRandomID()
if service.ContainerName == "" {
service.ContainerName = fmt.Sprintf("%s_%s_run_%s", project.Name, service.Name, stringid.TruncateID(slug))
}
service.Scale = 1
service.StdinOpen = true
service.Restart = ""
if service.Deploy != nil {
service.Deploy.RestartPolicy = nil
@@ -171,32 +88,17 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
}
updateServices(&service, observedState)
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.Detach && opts.AutoRemove, opts.UseNetworkAliases, true)
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1,
opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
if err != nil {
return "", err
}
containerID := created.ID
return containerID, nil
}
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)
if err != nil {
return nil, err
}
escapeKeys = customEscapeKeys
}
return ioutils.NewReadCloserWrapper(term.NewEscapeProxy(r, escapeKeys), r.Close), nil
return created.ID, nil
}
func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) {
service.Tty = opts.Tty
service.StdinOpen = true
service.StdinOpen = opts.Interactive
service.ContainerName = opts.Name
if len(opts.Command) > 0 {
@@ -214,6 +116,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
if len(opts.Environment) > 0 {
env := types.NewMappingWithEquals(opts.Environment)
projectEnv := env.Resolve(func(s string) (string, bool) {
if _, ok := service.Environment[s]; ok {
return "", false
}
v, ok := project.Environment[s]
return v, ok
}).RemoveEmpty()

View File

@@ -18,6 +18,7 @@ package compose
import (
"context"
"strings"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
@@ -30,20 +31,23 @@ import (
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, projectName, options, nil)
return s.start(ctx, strings.ToLower(projectName), options, nil)
})
}
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 := options.Project
if project == nil {
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
project, err = s.projectFromName(containers, projectName, options.AttachTo...)
if err != nil {
return err
}
}
eg, ctx := errgroup.WithContext(ctx)
@@ -55,12 +59,12 @@ func (s *composeService) start(ctx context.Context, projectName string, options
eg.Go(func() error {
return s.watchContainers(context.Background(), project.Name, options.AttachTo, listener, attached, func(container moby.Container) error {
return s.attachContainer(ctx, container, listener, project)
return s.attachContainer(ctx, container, listener)
})
})
}
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

View File

@@ -18,6 +18,7 @@ package compose
import (
"context"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
@@ -25,7 +26,7 @@ import (
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, projectName, options)
return s.stop(ctx, strings.ToLower(projectName), options)
})
}

View File

@@ -18,12 +18,14 @@ package compose
import (
"context"
"strings"
"github.com/docker/compose/v2/pkg/api"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) {
projectName = strings.ToLower(projectName)
var containers Containers
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false)
if err != nil {

View File

@@ -60,13 +60,13 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return progress.Run(ctx, func(ctx context.Context) error {
go func() {
<-signalChan
s.Kill(ctx, project, api.KillOptions{ // nolint:errcheck
Services: options.Create.Services,
s.Kill(ctx, project.Name, api.KillOptions{ // nolint:errcheck
Services: project.ServiceNames(),
})
}()
return s.Stop(ctx, project.Name, api.StopOptions{
Services: options.Create.Services,
Services: project.ServiceNames(),
})
})
}

View File

@@ -18,6 +18,7 @@ package e2e
import (
"net/http"
"os"
"strings"
"testing"
"time"
@@ -79,6 +80,57 @@ func TestLocalComposeBuild(t *testing.T) {
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
})
t.Run("build failed with ssh default value", func(t *testing.T) {
//unset SSH_AUTH_SOCK to be sure we don't have a default value for the SSH Agent
defaultSSHAUTHSOCK := os.Getenv("SSH_AUTH_SOCK")
os.Unsetenv("SSH_AUTH_SOCK") //nolint:errcheck
defer os.Setenv("SSH_AUTH_SOCK", defaultSSHAUTHSOCK) //nolint:errcheck
res := c.RunDockerComposeCmdNoCheck("--project-directory", "fixtures/build-test", "build", "--ssh", "")
res.Assert(t, icmd.Expected{
ExitCode: 1,
Err: "invalid empty ssh agent socket: make sure SSH_AUTH_SOCK is set",
})
})
t.Run("build succeed with ssh from Compose file", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test-ssh")
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test/ssh", "build")
c.RunDockerCmd("image", "inspect", "build-test-ssh")
})
t.Run("build succeed with ssh from CLI", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test-ssh")
c.RunDockerComposeCmd("-f", "fixtures/build-test/ssh/compose-without-ssh.yaml", "--project-directory",
"fixtures/build-test/ssh", "build", "--no-cache", "--ssh", "fake-ssh=./fixtures/build-test/ssh/fake_rsa")
c.RunDockerCmd("image", "inspect", "build-test-ssh")
})
t.Run("build failed with wrong ssh key id from CLI", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test-ssh")
res := c.RunDockerComposeCmdNoCheck("-f", "fixtures/build-test/ssh/compose-without-ssh.yaml",
"--project-directory", "fixtures/build-test/ssh", "build", "--no-cache", "--ssh",
"wrong-ssh=./fixtures/build-test/ssh/fake_rsa")
res.Assert(t, icmd.Expected{
ExitCode: 17,
Err: "failed to solve: rpc error: code = Unknown desc = unset ssh forward key fake-ssh",
})
})
t.Run("build succeed as part of up with ssh from Compose file", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test-ssh")
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test/ssh", "up", "-d", "--build")
t.Cleanup(func() {
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test/ssh", "down")
})
c.RunDockerCmd("image", "inspect", "build-test-ssh")
})
t.Run("build as part of up", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "build-test_nginx")
c.RunDockerOrExitError("rmi", "custom-nginx")
@@ -117,3 +169,36 @@ func TestLocalComposeBuild(t *testing.T) {
c.RunDockerCmd("rmi", "custom-nginx")
})
}
func TestBuildSecrets(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("build with secrets", func(t *testing.T) {
// ensure local test run does not reuse previously build image
c.RunDockerOrExitError("rmi", "build-test-secret")
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test/secrets", "build")
res.Assert(t, icmd.Success)
})
}
func TestBuildTags(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("build with tags", func(t *testing.T) {
// ensure local test run does not reuse previously build image
c.RunDockerOrExitError("rmi", "build-test-tags")
c.RunDockerComposeCmd("--project-directory", "./fixtures/build-test/tags", "build", "--no-cache")
res := c.RunDockerCmd("image", "inspect", "build-test-tags")
expectedOutput := `"RepoTags": [
"docker/build-test-tags:1.0.0",
"build-test-tags:latest",
"other-image-name:v1.0.0"
],
`
res.Assert(t, icmd.Expected{Out: expectedOutput})
})
}

View File

@@ -0,0 +1,34 @@
/*
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 TestDown(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
const projectName = "e2e-down"
t.Run("no resource to remove", func(t *testing.T) {
res := c.RunDockerOrExitError("compose", "--project-name", projectName, "down")
res.Assert(t, icmd.Expected{ExitCode: 0, Err: `No resource found to remove for project "e2e-down"`})
})
}

View File

@@ -0,0 +1,169 @@
/*
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 (
"os"
"strings"
"testing"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
)
func TestEnvPriority(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
projectDir := "./fixtures/environment/env-priority"
t.Run("up", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "env-compose-priority")
c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
"--project-directory", projectDir, "up", "-d", "--build")
})
// Full options activated
// 1. Compose file <-- Result expected
// 2. Shell environment variables
// 3. Environment file
// 4. Dockerfile
// 5. Variable is not defined
t.Run("compose file priority", func(t *testing.T) {
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Compose File")
})
// No Compose file, all other options
// 1. Compose file
// 2. Shell environment variables <-- Result expected
// 3. Environment file
// 4. Dockerfile
// 5. Variable is not defined
t.Run("shell priority", func(t *testing.T) {
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell")
})
// No Compose file and env variable pass to the run command
// 1. Compose file
// 2. Shell environment variables <-- Result expected
// 3. Environment file
// 4. Dockerfile
// 5. Variable is not defined
t.Run("shell priority from run command", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
"run", "--rm", "-e", "WHEREAMI=shell-run", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell-run")
})
// No Compose file & no env variable but override env file
// 1. Compose file
// 2. Shell environment variables
// 3. Environment file <-- Result expected
// 4. Dockerfile
// 5. Variable is not defined
t.Run("override env file", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "override")
})
// No Compose file & no env variable but override env file
// 1. Compose file
// 2. Shell environment variables
// 3. Environment file <-- Result expected
// 4. Dockerfile
// 5. Variable is not defined
t.Run("env file", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose.yaml",
"--project-directory", projectDir, "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Env File")
})
// No Compose file & no env variable, using an empty override env file
// 1. Compose file
// 2. Shell environment variables
// 3. Environment file
// 4. Dockerfile <-- Result expected
// 5. Variable is not defined
t.Run("use Dockerfile", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-priority/compose.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.empty",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Dockerfile")
})
t.Run("down", func(t *testing.T) {
c.RunDockerComposeCmd("--project-directory", projectDir, "down")
})
}
func TestEnvInterpolation(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
projectDir := "./fixtures/environment/env-interpolation"
// No variable defined in the Compose file and env variable pass to the run command
// 1. Compose file
// 2. Shell environment variables <-- Result expected
// 3. Environment file
// 4. Dockerfile
// 5. Variable is not defined
t.Run("shell priority from run command", func(t *testing.T) {
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-interpolation/compose.yaml",
"--project-directory", projectDir, "config")
res.Assert(t, icmd.Expected{Out: `IMAGE: default_env:shell`})
})
}
func TestCommentsInEnvFile(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
projectDir := "./fixtures/environment/env-file-comments"
t.Run("comments in env files", func(t *testing.T) {
c.RunDockerOrExitError("rmi", "env-file-comments")
c.RunDockerComposeCmd("-f", "./fixtures/environment/env-file-comments/compose.yaml",
"--project-directory", projectDir, "up", "-d", "--build")
res := c.RunDockerComposeCmd("-f", "./fixtures/environment/env-file-comments/compose.yaml",
"--project-directory", projectDir, "run", "--rm",
"-e", "COMMENT", "-e", "NO_COMMENT", "env-file-comments")
res.Assert(t, icmd.Expected{Out: `COMMENT=1234`})
res.Assert(t, icmd.Expected{Out: `NO_COMMENT=1234#5`})
c.RunDockerComposeCmd("--project-directory", projectDir, "down", "--rmi", "all")
})
}

View File

@@ -95,7 +95,7 @@ func TestLocalComposeUp(t *testing.T) {
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-web-1 "/dispatcher" web running (healthy) 0.0.0.0:90->80/tcp`})
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-db-1 "docker-entrypoint.s…" db running 5432/tcp`})
})
@@ -188,6 +188,11 @@ func TestRm(t *testing.T) {
assert.Assert(t, !strings.Contains(res.Combined(), projectName+"_simple"), res.Combined())
})
t.Run("rm -sf <none>", func(t *testing.T) {
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
res.Assert(t, icmd.Expected{ExitCode: 0})
})
t.Run("down", func(t *testing.T) {
c.RunDockerComposeCmd("-p", projectName, "down")
})

View File

@@ -47,15 +47,18 @@ func TestCopy(t *testing.T) {
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) {
t.Run("copy to container copies the file to the all containers by default", func(t *testing.T) {
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()
assert.Assert(t, strings.Contains(output, `hello world`), output)
res = c.RunDockerOrExitError("exec", projectName+"_nginx_2", "cat", "/tmp/default.txt")
res.Assert(t, icmd.Expected{ExitCode: 1})
output = c.RunDockerCmd("exec", projectName+"-nginx-2", "cat", "/tmp/default.txt").Stdout()
assert.Assert(t, strings.Contains(output, `hello world`), output)
output = c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/default.txt").Stdout()
assert.Assert(t, strings.Contains(output, `hello world`), output)
})
t.Run("copy to container with a given index copies the file to the given container", func(t *testing.T) {
@@ -69,20 +72,6 @@ func TestCopy(t *testing.T) {
res.Assert(t, icmd.Expected{ExitCode: 1})
})
t.Run("copy to container with the all flag copies the file to all containers", func(t *testing.T) {
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()
assert.Assert(t, strings.Contains(output, `hello world`), output)
output = c.RunDockerCmd("exec", projectName+"-nginx-2", "cat", "/tmp/all.txt").Stdout()
assert.Assert(t, strings.Contains(output, `hello world`), output)
output = c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/all.txt").Stdout()
assert.Assert(t, strings.Contains(output, `hello world`), output)
})
t.Run("copy from a container copies the file to the host from the first container by default", func(t *testing.T) {
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})

86
pkg/e2e/ddev_test.go Normal file
View File

@@ -0,0 +1,86 @@
/*
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 (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"gotest.tools/v3/assert"
)
const ddevVersion = "v1.19.1"
func TestComposeRunDdev(t *testing.T) {
if !composeStandaloneMode {
t.Skip("Not running on standalone mode.")
}
if runtime.GOOS == "windows" {
t.Skip("Running on Windows. Skipping...")
}
_ = os.Setenv("DDEV_DEBUG", "true")
c := NewParallelE2eCLI(t, binDir)
dir, err := os.MkdirTemp("", t.Name()+"-")
assert.NilError(t, err)
// ddev needs to be able to find mkcert to figure out where certs are.
_ = os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), dir))
siteName := filepath.Base(dir)
t.Cleanup(func() {
_ = c.RunCmdInDir(dir, "./ddev", "delete", "-Oy")
_ = c.RunCmdInDir(dir, "./ddev", "poweroff")
_ = os.RemoveAll(dir)
})
osName := "linux"
if runtime.GOOS == "darwin" {
osName = "macos"
}
compressedFilename := fmt.Sprintf("ddev_%s-%s.%s.tar.gz", osName, runtime.GOARCH, ddevVersion)
c.RunCmdInDir(dir, "curl", "-LO",
fmt.Sprintf("https://github.com/drud/ddev/releases/download/%s/%s",
ddevVersion,
compressedFilename))
c.RunCmdInDir(dir, "tar", "-xzf", compressedFilename)
// Create a simple index.php we can test against.
c.RunCmdInDir(dir, "sh", "-c", "echo '<?php\nprint \"ddev is working\";' >index.php")
c.RunCmdInDir(dir, "./ddev", "config", "--auto")
c.RunCmdInDir(dir, "./ddev", "config", "global", "--use-docker-compose-from-path")
vRes := c.RunCmdInDir(dir, "./ddev", "version")
out := vRes.Stdout()
fmt.Printf("ddev version: %s\n", out)
c.RunCmdInDir(dir, "./ddev", "poweroff")
c.RunCmdInDir(dir, "./ddev", "start", "-y")
curlRes := c.RunCmdInDir(dir, "curl", "-sSL", fmt.Sprintf("http://%s.ddev.site", siteName))
out = curlRes.Stdout()
fmt.Println(out)
assert.Assert(c.test, strings.Contains(out, "ddev is working"), "Could not start project")
}

View File

@@ -0,0 +1,22 @@
# syntax=docker/dockerfile:1.2
# 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.
FROM alpine
RUN echo "foo" > /tmp/expected
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /tmp/actual
RUN diff /tmp/expected /tmp/actual

View File

@@ -0,0 +1,11 @@
services:
ssh:
image: build-test-secret
build:
context: .
secrets:
- mysecret
secrets:
mysecret:
file: ./secret.txt

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1.2
# 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.
FROM alpine
RUN apk add --no-cache openssh-client
WORKDIR /compose
COPY fake_rsa.pub /compose/
RUN --mount=type=ssh,id=fake-ssh,required=true diff <(ssh-add -L) <(cat /compose/fake_rsa.pub)

View File

@@ -0,0 +1,5 @@
services:
ssh:
image: build-test-ssh
build:
context: .

View File

@@ -0,0 +1,7 @@
services:
ssh:
image: build-test-ssh
build:
context: .
ssh:
- fake-ssh=./fixtures/build-test/ssh/fake_rsa

View File

@@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAgEA7nJ4xAhJ7VwI63tuay3DCHaTXeEY92H6YNZ8ptAIBY0mUn6Gc9ms
94HvcAKemCJkO0fy6U2JOoST+q1YPAJf86NrIU41hZdzrw2QdqG/A3ja4VTAaOJbH9wafK
HpWLs6kyigGti3KSBabm4HARU8lgtRE6AuCC1+mw821FzTsMWMxRp/rKVxgsiMUsdd57WR
KOdn8TRm6NHcEsy7X7zAJ7+Ch/muGGCCk3Z9+YUzoVVtY/wGYmWXXj/NUzxnEq0XLyO8HC
+QU/9dWlh1OLmoMuxN1lYtHRFWWstCboKNsOcIiJsLKfQ1t4z4jXq5P7JTLE5Pngemrr4x
K21RFjVaGQpOjyQgZn1o0wAvy78KORwgN0Elwcb/XIKJepzzezCIyXlSafeXuHP+oMjM2s
2MXNHlMKv6Jwh4QYwUQ61+bAcPkcmIdltiAMNLcxYiqEud85EQQl9ciuhMKa0bZl1OEILw
VSIasEu9BEKVrz52ZZVLGMchqOV/4f1PqPEagnfnRYEttJ6AuaYUaJXvSQP6Zj4AFb6WrP
wEBIFOuAH9i4WtG52QAK6uc1wsPZlHm8J+VnTEBKFuGERu/uJBWPo43Lju8VrHuZU8QeON
ERKfJbc1EI9XpqWi+3VcWT0QJtxEGW2YmD505+cKNc31xwOtcqwogtwT0wnuj0BAf33HY3
8AAAc465v1nOub9ZwAAAAHc3NoLXJzYQAAAgEA7nJ4xAhJ7VwI63tuay3DCHaTXeEY92H6
YNZ8ptAIBY0mUn6Gc9ms94HvcAKemCJkO0fy6U2JOoST+q1YPAJf86NrIU41hZdzrw2Qdq
G/A3ja4VTAaOJbH9wafKHpWLs6kyigGti3KSBabm4HARU8lgtRE6AuCC1+mw821FzTsMWM
xRp/rKVxgsiMUsdd57WRKOdn8TRm6NHcEsy7X7zAJ7+Ch/muGGCCk3Z9+YUzoVVtY/wGYm
WXXj/NUzxnEq0XLyO8HC+QU/9dWlh1OLmoMuxN1lYtHRFWWstCboKNsOcIiJsLKfQ1t4z4
jXq5P7JTLE5Pngemrr4xK21RFjVaGQpOjyQgZn1o0wAvy78KORwgN0Elwcb/XIKJepzzez
CIyXlSafeXuHP+oMjM2s2MXNHlMKv6Jwh4QYwUQ61+bAcPkcmIdltiAMNLcxYiqEud85EQ
Ql9ciuhMKa0bZl1OEILwVSIasEu9BEKVrz52ZZVLGMchqOV/4f1PqPEagnfnRYEttJ6Aua
YUaJXvSQP6Zj4AFb6WrPwEBIFOuAH9i4WtG52QAK6uc1wsPZlHm8J+VnTEBKFuGERu/uJB
WPo43Lju8VrHuZU8QeONERKfJbc1EI9XpqWi+3VcWT0QJtxEGW2YmD505+cKNc31xwOtcq
wogtwT0wnuj0BAf33HY38AAAADAQABAAACAGK7A0YoKHQfp5HZid7XE+ptLpewnKXR69os
9XAcszWZPETsHr/ZYcUaCApZC1Hy642gPPRdJnUUcDFblS1DzncTM0iXGZI3I69X7nkwf+
bwI7EpZoIHN7P5bv4sDHKxE4/bQm/bS/u7abZP2JaaNHvsM6XsrSK1s7aAljNYPE71fVQf
pL3Xwyhj4bZk1n0asQA+0MsO541/V6BxJSR/AxFyOpoSyANP8sEcTw0CGl6zAJhlwj770b
E0uc+9MvCIuxDJuxnwl9Iv6nd+KQtT1FFBhvk4tXVTuG3fu6IGbKTTBLWLfRPiClv2AvSR
3CKDs+ykgFLu2BWCqtlQakLH1IW9DTkPExV4ZjkGCRWHEvmJxxOqL6B48tBjwa5gBuPJRA
aYRi15Z3sprsqCBfp+aHPkMXkkNGSe5ROj8lFFY/f50ZS/9DSlyuUURFLtIGe5XuPNJk7L
xJkYJAdNbgvk4IPgzsU2EuYvSja5mtuo3dVyEIRtsIAN4xl01edDAxHEow6ar4gZCtXnBb
WqeqchEi4zVTdkkuDP3SF362pktdY7Op0mS/yFd8LFrca3VCy2PqNhKvlxClRqM9Tlp9cY
qDuyS9AGT1QO4BMtvSJGFa3P+h76rQsNldC+nGa4wNWvpAUcT5NS8W9QnGp7ah/qOK07t7
fwYYENeRaAK3OItBABAAABAFjyDlnERaZ+/23B+zN0kQhCvmiNS5HE2+ooR5ofX08F3Uar
VPevy9p6s2LA+AlXY1ZZ1k0p5MI+4TkAbcB/VXaxrRUw9633p9rAgyumFGhK3i0M4whOCO
MJxmlp5sz5Qea+YzIa9z0F4ZwwvdHt7cp5joYBZoQ+Kv9OUy4xCs1zZ4ZbEsakGBrtLiTo
H3odXSg0mXQf10Ae3WkvAJ8M1xL/z1ryFeCvyv1sGwEx+5gvmZ6nnuJEEuXUBlpOwhPlST
4X9VL7gmdH9OoHnhUn3q2JEBQdVTegGij9wvoYT1bdzwBN/Amisn29K9w1aNdrNbYUJ6PO
0kE2lotSJ11qD8MAAAEBAP6IRuU25yj7zv0mEsaRWoQ5v3fYKKn4C6Eg3DbzKXybZkLyX7
6QlyO7uWf54SdXM7sQW8KoXaMu9qbo/o+4o3m7YfOY1MYeTz3yICYObVA7Fc9ZHwKzc1PB
dFNzy6/G+2niNQF3Q1Fjp31Ve9LwKJK8Kj/eUYZ3QiUIropkw4ppA8q3h+nkVGS23xSrTM
kGLugBjcnWUfuN0tKx/b5mqziRoyzr5u0qzFDtx97QAyETo/onFrd1bMGED2BHVyrCwtqI
p6SXo2uFzwm/nLtOMlmfpixNcK6dtql/brx3Lsu18a+0a42O5Q/TYRdRq8D60O16rUS/LN
sFOjIYSA3spnUAAAEBAO/Sc3NTarFylk+yhOTE8G9xDt5ndbY0gsfhM9D4byKlY4yYIvs+
yQAq3UHgSoN2f087zNubXSNiLJ8TOIPpbk8MzdvjqcpmnBhHcd4V2FLe9+hC8zEBf8MPPf
Cy1kXdCZ0bDMLTdgONiDTIc/0YXhFLZherXNIF1o/7Pcnu6IPwMDl/gcG3H1ncDxaLqxAm
L29SDXLX2hH9k+YJr9kFaho7PZBAwNYnMooupROSbQ9/lmfCt09ep/83n5G0mo93uGkyV2
1wcQw9X2ZT8eVHZ4ni3ACC6VYbUn2M3Z+e3tpGaYzKXd/yq0YyppoRvEaxM/ewXappUJul
Xsd/RqSc66MAAAAAAQID
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDucnjECEntXAjre25rLcMIdpNd4Rj3Yfpg1nym0AgFjSZSfoZz2az3ge9wAp6YImQ7R/LpTYk6hJP6rVg8Al/zo2shTjWFl3OvDZB2ob8DeNrhVMBo4lsf3Bp8oelYuzqTKKAa2LcpIFpubgcBFTyWC1EToC4ILX6bDzbUXNOwxYzFGn+spXGCyIxSx13ntZEo52fxNGbo0dwSzLtfvMAnv4KH+a4YYIKTdn35hTOhVW1j/AZiZZdeP81TPGcSrRcvI7wcL5BT/11aWHU4uagy7E3WVi0dEVZay0Jugo2w5wiImwsp9DW3jPiNerk/slMsTk+eB6auvjErbVEWNVoZCk6PJCBmfWjTAC/Lvwo5HCA3QSXBxv9cgol6nPN7MIjJeVJp95e4c/6gyMzazYxc0eUwq/onCHhBjBRDrX5sBw+RyYh2W2IAw0tzFiKoS53zkRBCX1yK6EwprRtmXU4QgvBVIhqwS70EQpWvPnZllUsYxyGo5X/h/U+o8RqCd+dFgS20noC5phRole9JA/pmPgAVvpas/AQEgU64Af2Lha0bnZAArq5zXCw9mUebwn5WdMQEoW4YRG7+4kFY+jjcuO7xWse5lTxB440REp8ltzUQj1empaL7dVxZPRAm3EQZbZiYPnTn5wo1zfXHA61yrCiC3BPTCe6PQEB/fcdjfw==

View File

@@ -0,0 +1,17 @@
# 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.
FROM nginx:alpine
RUN echo "SUCCESS"

View File

@@ -0,0 +1,9 @@
services:
nginx:
image: build-test-tags
build:
context: .
tags:
- docker.io/docker/build-test-tags:1.0.0
- other-image-name:v1.0.0

View File

@@ -0,0 +1,2 @@
COMMENT=1234#5
NO_COMMENT="1234#5"

View File

@@ -0,0 +1,18 @@
# 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.
FROM alpine
ENV COMMENT=Dockerfile
ENV NO_COMMENT=Dockerfile
CMD ["sh", "-c", "printenv", "|", "grep", "COMMENT"]

View File

@@ -0,0 +1,5 @@
services:
env-file-comments:
build:
context: .
image: env-file-comments

View File

@@ -0,0 +1,2 @@
WHEREAMI=Env File
IMAGE=default_env:${WHEREAMI}

View File

@@ -0,0 +1,6 @@
services:
env-interpolation:
image: bash
environment:
IMAGE: ${IMAGE}
command: echo "$IMAGE"

View File

@@ -0,0 +1 @@
WHEREAMI=Env File

View File

@@ -0,0 +1 @@
WHEREAMI=override

View File

@@ -0,0 +1,17 @@
# 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.
FROM alpine
ENV WHEREAMI=Dockerfile
CMD ["printenv", "WHEREAMI"]

View File

@@ -0,0 +1,7 @@
services:
env-compose-priority:
image: env-compose-priority
build:
context: .
environment:
WHEREAMI: "Compose File"

View File

@@ -0,0 +1,3 @@
services:
env-compose-priority:
image: env-compose-priority

View File

@@ -192,6 +192,17 @@ func (c *E2eCLI) RunCmd(args ...string) *icmd.Result {
return res
}
// RunCmdInDir runs a command in a given dir, expects no error and returns a result
func (c *E2eCLI) RunCmdInDir(dir string, args ...string) *icmd.Result {
fmt.Printf("\t[%s] %s\n", c.test.Name(), strings.Join(args, " "))
assert.Assert(c.test, len(args) >= 1, "require at least one command in parameters")
cmd := c.NewCmd(args[0], args[1:]...)
cmd.Dir = dir
res := icmd.RunCmd(cmd)
res.Assert(c.test, icmd.Success)
return res
}
// 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 {
@@ -204,17 +215,20 @@ func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
// RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
res := c.RunDockerComposeCmdNoCheck(args...)
res.Assert(c.test, icmd.Success)
return res
}
// RunDockerComposeCmdNoCheck runs a docker compose command, don't presume of any expectation and returns a result
func (c *E2eCLI) RunDockerComposeCmdNoCheck(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
return icmd.RunCmd(c.NewCmd(composeBinary, args...))
}
args = append([]string{"compose"}, args...)
res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
res.Assert(c.test, icmd.Success)
return res
return icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
}
// StdoutContains returns a predicate on command result expecting a string in stdout

View File

@@ -27,7 +27,7 @@ import (
func TestStartStop(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
const projectName = "e2e-start-stop"
const projectName = "e2e-start-stop-no-dependencies"
getProjectRegx := func(status string) string {
// match output with random spaces like:
@@ -43,7 +43,7 @@ func TestStartStop(t *testing.T) {
t.Run("Up a project", func(t *testing.T) {
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())
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-no-dependencies-simple-1 Started"), res.Combined())
res = c.RunDockerComposeCmd("ls", "--all")
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
@@ -57,13 +57,13 @@ func TestStartStop(t *testing.T) {
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop")
res := c.RunDockerComposeCmd("ls")
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop"), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-no-dependencies"), res.Combined())
res = c.RunDockerComposeCmd("ls", "--all")
testify.Regexp(t, getProjectRegx("exited"), res.Stdout())
res = c.RunDockerComposeCmd("--project-name", projectName, "ps")
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-words-1"), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-no-dependencies-words-1"), res.Combined())
res = c.RunDockerComposeCmd("--project-name", projectName, "ps", "--all")
testify.Regexp(t, getServiceRegx("simple", "exited"), res.Stdout())

View File

@@ -62,6 +62,21 @@ func (mr *MockCliMockRecorder) Apply(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockCli)(nil).Apply), arg0...)
}
// BuildKitEnabled mocks base method.
func (m *MockCli) BuildKitEnabled() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BuildKitEnabled")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BuildKitEnabled indicates an expected call of BuildKitEnabled.
func (mr *MockCliMockRecorder) BuildKitEnabled() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildKitEnabled", reflect.TypeOf((*MockCli)(nil).BuildKitEnabled))
}
// Client mocks base method.
func (m *MockCli) Client() client0.APIClient {
m.ctrl.T.Helper()
@@ -284,18 +299,3 @@ func (mr *MockCliMockRecorder) SetIn(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetIn", reflect.TypeOf((*MockCli)(nil).SetIn), arg0)
}
// StackOrchestrator mocks base method.
func (m *MockCli) StackOrchestrator(arg0 string) (command.Orchestrator, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StackOrchestrator", arg0)
ret0, _ := ret[0].(command.Orchestrator)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StackOrchestrator indicates an expected call of StackOrchestrator.
func (mr *MockCliMockRecorder) StackOrchestrator(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StackOrchestrator", reflect.TypeOf((*MockCli)(nil).StackOrchestrator), arg0)
}

View File

@@ -83,7 +83,10 @@ func (w *ttyWriter) Event(e Event) {
last.Status = e.Status
last.Text = e.Text
last.StatusText = e.StatusText
last.ParentID = e.ParentID
// allow set/unset of parent, but not swapping otherwise prompt is flickering
if last.ParentID == "" || e.ParentID == "" {
last.ParentID = e.ParentID
}
w.events[e.ID] = last
} else {
e.startTime = time.Now()