Compare commits

..

63 Commits

Author SHA1 Message Date
Guillaume Lours
32ae036fd0 Merge pull request #9925 from glours/update-docker-dependencies
bump docker dependencies version
2022-10-18 17:47:21 +02:00
Guillaume Lours
3f0550f884 log the error object instead of the string message only
Co-authored-by: Nick Sieger <nicksieger@gmail.com>
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-10-18 17:34:16 +02:00
Guillaume Lours
18ce1f41b7 replace deprecated functions
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-10-18 16:57:53 +02:00
Guillaume Lours
3bf29d401c bump docker dependencies version
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-10-18 16:26:54 +02:00
Laura Brehm
c384905d70 Merge pull request #9926 from laurazard/fix-makefile-modules-target
Fix Makefile target `validate-go-mod`
2022-10-18 16:09:43 +02:00
Laura Brehm
7424a3d3c1 Fix Makefile target validate-go-mod to only run correct bakefile target
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-10-18 15:38:41 +02:00
Laura Brehm
7c0b8a4c96 Merge pull request #9912 from docker/dependabot/go_modules/go.opentelemetry.io/otel-1.11.0
build(deps): bump go.opentelemetry.io/otel from 1.10.0 to 1.11.0
2022-10-18 13:53:07 +02:00
Laura Brehm
6b7e9466c4 Update e2e module deps
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-10-18 13:41:47 +02:00
Ulysses Souza
a6dd996988 Merge pull request #9823 from ulyssessouza/add-codecov
Add Codecov
2022-10-17 15:56:59 +02:00
Ulysses Souza
91eae4f035 Add Codecov
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-10-17 15:32:51 +02:00
Tiago Silva
8b89721476 port: fix container name in error message (#9909)
The error message is using V1 separator hardcoded, it should be using the configured separator value.

Signed-off-by: Tiago Silva <Tiago.MB.Silva@edu.azores.gov.pt>
2022-10-13 14:47:35 -04:00
Guillaume Lours
3892e9cbc4 Merge pull request #9887 from milas/issue-template
github: switch to issue template form
2022-10-13 20:43:33 +02:00
Milas Bowman
f43a1e3ece github: add feature request template
Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-10-13 14:31:11 -04:00
Milas Bowman
fa1ae635d1 github: switch to issue template form
Migrate the existing template into the new format and streamline
it a bit.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-10-13 14:31:11 -04:00
dependabot[bot]
afc0263f5c build(deps): bump go.opentelemetry.io/otel from 1.10.0 to 1.11.0
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-13 09:33:25 +00:00
Laura Brehm
b15df818c7 Merge pull request #9908 from docker/dependabot/go_modules/github.com/spf13/cobra-1.6.0
build(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.0
2022-10-12 18:23:35 +02:00
Laura Brehm
bb002a7688 Update e2e mod dependencies
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-10-12 18:12:11 +02:00
dependabot[bot]
2ccd57e01a build(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.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-10-12 10:00:55 +00:00
Nick Sieger
1c14d30777 Merge pull request #9168 from KoditkarVedant/9089-add-support-to-docker-compose-push-quiet-option
Add support to push images quietly via compose cli 🤫
2022-10-11 17:05:17 -04:00
Nick Sieger
8bd487ac43 docs: update with result of make docs
Signed-off-by: Nick Sieger <nick@nicksieger.com>
2022-10-11 15:19:24 -05:00
Vedant Koditkar
1d4cb32001 Add support to push images quietly via compose cli
Signed-off-by: Vedant Koditkar <vedant.koditkar@outlook.com>
2022-10-11 15:19:23 -05:00
Laura Brehm
19d1ab77eb Merge pull request #9905 from docker/dependabot/go_modules/gotest.tools/v3-3.4.0
build(deps): bump gotest.tools/v3 from 3.3.0 to 3.4.0
2022-10-10 15:32:42 +02:00
Laura Brehm
a01f62f5dc Bump e2e module deps
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-10-10 15:23:04 +02:00
dependabot[bot]
045f5ad758 build(deps): bump gotest.tools/v3 from 3.3.0 to 3.4.0
Bumps [gotest.tools/v3](https://github.com/gotestyourself/gotest.tools) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/gotestyourself/gotest.tools/releases)
- [Commits](https://github.com/gotestyourself/gotest.tools/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: gotest.tools/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-10 09:33:24 +00:00
Guillaume Lours
b6b58d26c1 don't fail when trying to remove an orphan container during down command
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-10-06 16:44:37 +02:00
Guillaume Lours
55b1b9976b Merge pull request #9894 from thaJeztah/bump_go_1.19.2
Update to go 1.19.2 to address CVE-2022-2879, CVE-2022-2880, CVE-2022-41715
2022-10-04 23:10:38 +02:00
Sebastiaan van Stijn
34441c8e4a Update to go 1.19.2 to address CVE-2022-2879, CVE-2022-2880, CVE-2022-41715
From the mailing list:

We have just released Go versions 1.19.2 and 1.18.7, minor point releases.

These minor releases include 3 security fixes following the security policy:

- archive/tar: unbounded memory consumption when reading headers

  Reader.Read did not set a limit on the maximum size of file headers.
  A maliciously crafted archive could cause Read to allocate unbounded
  amounts of memory, potentially causing resource exhaustion or panics.
  Reader.Read now limits the maximum size of header blocks to 1 MiB.

  Thanks to Adam Korczynski (ADA Logics) and OSS-Fuzz for reporting this issue.

  This is CVE-2022-2879 and Go issue https://go.dev/issue/54853.

- net/http/httputil: ReverseProxy should not forward unparseable query parameters

  Requests forwarded by ReverseProxy included the raw query parameters from the
  inbound request, including unparseable parameters rejected by net/http. This
  could permit query parameter smuggling when a Go proxy forwards a parameter
  with an unparseable value.

  ReverseProxy will now sanitize the query parameters in the forwarded query
  when the outbound request's Form field is set after the ReverseProxy.Director
  function returns, indicating that the proxy has parsed the query parameters.
  Proxies which do not parse query parameters continue to forward the original
  query parameters unchanged.

  Thanks to Gal Goldstein (Security Researcher, Oxeye) and
  Daniel Abeles (Head of Research, Oxeye) for reporting this issue.

  This is CVE-2022-2880 and Go issue https://go.dev/issue/54663.

- regexp/syntax: limit memory used by parsing regexps

  The parsed regexp representation is linear in the size of the input,
  but in some cases the constant factor can be as high as 40,000,
  making relatively small regexps consume much larger amounts of memory.

  Each regexp being parsed is now limited to a 256 MB memory footprint.
  Regular expressions whose representation would use more space than that
  are now rejected. Normal use of regular expressions is unaffected.

  Thanks to Adam Korczynski (ADA Logics) and OSS-Fuzz for reporting this issue.

  This is CVE-2022-41715 and Go issue https://go.dev/issue/55949.

View the release notes for more information: https://go.dev/doc/devel/release#go1.19.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-10-04 21:27:06 +02:00
Guillaume Lours
139a6945cb Merge pull request #9886 from milas/ci-docs-repo
ci: update docs repo path
2022-09-29 19:20:49 +02:00
Milas Bowman
97a9d02dda ci: update docs repo path
The Docker docs now live at `docker/docs` instead of
`docker/docker.github.io`.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-09-29 13:09:25 -04:00
Laura Brehm
25c4bcef85 Merge pull request #9824 from laurazard/cucumber-test
🥒 Cucumber PoC 🥒
2022-09-27 23:38:44 +02:00
Laura Brehm
4607dac19c Adjust modules sync validating script
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 15:47:00 +02:00
Milas Bowman
616777eb4a deps: fix race condition during graph traversal (#9878)
Keep track of visited nodes to prevent visiting a service multiple
times. This is possible when a service depends on multiple others,
as an attempt could be made to visit it from multiple parents.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-09-27 09:01:13 -04:00
Laura Brehm
c1f475d7bd Add validate-modules target to CI matrix
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:36:15 +02:00
Laura Brehm
c6109b2e5c Add Makefile, buildx target to ensure root and e2e go.mod are kept in sync
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:35:57 +02:00
Laura Brehm
fffe7fff57 Create new e2e module to separate out test dependencies, move cucumber tests
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:52 +02:00
Laura Brehm
0a5f4e62e4 Removed tests that were replaced by Cucumber features
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:52 +02:00
Laura Brehm
d88f6805e7 Update go.mod replace
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:52 +02:00
Laura Brehm
266ab22d53 Rename start cucumber feature
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:51 +02:00
Laura Brehm
a7476c8eeb Convert cascade_stop_test.go into a cucumber feature stop.feature
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:51 +02:00
Laura Brehm
15ebff00b1 Cucumber test setup/fixtures
(run with `go test -v -run ^TestCucumberFeatures$ github.com/docker/compose/v2/pkg/e2e/cucumber`)

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-27 02:13:47 +02:00
Alex
f44ca01fcf ci: limit job permissions from default (#9874)
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-26 15:41:24 -04:00
Guillaume Lours
19a1454c2d Merge pull request #9868 from bkielbasa/v2
add more information when `service.platform` isn't part of `service.build.platforms`
2022-09-26 21:12:45 +02:00
Bartłomiej Klimczak
aa297a9969 remove unnecessary code
Signed-off-by: Bartłomiej Klimczak <bartlomiej.klimczak88@gmail.com>
2022-09-26 20:54:33 +02:00
Bartłomiej Klimczak
0d0a02cc6b add more information when service.platform isn't part of service.build.platforms
Signed-off-by: Bartłomiej Klimczak <bartlomiej.klimczak88@gmail.com>
2022-09-26 20:44:59 +02:00
Guillaume Lours
3c641ed265 Merge pull request #9876 from milas/compose-go-1.6.0
ci: upgrade to compose-go v1.6.0
2022-09-26 19:42:19 +02:00
Milas Bowman
f41eec4e09 ci: upgrade to compose-go v1.6.0
https://github.com/compose-spec/compose-go/releases/tag/v1.6.0

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-09-26 19:23:30 +02:00
Ulysses Souza
140dc519d3 cli: add shell completion function (#9269)
Integrates PR #9462 with additional fixes/changes.

Additional changes will be required to utilize this.

Co-authored-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2022-09-26 13:21:45 -04:00
Guillaume Lours
279225896a run: clean service command if entrypoint is overridden (#9836)
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-09-26 12:08:14 -04:00
Milas Bowman
a95cc4074a Remove support for DOCKER_HOST in .env files (#9871)
Revert "Merge pull request #9817 from ulyssessouza/apply-newly-loaded-envvars"

This reverts commit 126cb988c6, reversing
changes made to b80222fb07.

Signed-off-by: Milas Bowman <milas.bowman@docker.com>
2022-09-26 09:26:01 -04:00
Guillaume Lours
b4420c372b Merge pull request #9866 from glours/issue-service-platform-on-up
keep the platform defined, in priority, via DOCKER_DEFAULT_PLATFORM o…
2022-09-26 10:32:43 +02:00
Guillaume Lours
ce3700d334 keep the platform defined, in priority, via DOCKER_DEFAULT_PLATFORM or the service.plaform one if no build platforms provided
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-09-22 13:46:24 +02:00
Guillaume Lours
e2a3fe9427 Merge pull request #9862 from glours/use-docker-export-if-no-build-platforms
configure default builder export when no build.platforms defined
2022-09-22 13:46:00 +02:00
Laura Brehm
94465d57cc Merge pull request #9863 from docker/gha-win-mac-runners
Add `merge` GitHub Actions workflow to run tests on Windows and macOS runners
2022-09-21 16:39:27 +02:00
Laura Brehm
0dc64723c9 Restore -s in uname OS detection logic in Makefile
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-21 10:19:00 -04:00
Laura Brehm
8891d9e2b5 Streamline GHA workflow
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-21 08:59:09 -04:00
Laura Brehm
6cd68a4bf2 Upgrade actions/setup-go to v3
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:31 -04:00
Laura Brehm
a1984ca1de Skip some tests in CI due to flakiness
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:31 -04:00
Laura Brehm
118b4f07e5 Increase E2E test timeouts to reduce flakiness
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:31 -04:00
Laura Brehm
8714f983ac Temporarily disable broken E2E tests on Windows
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:31 -04:00
Laura Brehm
6bc50cb457 Rework Makefile for better Windows support
Fixes error when attempting to run `uname` on Windows, and add `.exe` to built binary on `make` if on Windows

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:31 -04:00
Laura Brehm
937fa2dc8f Add GitHub Action workflow to run tests on Mac/Windows runners
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-20 11:33:28 -04:00
Guillaume Lours
71ab6c9eef configure default builder export when no build.platforms defined
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2022-09-20 15:27:41 +02:00
Laura Brehm
723078c593 Remove /rebase GitHub Action since it's no longer necessary
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2022-09-19 17:50:41 -04:00
81 changed files with 2306 additions and 1764 deletions

View File

@@ -1,64 +0,0 @@
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.
If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.
For more information about reporting issues, see
https://github.com/docker/compose-cli/blob/master/CONTRIBUTING.md#reporting-other-issues
---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------
The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:
- Docker Support Forums - https://forums.docker.com
- Docker Community Slack - https://dockr.ly/slack
- Post a question on StackOverflow, using the Docker tag
---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->
**Description**
<!--
Briefly describe the problem you are having in a few paragraphs.
-->
**Steps to reproduce the issue:**
1.
2.
3.
**Describe the results you received:**
**Describe the results you expected:**
**Additional information you deem important (e.g. issue happens only occasionally):**
**Output of `docker compose version`:**
```
(paste your output here)
```
**Output of `docker info`:**
```
(paste your output here)
```
**Additional environment details:**

49
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: 🐞 Bug
description: File a bug/issue
title: "[BUG] <title>"
labels: ['status/0-triage', 'kind/bug']
body:
- type: textarea
attributes:
label: Description
description: |
Briefly describe the problem you are having.
Include both the current behavior (what you are seeing) as well as what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: false
- type: textarea
attributes:
label: Compose Version
description: |
Paste output of `docker compose version` and `docker-compose version`.
render: Text
validations:
required: false
- type: textarea
attributes:
label: Docker Environment
description: Paste output of `docker info`.
render: Text
validations:
required: false
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: true
contact_links:
- name: Docker Community Slack
url: https://dockr.ly/slack
about: 'Use the #docker-compose channel'
- name: Docker Support Forums
url: https://forums.docker.com/c/open-source-projects/compose/15
about: 'Use the "Open Source Projects > Compose" category'
- name: 'Ask on Stack Overflow'
url: https://stackoverflow.com/questions/tagged/docker-compose
about: 'Use the [docker-compose] tag when creating new questions'

View File

@@ -0,0 +1,13 @@
name: Feature request
description: Missing functionality? Come tell us about it!
labels:
- kind/feature
- status/0-triage
body:
- type: textarea
id: description
attributes:
label: Description
description: What is the feature you want to see?
validations:
required: true

View File

@@ -22,6 +22,9 @@ env:
DESTDIR: "./bin"
DOCKER_CLI_VERSION: "20.10.17"
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
prepare:
runs-on: ubuntu-latest
@@ -49,6 +52,7 @@ jobs:
target:
- lint
- validate-go-mod
- validate-modules
- validate-headers
- validate-docs
steps:
@@ -173,6 +177,9 @@ jobs:
if: ${{ matrix.mode == 'plugin' }}
run: |
make e2e-compose
-
name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
-
name: Test standalone mode
if: ${{ matrix.mode == 'standalone' }}
@@ -182,6 +189,9 @@ jobs:
make e2e-compose-standalone
release:
permissions:
contents: write # to create a release (ncipollo/release-action)
runs-on: ubuntu-latest
needs:
- binary

View File

@@ -4,8 +4,13 @@ on:
release:
types: [published]
permissions: {}
jobs:
open-pr:
permissions:
contents: write # to create branch (peter-evans/create-pull-request)
pull-requests: write # to create a PR (peter-evans/create-pull-request)
runs-on: ubuntu-latest
steps:
-
@@ -13,7 +18,7 @@ jobs:
uses: actions/checkout@v3
with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
repository: docker/docker.github.io
repository: docker/docs
ref: master
-
name: Prepare

74
.github/workflows/merge.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: merge
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'v2'
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
e2e:
name: Build and test
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [desktop-windows, desktop-macos, desktop-m1]
# mode: [plugin, standalone]
mode: [plugin]
env:
GO111MODULE: "on"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version-file: go.mod
cache: true
check-latest: true
- name: List Docker resources on machine
run: |
docker ps --all
docker volume ls
docker network ls
docker image ls
- name: Remove Docker resources on machine
continue-on-error: true
run: |
docker kill $(docker ps -q)
docker rm -f $(docker ps -aq)
docker volume rm -f $(docker volume ls -q)
docker ps --all
- name: Unit tests
run: make test
- name: Build binaries
run: |
make
- name: Check arch of go compose binary
run: |
file ./bin/build/docker-compose
if: ${{ !contains(matrix.os, 'desktop-windows') }}
-
name: Test plugin mode
if: ${{ matrix.mode == 'plugin' }}
run: |
make e2e-compose
-
name: Test standalone mode
if: ${{ matrix.mode == 'standalone' }}
run: |
make e2e-compose-standalone

View File

@@ -1,19 +0,0 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
bin/
/.vscode/
coverage.out

View File

@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.19.1
ARG GO_VERSION=1.19.2
ARG XX_VERSION=1.1.2
ARG GOLANGCI_LINT_VERSION=v1.49.0
ARG ADDLICENSE_VERSION=v1.0.0
@@ -69,6 +69,11 @@ RUN --mount=type=bind,target=.,rw <<EOT
fi
EOT
FROM vendored AS modules-validate
RUN apk add --no-cache bash
RUN apk add --no-cache jq
RUN --mount=type=bind,target=.,rw ./verify-go-modules.sh e2e
FROM build-base AS build
ARG BUILD_TAGS
ARG TARGETPLATFORM
@@ -177,7 +182,7 @@ FROM scratch AS release
COPY --from=releaser /out/ /
# docs-reference is a target used as remote context to update docs on release
# with latest changes on docker.github.io.
# with latest changes on docs.docker.com.
# see open-pr job in .github/workflows/docs.yml for more details
FROM scratch AS docs-reference
COPY docs/reference/*.yaml .

View File

@@ -18,15 +18,23 @@ VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
GO_LDFLAGS ?= -s -w -X ${PKG}/internal.Version=${VERSION}
GO_BUILDTAGS ?= e2e,kube
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
ifeq ($(OS),Windows_NT)
DETECTED_OS = Windows
else
DETECTED_OS = $(shell uname -s)
endif
ifeq ($(DETECTED_OS),Linux)
MOBY_DOCKER=/usr/bin/docker
endif
ifeq ($(UNAME_S),Darwin)
ifeq ($(DETECTED_OS),Darwin)
MOBY_DOCKER=/Applications/Docker.app/Contents/Resources/bin/docker
endif
ifeq ($(DETECTED_OS),Windows)
BINARY_EXT=.exe
endif
TEST_FLAGS?=
TEST_COVERAGE_FLAGS = -race -coverprofile=coverage.out -covermode=atomic
TEST_FLAGS?= -timeout 15m
E2E_TEST?=
ifeq ($(E2E_TEST),)
else
@@ -40,7 +48,7 @@ all: build
.PHONY: build ## Build the compose cli-plugin
build:
CGO_ENABLED=0 GO111MODULE=on go build -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(DESTDIR)/docker-compose" ./cmd
CGO_ENABLED=0 GO111MODULE=on go build -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(DESTDIR)/docker-compose$(BINARY_EXT)" ./cmd
.PHONY: binary
binary:
@@ -54,7 +62,7 @@ install: binary
.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
go test $(TEST_FLAGS) $(TEST_COVERAGE_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
@@ -123,7 +131,11 @@ 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
$(BUILDX_CMD) bake vendor-validate
validate: validate-go-mod validate-headers validate-docs ## Validate sources
.PHONY: validate-modules
validate-modules: ## Validate root and e2e go.mod are synced
$(BUILDX_CMD) bake modules-validate
validate: validate-go-mod validate-modules validate-headers validate-docs ## Validate sources
pre-commit: validate check-dependencies lint build test e2e-compose

View File

@@ -4,6 +4,7 @@
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/compose/v2)
[![Build Status](https://img.shields.io/github/workflow/status/docker/compose/ci?label=ci&logo=github&style=flat-square)](https://github.com/docker/compose/actions?query=workflow%3Aci)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/compose/v2?style=flat-square)](https://goreportcard.com/report/github.com/docker/compose/v2)
[![Codecov](https://codecov.io/gh/docker/compose/branch/master/graph/badge.svg?token=HP3K4Y4ctu)](https://codecov.io/gh/docker/compose)
![Docker Compose](logo.png?raw=true "Docker Compose Logo")

View File

@@ -23,6 +23,13 @@ import (
"github.com/docker/compose/v2/cmd/compose"
)
func getCompletionCommands() []string {
return []string{
"__complete",
"__completeNoDesc",
}
}
func getBoolFlags() []string {
return []string{
"--debug", "-D",
@@ -50,6 +57,10 @@ func Convert(args []string) []string {
l := len(args)
for i := 0; i < l; i++ {
arg := args[i]
if contains(getCompletionCommands(), arg) {
command = append([]string{arg}, command...)
continue
}
if len(arg) > 0 && arg[0] != '-' {
// not a top-level flag anymore, keep the rest of the command unmodified
if arg == compose.PluginName {

View File

@@ -102,7 +102,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
return runBuild(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")

View File

@@ -19,6 +19,7 @@ package compose
import (
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -27,11 +28,11 @@ type validArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]s
func noCompletion() validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
return []string{}, cobra.ShellCompDirectiveNoSpace
}
}
func serviceCompletion(p *projectOptions) validArgsFn {
func completeServiceNames(p *projectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
project, err := p.toProject(nil)
if err != nil {
@@ -46,3 +47,21 @@ func serviceCompletion(p *projectOptions) validArgsFn {
return serviceNames, cobra.ShellCompDirectiveNoFileComp
}
}
func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := backend.List(cmd.Context(), api.ListOptions{
All: true,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var values []string
for _, stack := range list {
if strings.HasPrefix(stack.Name, toComplete) {
values = append(values, stack.Name)
}
}
return values, cobra.ShellCompDirectiveNoFileComp
}
}

View File

@@ -25,7 +25,6 @@ import (
"strings"
"syscall"
cnabgodocker "github.com/cnabio/cnab-go/driver/docker"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
composegoutils "github.com/compose-spec/compose-go/utils"
@@ -33,7 +32,6 @@ import (
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/client"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -293,16 +291,6 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
if err != nil {
return err
}
// Reset DockerCli and APIClient to get possible `DOCKER_HOST` and/or `DOCKER_CONTEXT` loaded from environment file.
err = dockerCli.Apply(func(cli *command.DockerCli) error {
return cli.Initialize(cnabgodocker.BuildDockerClientOptions(),
command.WithInitializeClient(func(_ *command.DockerCli) (client.APIClient, error) {
return nil, nil
}))
})
if err != nil {
return err
}
parent := cmd.Root()
if parent != nil {
parentPrerun := parent.PersistentPreRunE
@@ -370,6 +358,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
)
c.Flags().SetInterspersed(false)
opts.addProjectFlags(c.Flags())
c.RegisterFlagCompletionFunc( //nolint:errcheck
"project-name",
completeProjectNames(backend),
)
c.RegisterFlagCompletionFunc( //nolint:errcheck
"file",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
},
)
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
c.Flags().MarkHidden("version") //nolint:errcheck

View File

@@ -93,7 +93,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
return runConvert(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")

View File

@@ -60,7 +60,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
opts.destination = args[1]
return runCopy(ctx, backend, opts)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := copyCmd.Flags()

View File

@@ -70,7 +70,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
QuietPull: false,
})
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")

View File

@@ -43,7 +43,7 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")

View File

@@ -61,7 +61,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
RunE: Adapt(func(ctx context.Context, args []string) error {
return runExec(ctx, backend, opts)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")

View File

@@ -48,7 +48,7 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
return imgCmd

View File

@@ -42,7 +42,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()

View File

@@ -49,7 +49,7 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runLogs(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := logsCmd.Flags()
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")

View File

@@ -38,7 +38,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPause(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
return cmd
}
@@ -69,7 +69,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runUnPause(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
return cmd
}

View File

@@ -52,7 +52,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPort(ctx, backend, opts, args[0])
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")

View File

@@ -78,7 +78,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPs(ctx, backend, args, opts)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := psCmd.Flags()
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")

View File

@@ -54,7 +54,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPull(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")

View File

@@ -29,6 +29,7 @@ type pushOptions struct {
composeOptions
Ignorefailures bool
Quiet bool
}
func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
@@ -41,9 +42,10 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPush(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
return pushCmd
}
@@ -56,5 +58,6 @@ func runPush(ctx context.Context, backend api.Service, opts pushOptions, service
return backend.Push(ctx, project, api.PushOptions{
IgnoreFailures: opts.Ignorefailures,
Quiet: opts.Quiet,
})
}

View File

@@ -46,7 +46,7 @@ Any data which is not in a volume will be lost.`,
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRemove(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
f := cmd.Flags()
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")

View File

@@ -40,7 +40,7 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runRestart(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := restartCmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")

View File

@@ -143,7 +143,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
return runRun(ctx, backend, project, opts)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")

View File

@@ -37,7 +37,7 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStart(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
return startCmd
}

View File

@@ -44,7 +44,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runStop(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := cmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")

View File

@@ -44,7 +44,7 @@ func topCommand(p *projectOptions, backend api.Service) *cobra.Command {
RunE: Adapt(func(ctx context.Context, args []string) error {
return runTop(ctx, backend, opts, args)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
return topCmd
}

View File

@@ -109,7 +109,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
}
return runUp(ctx, backend, create, up, project, services)
}),
ValidArgsFunction: serviceCompletion(p),
ValidArgsFunction: completeServiceNames(p),
}
flags := upCmd.Flags()
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")

View File

@@ -34,14 +34,13 @@ import (
func pluginMain() {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
lazyInit := api.NewServiceProxy()
cmd := commands.RootCommand(dockerCli, lazyInit)
serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli))
cmd := commands.RootCommand(dockerCli, serviceProxy)
originalPreRun := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
return err
}
lazyInit.WithService(compose.NewComposeService(dockerCli))
if originalPreRun != nil {
return originalPreRun(cmd, args)
}

View File

@@ -13,7 +13,7 @@
// limitations under the License.
variable "GO_VERSION" {
default = "1.19.1"
default = "1.19.2"
}
variable "BUILD_TAGS" {
@@ -71,6 +71,12 @@ target "vendor-validate" {
output = ["type=cacheonly"]
}
target "modules-validate" {
inherits = ["_common"]
target = "modules-validate"
output = ["type=cacheonly"]
}
target "vendor" {
inherits = ["_common"]
target = "vendor-update"

View File

@@ -8,6 +8,7 @@ Push service images
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
| `-q`, `--quiet` | | | Push without printing progress information |
<!---MARKER_GEN_END-->

View File

@@ -33,6 +33,17 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet
shorthand: q
value_type: bool
default_value: "false"
description: Push without printing progress information
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
experimental: false
experimentalcli: false

View File

@@ -0,0 +1,5 @@
Feature: Down
Scenario: No resources to remove
When I run "compose down"
Then the output contains "Warning: No resource found to remove for project "no_resources_to_remove""

View File

@@ -0,0 +1,15 @@
Feature: Simple service up
Background:
Given a compose file
"""
services:
simple:
image: alpine
command: top
"""
Scenario: compose up
When I run "compose up -d"
Then the output contains "simple-1 Started"
And service "simple" is "running"

View File

@@ -0,0 +1,21 @@
Feature: Start
Background:
Given a compose file
"""
services:
simple:
image: alpine
command: top
another:
image: alpine
command: top
"""
Scenario: Start single service
When I run "compose create"
Then the output contains "simple-1 Created"
And the output contains "another-1 Created"
Then I run "compose start another"
And service "another" is "running"
And service "simple" is "created"

View File

@@ -0,0 +1,30 @@
Feature: Stop
Background:
Given a compose file
"""
services:
should_fail:
image: alpine
command: ls /does_not_exist
sleep: # will be killed
image: alpine
command: ping localhost
"""
Scenario: Cascade stop
When I run "compose up --abort-on-container-exit"
Then the output contains "should_fail-1 exited with code 1"
And the output contains "Aborting on container exit..."
And the exit code is 1
Scenario: Exit code from
When I run "compose up --exit-code-from sleep"
Then the output contains "should_fail-1 exited with code 1"
And the output contains "Aborting on container exit..."
And the exit code is 137
Scenario: Exit code from unknown service
When I run "compose up --exit-code-from unknown"
Then the output contains "no such service: unknown"
And the exit code is 1

129
e2e/cucumber_test.go Normal file
View File

@@ -0,0 +1,129 @@
/*
Copyright 2022 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 cucumber
import (
"context"
"fmt"
"os"
"regexp"
"strings"
"testing"
"github.com/cucumber/godog"
"github.com/cucumber/godog/colors"
"github.com/docker/compose/v2/pkg/e2e"
"gotest.tools/v3/icmd"
)
func TestCucumber(t *testing.T) {
testingOptions := godog.Options{
TestingT: t,
Paths: []string{"./cucumber-features"},
Output: colors.Colored(os.Stdout),
Format: "pretty",
}
status := godog.TestSuite{
Name: "godogs",
Options: &testingOptions,
ScenarioInitializer: setup,
}.Run()
if status == 2 {
t.SkipNow()
}
if status != 0 {
t.Fatalf("zero status code expected, %d received", status)
}
}
func setup(s *godog.ScenarioContext) {
t := s.TestingT()
cli := e2e.NewCLI(t, e2e.WithEnv(
fmt.Sprintf("COMPOSE_PROJECT_NAME=%s", strings.Split(t.Name(), "/")[1]),
))
th := testHelper{
T: t,
CLI: cli,
}
s.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
return ctx, nil
})
s.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
return ctx, nil
})
s.Step(`^a compose file$`, th.setComposeFile)
s.Step(`^I run "compose (.*)"$`, th.runComposeCommand)
s.Step(`service "(.*)" is "(.*)"$`, th.serviceIsStatus)
s.Step(`output contains "(.*)"$`, th.outputContains)
s.Step(`exit code is (\d+)$`, th.exitCodeIs)
}
type testHelper struct {
T *testing.T
ComposeFile string
CommandOutput string
CommandExitCode int
CLI *e2e.CLI
}
func (th *testHelper) serviceIsStatus(service, status string) error {
res := th.CLI.RunDockerComposeCmd(th.T, "ps", "-a")
statusRegex := fmt.Sprintf("%s\\s+%s", service, status)
r, _ := regexp.Compile(statusRegex)
if !r.MatchString(res.Combined()) {
return fmt.Errorf("Missing/incorrect ps output:\n%s", res.Combined())
}
return nil
}
func (th *testHelper) outputContains(substring string) error {
if !strings.Contains(th.CommandOutput, substring) {
return fmt.Errorf("Missing output substring: %s\noutput: %s", substring, th.CommandOutput)
}
return nil
}
func (th *testHelper) exitCodeIs(exitCode int) error {
if exitCode != th.CommandExitCode {
return fmt.Errorf("Wrong exit code: %d expected: %d", th.CommandExitCode, exitCode)
}
return nil
}
func (th *testHelper) runComposeCommand(command string) error {
commandArgs := []string{"-f", "-"}
commandArgs = append(commandArgs, strings.Split(command, " ")...)
cmd := th.CLI.NewDockerComposeCmd(th.T, commandArgs...)
cmd.Stdin = strings.NewReader(th.ComposeFile)
res := icmd.RunCmd(cmd)
th.CommandOutput = res.Combined()
th.CommandExitCode = res.ExitCode
return nil
}
func (th *testHelper) setComposeFile(composeString string) error {
th.ComposeFile = composeString
return nil
}

171
e2e/go.mod Normal file
View File

@@ -0,0 +1,171 @@
module github.com/docker/compose/v2/e2e
go 1.19
replace github.com/docker/compose/v2 => ../
replace github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
require (
github.com/cucumber/godog v0.12.5
github.com/docker/compose/v2 v2.11.1
gotest.tools/v3 v3.4.0
)
require (
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/goterm v1.0.4 // indirect
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cnabio/cnab-go v0.23.4 // indirect
github.com/cnabio/cnab-to-oci v0.3.7 // indirect
github.com/compose-spec/compose-go v1.6.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/containerd/containerd v1.6.8 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/ttrpc v1.1.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31 // indirect
github.com/docker/buildx v0.9.1 // indirect
github.com/docker/cli v20.10.19+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.19+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fvbommel/sortorder v1.0.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofrs/flock v0.8.0 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gnostic v0.5.5 // 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/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-memdb v1.3.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // 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.15.9 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/buildkit v0.10.4 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 // indirect
github.com/opencontainers/runc v1.1.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.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.1 // indirect
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cobra v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
github.com/weppos/publicsuffix-go v0.20.0 // 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/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.11.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp 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.11.0 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
google.golang.org/grpc v1.47.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.1 // indirect
k8s.io/api v0.24.1 // indirect
k8s.io/apimachinery v0.24.1 // indirect
k8s.io/client-go v0.24.1 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
replace (
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221013203545-33ab36d6b304+incompatible // 22.06 branch
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
// 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
)

1041
e2e/go.sum Normal file

File diff suppressed because it is too large Load Diff

53
go.mod
View File

@@ -5,16 +5,15 @@ go 1.19
require (
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/buger/goterm v1.0.4
github.com/cnabio/cnab-go v0.24.1-0.20220907172316-1ca5c8721bf7
github.com/cnabio/cnab-to-oci v0.3.7
github.com/compose-spec/compose-go v1.5.1
github.com/compose-spec/compose-go v1.6.0
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.8
github.com/distribution/distribution/v3 v3.0.0-20220902125104-0122d7ddaec0
github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
github.com/docker/cli v20.10.17+incompatible
github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31
github.com/docker/buildx v0.9.1 // when updating, also update the replace rules accordingly
github.com/docker/cli v20.10.19+incompatible
github.com/docker/cli-docs-tool v0.5.0
github.com/docker/docker v20.10.17+incompatible
github.com/docker/docker v20.10.19+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.0
github.com/golang/mock v1.6.0
@@ -26,17 +25,17 @@ require (
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/spf13/cobra v1.6.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
github.com/theupdateframework/notary v0.7.0
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
gopkg.in/yaml.v2 v2.4.0
gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.3.0
gotest.tools/v3 v3.4.0
)
require (
@@ -45,7 +44,8 @@ require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8 // indirect
github.com/cnabio/cnab-go v0.23.4 // indirect
github.com/containerd/continuity v0.3.0 // 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
@@ -69,30 +69,30 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // 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.15.1 // indirect
github.com/klauspost/compress v1.15.9 // 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/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/signal v0.6.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/runc v1.1.2 // indirect
github.com/opencontainers/runc v1.1.3 // 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.12.1 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.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.1 // indirect
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // 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
@@ -101,14 +101,14 @@ require (
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.10.0
go.opentelemetry.io/otel v1.11.0
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.10.0 // indirect
go.opentelemetry.io/otel/trace v1.11.0 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
@@ -117,7 +117,7 @@ require (
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -133,9 +133,11 @@ require (
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
github.com/zmap/zlint v1.1.0 // indirect
@@ -146,8 +148,9 @@ require (
)
replace (
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
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221013203545-33ab36d6b304+incompatible // 22.06 branch
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4

1347
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -183,6 +183,7 @@ type ConvertOptions struct {
// PushOptions group options of the Push API
type PushOptions struct {
Quiet bool
IgnoreFailures bool
}

View File

@@ -27,7 +27,7 @@ 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/docker/pkg/urlutil"
"github.com/docker/docker/builder/remotecontext/urlutil"
bclient "github.com/moby/buildkit/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
@@ -81,6 +81,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
Attrs: map[string]string{"ref": image},
})
}
buildOptions.Exports = []bclient.ExportEntry{{
Type: "docker",
Attrs: map[string]string{
"load": "true",
},
}}
if len(buildOptions.Platforms) > 1 {
buildOptions.Exports = []bclient.ExportEntry{{
Type: "image",
@@ -173,7 +179,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
"load": "true",
},
}}
if opt.Platforms, err = useDockerDefaultPlatform(project, service.Build.Platforms); err != nil {
if opt.Platforms, err = useDockerDefaultOrServicePlatform(project, service, true); err != nil {
opt.Platforms = []specs.Platform{}
}
opts[imageName] = opt
@@ -243,7 +249,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
}
sessionConfig := []session.Attachable{
authprovider.NewDockerAuthProvider(s.stderr()),
authprovider.NewDockerAuthProvider(s.configFile()),
}
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
@@ -357,23 +363,11 @@ func addSecretsConfig(project *types.Project, service types.ServiceConfig) (sess
}
func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.Platform, error) {
plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
plats, err := useDockerDefaultOrServicePlatform(project, service, false)
if err != nil {
return nil, err
}
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
if len(service.Build.Platforms) > 0 {
return nil, fmt.Errorf("service.platform should be part of the service.build.platforms: %q", service.Platform)
}
// User defined a service platform and no build platforms, so we should keep the one define on the service level
p, err := platforms.Parse(service.Platform)
if !utils.Contains(plats, p) {
plats = append(plats, p)
}
return plats, err
}
for _, buildPlatform := range service.Build.Platforms {
p, err := platforms.Parse(buildPlatform)
if err != nil {
@@ -404,7 +398,7 @@ func useDockerDefaultPlatform(project *types.Project, platformList types.StringL
var plats []specs.Platform
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
if len(platformList) > 0 && !utils.StringContains(platformList, platform) {
return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: %q", platform)
return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM %q value should be part of the service.build.platforms: %q", platform, platformList)
}
p, err := platforms.Parse(platform)
if err != nil {
@@ -414,3 +408,23 @@ func useDockerDefaultPlatform(project *types.Project, platformList types.StringL
}
return plats, nil
}
func useDockerDefaultOrServicePlatform(project *types.Project, service types.ServiceConfig, useOnePlatform bool) ([]specs.Platform, error) {
plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
if (len(plats) > 0 && useOnePlatform) || err != nil {
return plats, err
}
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
if len(service.Build.Platforms) > 0 {
return nil, fmt.Errorf("service.platform %q should be part of the service.build.platforms: %q", service.Platform, service.Build.Platforms)
}
// User defined a service platform and no build platforms, so we should keep the one define on the service level
p, err := platforms.Parse(service.Platform)
if !utils.Contains(plats, p) {
plats = append(plats, p)
}
return plats, err
}
return plats, nil
}

View File

@@ -95,7 +95,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
dis := make([]build.DriverInfo, len(ng.Nodes))
var f driver.Factory
if ng.Driver != "" {
factories := driver.GetFactories()
factories := driver.GetFactories(true)
for _, fac := range factories {
if fac.Name() == ng.Driver {
f = fac
@@ -103,8 +103,8 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
}
}
if f == nil {
if f = driver.GetFactory(ng.Driver, true); f == nil {
return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
if f, err = driver.GetFactory(ng.Driver, true); f == nil || err != nil {
return nil, fmt.Errorf("failed to find buildx driver %q, error: %w", ng.Driver, err)
}
}
} else {
@@ -113,7 +113,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
if err != nil {
return nil, err
}
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
if err != nil {
return nil, err
}
@@ -176,7 +176,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
}
}
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
if err != nil {
di.Err = err
return nil

View File

@@ -30,13 +30,13 @@ import (
buildx "github.com/docker/buildx/build"
"github.com/docker/cli/cli/command/image/build"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"

View File

@@ -27,6 +27,7 @@ import (
"github.com/compose-spec/compose-go/types"
"github.com/containerd/containerd/platforms"
moby "github.com/docker/docker/api/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
@@ -180,7 +181,10 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
// Scale Down
container := container
eg.Go(func() error {
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
timeoutInSecond := utils.DurationSecondToInt(timeout)
err := c.service.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{
Timeout: timeoutInSecond,
})
if err != nil {
return err
}
@@ -412,7 +416,8 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
var created moby.Container
w := progress.ContextWriter(ctx)
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
timeoutInSecond := utils.DurationSecondToInt(timeout)
err := s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
if err != nil {
return created, err
}

View File

@@ -1155,7 +1155,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName))
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
_, err := s.apiClient().VolumeCreate(ctx, volume_api.CreateOptions{
Labels: volume.Labels,
Name: volume.Name,
Driver: volume.Driver,

View File

@@ -37,38 +37,49 @@ const (
ServiceStarted
)
type graphTraversalConfig struct {
type graphTraversal struct {
mu sync.Mutex
seen map[string]struct{}
extremityNodesFn func(*Graph) []*Vertex // leaves or roots
adjacentNodesFn func(*Vertex) []*Vertex // getParents or getChildren
filterAdjacentByStatusFn func(*Graph, string, ServiceStatus) []*Vertex // filterChildren or filterParents
targetServiceStatus ServiceStatus
adjacentServiceStatusToSkip ServiceStatus
visitorFn func(context.Context, string) error
}
var (
upDirectionTraversalConfig = graphTraversalConfig{
func upDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
return &graphTraversal{
extremityNodesFn: leaves,
adjacentNodesFn: getParents,
filterAdjacentByStatusFn: filterChildren,
adjacentServiceStatusToSkip: ServiceStopped,
targetServiceStatus: ServiceStarted,
visitorFn: visitorFn,
}
downDirectionTraversalConfig = graphTraversalConfig{
}
func downDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
return &graphTraversal{
extremityNodesFn: roots,
adjacentNodesFn: getChildren,
filterAdjacentByStatusFn: filterParents,
adjacentServiceStatusToSkip: ServiceStarted,
targetServiceStatus: ServiceStopped,
visitorFn: visitorFn,
}
)
}
// InDependencyOrder applies the function to the services of the project taking in account the dependency order
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversalConfig)) error {
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversal)) error {
graph, err := NewGraph(project.Services, ServiceStopped)
if err != nil {
return err
}
return visit(ctx, graph, upDirectionTraversalConfig, fn)
t := upDirectionTraversal(fn)
return t.visit(ctx, graph)
}
// InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
@@ -77,43 +88,59 @@ func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn fu
if err != nil {
return err
}
return visit(ctx, graph, downDirectionTraversalConfig, fn)
t := downDirectionTraversal(fn)
return t.visit(ctx, graph)
}
func visit(ctx context.Context, g *Graph, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
nodes := traversalConfig.extremityNodesFn(g)
func (t *graphTraversal) visit(ctx context.Context, g *Graph) error {
nodes := t.extremityNodesFn(g)
eg, _ := errgroup.WithContext(ctx)
eg.Go(func() error {
return run(ctx, g, eg, nodes, traversalConfig, fn)
})
eg, ctx := errgroup.WithContext(ctx)
t.run(ctx, g, eg, nodes)
return eg.Wait()
}
// Note: this could be `graph.walk` or whatever
func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
func (t *graphTraversal) run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex) {
for _, node := range nodes {
// Don't start this service yet if all of its children have
// not been started yet.
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
if len(t.filterAdjacentByStatusFn(graph, node.Key, t.adjacentServiceStatusToSkip)) != 0 {
continue
}
node := node
if !t.consume(node.Key) {
// another worker already visited this node
continue
}
eg.Go(func() error {
err := fn(ctx, node.Service)
err := t.visitorFn(ctx, node.Service)
if err != nil {
return err
}
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
graph.UpdateStatus(node.Key, t.targetServiceStatus)
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
t.run(ctx, graph, eg, t.adjacentNodesFn(node))
return nil
})
}
}
return nil
func (t *graphTraversal) consume(nodeKey string) bool {
t.mu.Lock()
defer t.mu.Unlock()
if t.seen == nil {
t.seen = make(map[string]struct{})
}
if _, ok := t.seen[nodeKey]; ok {
return false
}
t.seen[nodeKey] = struct{}{}
return true
}
// Graph represents project as service dependencies

View File

@@ -22,6 +22,7 @@ import (
"testing"
"github.com/compose-spec/compose-go/types"
testify "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gotest.tools/assert"
)
@@ -46,6 +47,51 @@ var project = types.Project{
},
}
func TestTraversalWithMultipleParents(t *testing.T) {
dependent := types.ServiceConfig{
Name: "dependent",
DependsOn: make(types.DependsOnConfig),
}
project := types.Project{
Services: []types.ServiceConfig{dependent},
}
for i := 1; i <= 100; i++ {
name := fmt.Sprintf("svc_%d", i)
dependent.DependsOn[name] = types.ServiceDependency{}
svc := types.ServiceConfig{Name: name}
project.Services = append(project.Services, svc)
}
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
svc := make(chan string, 10)
seen := make(map[string]int)
done := make(chan struct{})
go func() {
for service := range svc {
seen[service]++
}
done <- struct{}{}
}()
err := InDependencyOrder(ctx, &project, func(ctx context.Context, service string) error {
svc <- service
return nil
})
require.NoError(t, err, "Error during iteration")
close(svc)
<-done
testify.Len(t, seen, 101)
for svc, count := range seen {
assert.Equal(t, 1, count, "Service: %s", svc)
}
}
func TestInDependencyUpCommandOrder(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

View File

@@ -22,8 +22,11 @@ import (
"strings"
"time"
"github.com/docker/compose/v2/pkg/utils"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
@@ -241,7 +244,8 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
eg.Go(func() error {
eventName := getContainerProgressName(container)
w.Event(progress.StoppingEvent(eventName))
err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
timeoutInSecond := utils.DurationSecondToInt(timeout)
err := s.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{Timeout: timeoutInSecond})
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err
@@ -270,7 +274,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
Force: true,
RemoveVolumes: volumes,
})
if err != nil {
if err != nil && !errdefs.IsNotFound(err) {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
return err
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
@@ -53,7 +54,7 @@ func TestDown(t *testing.T) {
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
Return(volume.ListResponse{}, nil)
// network names are not guaranteed to be unique, ensure Compose handles
// cleanup properly if duplicates are inadvertently created
@@ -63,9 +64,10 @@ func TestDown(t *testing.T) {
{ID: "def456", Name: "myProject_default"},
}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
stopOptions := containerType.StopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
@@ -102,13 +104,14 @@ func TestDownRemoveOrphans(t *testing.T) {
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
Return(volume.ListResponse{}, 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)
api.EXPECT().ContainerStop(gomock.Any(), "321", nil).Return(nil)
stopOptions := containerType.StopOptions{}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "321", stopOptions).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
@@ -137,13 +140,13 @@ func TestDownRemoveVolumes(t *testing.T) {
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]moby.Container{testContainer("service1", "123", false)}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
Return(volume.ListResponse{
Volumes: []*volume.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().ContainerStop(gomock.Any(), "123", containerType.StopOptions{}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
@@ -274,8 +277,8 @@ func TestDownRemoveImages_NoLabel(t *testing.T) {
[]moby.Container{container}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
Return(volume.ListResponse{
Volumes: []*volume.Volume{{Name: "myProject_volume"}},
}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return(nil, nil)
@@ -292,7 +295,7 @@ func TestDownRemoveImages_NoLabel(t *testing.T) {
api.EXPECT().ImageInspectWithRaw(gomock.Any(), "testproject-service1").
Return(moby.ImageInspect{}, nil, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", containerType.StopOptions{}).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
api.EXPECT().ImageRemove(gomock.Any(), "testproject-service1:latest", moby.ImageRemoveOptions{}).Return(nil, nil)

View File

@@ -54,7 +54,7 @@ func TestKillAll(t *testing.T) {
}).Return(
[]moby.Container{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]moby.NetworkResource{
{ID: "abc123", Name: "testProject_default"},
@@ -87,7 +87,7 @@ func TestKillSignal(t *testing.T) {
ctx := context.Background()
api.EXPECT().ContainerList(ctx, listOptions).Return([]moby.Container{testContainer(serviceName, "123", false)}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]moby.NetworkResource{
{ID: "abc123", Name: "testProject_default"},

View File

@@ -40,7 +40,7 @@ func (s *composeService) Port(ctx context.Context, projectName string, service s
return "", 0, err
}
if len(list) == 0 {
return "", 0, fmt.Errorf("no container found for %s_%d", service, options.Index)
return "", 0, fmt.Errorf("no container found for %s%s%d", service, api.Separator, options.Index)
}
container := list[0]
for _, p := range container.Ports {

View File

@@ -51,7 +51,7 @@ func TestPs(t *testing.T) {
c2, inspect2 := containerDetails("service1", "456", "running", "", 0)
c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c3, inspect3 := containerDetails("service2", "789", "exited", "", 130)
api.EXPECT().VolumeList(ctx, gomock.Any()).Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().VolumeList(ctx, gomock.Any()).Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(ctx, gomock.Any()).Return([]moby.NetworkResource{}, nil)
api.EXPECT().ContainerList(ctx, listOpts).Return([]moby.Container{c1, c2, c3}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)

View File

@@ -37,6 +37,9 @@ import (
)
func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
if options.Quiet {
return s.push(ctx, project, options)
}
return progress.Run(ctx, func(ctx context.Context) error {
return s.push(ctx, project, options)
})
@@ -65,7 +68,7 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
}
service := service
eg.Go(func() error {
err := s.pushServiceImage(ctx, service, info, s.configFile(), w)
err := s.pushServiceImage(ctx, service, info, s.configFile(), w, options.Quiet)
if err != nil {
if !options.IgnoreFailures {
return err
@@ -78,7 +81,7 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
return eg.Wait()
}
func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer) error {
func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
ref, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return err
@@ -121,7 +124,10 @@ func (s *composeService) pushServiceImage(ctx context.Context, service types.Ser
if jm.Error != nil {
return errors.New(jm.Error.Message)
}
toPushProgressEvent(service.Name, jm, w)
if !quietPush {
toPushProgressEvent(service.Name, jm, w)
}
}
return nil
}

View File

@@ -21,10 +21,10 @@ import (
"strings"
"github.com/docker/compose/v2/pkg/api"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
containerType "github.com/docker/docker/api/types/container"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
@@ -62,7 +62,8 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
eg.Go(func() error {
eventName := getContainerProgressName(container)
w.Event(progress.RestartingEvent(eventName))
err := s.apiClient().ContainerRestart(ctx, container.ID, options.Timeout)
timeout := utils.DurationSecondToInt(options.Timeout)
err := s.apiClient().ContainerRestart(ctx, container.ID, containerType.StopOptions{Timeout: timeout})
if err == nil {
w.Event(progress.StartedEvent(eventName))
}

View File

@@ -112,6 +112,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
}
if opts.Entrypoint != nil {
service.Entrypoint = opts.Entrypoint
if len(opts.Command) == 0 {
service.Command = []string{}
}
}
if len(opts.Environment) > 0 {
cmdEnv := types.NewMappingWithEquals(opts.Environment)

View File

@@ -22,8 +22,11 @@ import (
"testing"
"time"
"github.com/docker/compose/v2/pkg/utils"
compose "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
containerType "github.com/docker/docker/api/types/container"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
@@ -51,14 +54,15 @@ func TestStopTimeout(t *testing.T) {
testContainer("service2", "789", false),
}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{}, nil)
Return(volume.ListResponse{}, nil)
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
Return([]moby.NetworkResource{}, nil)
timeout := time.Duration(2) * time.Second
api.EXPECT().ContainerStop(gomock.Any(), "123", &timeout).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
timeout := 2 * time.Second
stopConfig := containerType.StopOptions{Timeout: utils.DurationSecondToInt(&timeout)}
api.EXPECT().ContainerStop(gomock.Any(), "123", stopConfig).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", stopConfig).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", stopConfig).Return(nil)
err := tested.Stop(ctx, strings.ToLower(testProject), compose.StopOptions{
Timeout: &timeout,

View File

@@ -1,66 +0,0 @@
/*
Copyright 2022 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 (
"bytes"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type lockedBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (l *lockedBuffer) Read(p []byte) (n int, err error) {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.Read(p)
}
func (l *lockedBuffer) Write(p []byte) (n int, err error) {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.Write(p)
}
func (l *lockedBuffer) String() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.String()
}
func (l *lockedBuffer) RequireEventuallyContains(t testing.TB, v string) {
t.Helper()
var bufContents strings.Builder
require.Eventuallyf(t, func() bool {
l.mu.Lock()
defer l.mu.Unlock()
if _, err := l.buf.WriteTo(&bufContents); err != nil {
require.FailNowf(t, "Failed to copy from buffer",
"Error: %v", err)
}
return strings.Contains(bufContents.String(), v)
}, 2*time.Second, 20*time.Millisecond,
"Buffer did not contain %q\n============\n%s\n============",
v, &bufContents)
}

View File

@@ -18,6 +18,7 @@ package e2e
import (
"net/http"
"runtime"
"strings"
"testing"
"time"
@@ -85,6 +86,51 @@ func TestLocalComposeBuild(t *testing.T) {
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
})
t.Run("build as part of up", func(t *testing.T) {
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
})
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second)
assert.Assert(t, strings.Contains(output, "Hello from Nginx container"))
c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
})
t.Run("no rebuild when up again", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
})
t.Run("rebuild when up --build", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "up", "-d", "--build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
})
t.Run("cleanup build project", func(t *testing.T) {
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
c.RunDockerCmd(t, "rmi", "build-test-nginx")
c.RunDockerCmd(t, "rmi", "custom-nginx")
})
}
func TestBuildSSH(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Running on Windows. Skipping...")
}
c := NewParallelCLI(t)
t.Run("build failed with ssh default value", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test", "build", "--ssh", "")
res.Assert(t, icmd.Expected{
@@ -130,47 +176,12 @@ func TestLocalComposeBuild(t *testing.T) {
})
c.RunDockerCmd(t, "image", "inspect", "build-test-ssh")
})
t.Run("build as part of up", func(t *testing.T) {
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
})
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second)
assert.Assert(t, strings.Contains(output, "Hello from Nginx container"))
c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
})
t.Run("no rebuild when up again", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
})
t.Run("rebuild when up --build", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "up", "-d", "--build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
})
t.Run("cleanup build project", func(t *testing.T) {
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
c.RunDockerCmd(t, "rmi", "build-test-nginx")
c.RunDockerCmd(t, "rmi", "custom-nginx")
})
}
func TestBuildSecrets(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping test on windows")
}
c := NewParallelCLI(t)
t.Run("build with secrets", func(t *testing.T) {
@@ -259,6 +270,9 @@ func TestBuildImageDependencies(t *testing.T) {
}
func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Running on Windows. Skipping...")
}
c := NewParallelCLI(t)
// declare builder
@@ -352,7 +366,7 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
"-f", "fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml", "build")
res.Assert(t, icmd.Expected{
ExitCode: 1,
Err: `service.platform should be part of the service.build.platforms: "linux/riscv64"`,
Err: `service.platform "linux/riscv64" should be part of the service.build.platforms: ["linux/amd64" "linux/arm64"]`,
})
})
@@ -363,7 +377,7 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
})
res.Assert(t, icmd.Expected{
ExitCode: 1,
Err: `DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: "windows/amd64"`,
Err: `DOCKER_DEFAULT_PLATFORM "windows/amd64" value should be part of the service.build.platforms: ["linux/amd64" "linux/arm64"]`,
})
})
}

View File

@@ -20,7 +20,7 @@
package e2e
import (
"bytes"
"context"
"fmt"
"os/exec"
"strings"
@@ -28,6 +28,7 @@ import (
"testing"
"time"
"github.com/docker/compose/v2/pkg/utils"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
)
@@ -41,8 +42,12 @@ func TestComposeCancel(t *testing.T) {
// require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal.
// sending kill signal
cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build",
"--progress", "plain"))
stdout := &utils.SafeBuffer{}
stderr := &utils.SafeBuffer{}
cmd, err := StartWithNewGroupID(context.Background(),
c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build", "--progress", "plain"),
stdout,
stderr)
assert.NilError(t, err)
c.WaitForCondition(t, func() (bool, string) {
@@ -65,15 +70,16 @@ func TestComposeCancel(t *testing.T) {
})
}
func StartWithNewGroupID(command icmd.Cmd) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
cmd := exec.Command(command.Command[0], command.Command[1:]...)
func StartWithNewGroupID(ctx context.Context, command icmd.Cmd, stdout *utils.SafeBuffer, stderr *utils.SafeBuffer) (*exec.Cmd, error) {
cmd := exec.CommandContext(ctx, command.Command[0], command.Command[1:]...)
cmd.Env = command.Env
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if stdout != nil {
cmd.Stdout = stdout
}
if stderr != nil {
cmd.Stderr = stderr
}
err := cmd.Start()
return cmd, &stdout, &stderr, err
return cmd, err
}

View File

@@ -1,50 +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 e2e
import (
"testing"
"gotest.tools/v3/icmd"
)
func TestCascadeStop(t *testing.T) {
c := NewParallelCLI(t)
const projectName = "e2e-cascade-stop"
t.Run("abort-on-container-exit", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
res.Assert(t, icmd.Expected{ExitCode: 1, Out: `should_fail-1 exited with code 1`})
res.Assert(t, icmd.Expected{ExitCode: 1, Out: `Aborting on container exit...`})
})
t.Run("exit-code-from", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=sleep")
res.Assert(t, icmd.Expected{ExitCode: 137, Out: `should_fail-1 exited with code 1`})
res.Assert(t, icmd.Expected{ExitCode: 137, Out: `Aborting on container exit...`})
})
t.Run("exit-code-from unknown", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=unknown")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `no such service: unknown`})
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
}

View File

@@ -135,6 +135,9 @@ func TestDownComposefileInParentFolder(t *testing.T) {
}
func TestAttachRestart(t *testing.T) {
if _, ok := os.LookupEnv("CI"); ok {
t.Skip("Skipping test on CI... flaky")
}
c := NewParallelCLI(t)
cmd := c.NewDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/attach-restart", "up")
@@ -146,7 +149,7 @@ func TestAttachRestart(t *testing.T) {
return strings.Count(res.Stdout(),
"failing-1 exited with code 1") == 3, fmt.Sprintf("'failing-1 exited with code 1' not found 3 times in : \n%s\n",
debug)
}, 2*time.Minute, 2*time.Second)
}, 4*time.Minute, 2*time.Second)
assert.Equal(t, strings.Count(res.Stdout(), "failing-1 | world"), 3, res.Combined())
}

View File

@@ -1,7 +0,0 @@
services:
should_fail:
image: alpine
command: ls /does_not_exist
sleep: # will be killed
image: alpine
command: ping localhost

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"net"
"net/http"
"os"
"testing"
"time"
@@ -29,6 +30,9 @@ import (
)
func TestPause(t *testing.T) {
if _, ok := os.LookupEnv("CI"); ok {
t.Skip("Skipping test on CI... flaky")
}
cli := NewParallelCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-pause",
"COMPOSE_FILE=./fixtures/pause/compose.yaml"))
@@ -46,7 +50,7 @@ func TestPause(t *testing.T) {
"b": urlForService(t, cli, "b", 80),
}
for _, url := range urls {
HTTPGetWithRetry(t, url, http.StatusOK, 50*time.Millisecond, 5*time.Second)
HTTPGetWithRetry(t, url, http.StatusOK, 50*time.Millisecond, 20*time.Second)
}
// pause a and verify that it can no longer be hit but b still can
@@ -98,7 +102,7 @@ func TestPauseServiceAlreadyPaused(t *testing.T) {
// launch a and wait for it to come up
cli.RunDockerComposeCmd(t, "up", "-d", "a")
HTTPGetWithRetry(t, urlForService(t, cli, "a", 80), http.StatusOK, 50*time.Millisecond, 5*time.Second)
HTTPGetWithRetry(t, urlForService(t, cli, "a", 80), http.StatusOK, 50*time.Millisecond, 10*time.Second)
// pause a twice - first time should pass, second time fail
cli.RunDockerComposeCmd(t, "pause", "a")

View File

@@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
/*
Copyright 2022 Docker Compose CLI authors
@@ -23,6 +26,7 @@ import (
"testing"
"time"
"github.com/docker/compose/v2/pkg/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gotest.tools/v3/icmd"
@@ -49,9 +53,6 @@ func TestUpDependenciesNotStopped(t *testing.T) {
reset()
t.Cleanup(reset)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
t.Log("Launching orphan container (background)")
c.RunDockerComposeCmd(t,
"-f=./fixtures/ups-deps-stop/orphan.yaml",
@@ -63,22 +64,18 @@ func TestUpDependenciesNotStopped(t *testing.T) {
RequireServiceState(t, c, "orphan", "running")
t.Log("Launching app container with implicit dependency")
var upOut lockedBuffer
var upCmd *exec.Cmd
go func() {
testCmd := c.NewDockerComposeCmd(t,
"-f=./fixtures/ups-deps-stop/compose.yaml",
"up",
"app",
)
cmd := exec.CommandContext(ctx, testCmd.Command[0], testCmd.Command[1:]...)
cmd.Env = testCmd.Env
cmd.Stdout = &upOut
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
upOut := &utils.SafeBuffer{}
testCmd := c.NewDockerComposeCmd(t,
"-f=./fixtures/ups-deps-stop/compose.yaml",
"up",
"app",
)
assert.NoError(t, cmd.Start(), "Failed to run compose up")
upCmd = cmd
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cmd, err := StartWithNewGroupID(ctx, testCmd, upOut, nil)
assert.NoError(t, err, "Failed to run compose up")
t.Log("Waiting for containers to be in running state")
upOut.RequireEventuallyContains(t, "hello app")
@@ -86,13 +83,13 @@ func TestUpDependenciesNotStopped(t *testing.T) {
RequireServiceState(t, c, "dependency", "running")
t.Log("Simulating Ctrl-C")
require.NoError(t, syscall.Kill(-upCmd.Process.Pid, syscall.SIGINT),
require.NoError(t, syscall.Kill(-cmd.Process.Pid, syscall.SIGINT),
"Failed to send SIGINT to compose up process")
time.AfterFunc(5*time.Second, cancel)
t.Log("Waiting for `compose up` to exit")
err := upCmd.Wait()
err = cmd.Wait()
if err != nil {
exitErr := err.(*exec.ExitError)
require.EqualValues(t, exitErr.ExitCode(), 130)

View File

@@ -20,6 +20,7 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
@@ -99,6 +100,9 @@ func TestProjectVolumeBind(t *testing.T) {
const projectName = "compose-e2e-project-volume-bind"
t.Run("up on project volume with bind specification", func(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Running on Windows. Skipping...")
}
tmpDir, err := os.MkdirTemp("", projectName)
assert.NilError(t, err)
defer os.RemoveAll(tmpDir) //nolint

View File

@@ -10,7 +10,6 @@ import (
net "net"
http "net/http"
reflect "reflect"
time "time"
types "github.com/docker/docker/api/types"
container "github.com/docker/docker/api/types/container"
@@ -253,10 +252,10 @@ func (mr *MockAPIClientMockRecorder) ContainerCommit(arg0, arg1, arg2 interface{
}
// ContainerCreate mocks base method.
func (m *MockAPIClient) ContainerCreate(arg0 context.Context, arg1 *container.Config, arg2 *container.HostConfig, arg3 *network.NetworkingConfig, arg4 *v1.Platform, arg5 string) (container.ContainerCreateCreatedBody, error) {
func (m *MockAPIClient) ContainerCreate(arg0 context.Context, arg1 *container.Config, arg2 *container.HostConfig, arg3 *network.NetworkingConfig, arg4 *v1.Platform, arg5 string) (container.CreateResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ContainerCreate", arg0, arg1, arg2, arg3, arg4, arg5)
ret0, _ := ret[0].(container.ContainerCreateCreatedBody)
ret0, _ := ret[0].(container.CreateResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -502,7 +501,7 @@ func (mr *MockAPIClientMockRecorder) ContainerResize(arg0, arg1, arg2 interface{
}
// ContainerRestart mocks base method.
func (m *MockAPIClient) ContainerRestart(arg0 context.Context, arg1 string, arg2 *time.Duration) error {
func (m *MockAPIClient) ContainerRestart(arg0 context.Context, arg1 string, arg2 container.StopOptions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ContainerRestart", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
@@ -575,7 +574,7 @@ func (mr *MockAPIClientMockRecorder) ContainerStatsOneShot(arg0, arg1 interface{
}
// ContainerStop mocks base method.
func (m *MockAPIClient) ContainerStop(arg0 context.Context, arg1 string, arg2 *time.Duration) error {
func (m *MockAPIClient) ContainerStop(arg0 context.Context, arg1 string, arg2 container.StopOptions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ContainerStop", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
@@ -633,10 +632,10 @@ func (mr *MockAPIClientMockRecorder) ContainerUpdate(arg0, arg1, arg2 interface{
}
// ContainerWait mocks base method.
func (m *MockAPIClient) ContainerWait(arg0 context.Context, arg1 string, arg2 container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
func (m *MockAPIClient) ContainerWait(arg0 context.Context, arg1 string, arg2 container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ContainerWait", arg0, arg1, arg2)
ret0, _ := ret[0].(<-chan container.ContainerWaitOKBody)
ret0, _ := ret[0].(<-chan container.WaitResponse)
ret1, _ := ret[1].(<-chan error)
return ret0, ret1
}
@@ -1723,10 +1722,10 @@ func (mr *MockAPIClientMockRecorder) TaskLogs(arg0, arg1, arg2 interface{}) *gom
}
// VolumeCreate mocks base method.
func (m *MockAPIClient) VolumeCreate(arg0 context.Context, arg1 volume.VolumeCreateBody) (types.Volume, error) {
func (m *MockAPIClient) VolumeCreate(arg0 context.Context, arg1 volume.CreateOptions) (volume.Volume, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VolumeCreate", arg0, arg1)
ret0, _ := ret[0].(types.Volume)
ret0, _ := ret[0].(volume.Volume)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -1738,10 +1737,10 @@ func (mr *MockAPIClientMockRecorder) VolumeCreate(arg0, arg1 interface{}) *gomoc
}
// VolumeInspect mocks base method.
func (m *MockAPIClient) VolumeInspect(arg0 context.Context, arg1 string) (types.Volume, error) {
func (m *MockAPIClient) VolumeInspect(arg0 context.Context, arg1 string) (volume.Volume, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VolumeInspect", arg0, arg1)
ret0, _ := ret[0].(types.Volume)
ret0, _ := ret[0].(volume.Volume)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -1753,10 +1752,10 @@ func (mr *MockAPIClientMockRecorder) VolumeInspect(arg0, arg1 interface{}) *gomo
}
// VolumeInspectWithRaw mocks base method.
func (m *MockAPIClient) VolumeInspectWithRaw(arg0 context.Context, arg1 string) (types.Volume, []byte, error) {
func (m *MockAPIClient) VolumeInspectWithRaw(arg0 context.Context, arg1 string) (volume.Volume, []byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VolumeInspectWithRaw", arg0, arg1)
ret0, _ := ret[0].(types.Volume)
ret0, _ := ret[0].(volume.Volume)
ret1, _ := ret[1].([]byte)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
@@ -1769,10 +1768,10 @@ func (mr *MockAPIClientMockRecorder) VolumeInspectWithRaw(arg0, arg1 interface{}
}
// VolumeList mocks base method.
func (m *MockAPIClient) VolumeList(arg0 context.Context, arg1 filters.Args) (volume.VolumeListOKBody, error) {
func (m *MockAPIClient) VolumeList(arg0 context.Context, arg1 filters.Args) (volume.ListResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VolumeList", arg0, arg1)
ret0, _ := ret[0].(volume.VolumeListOKBody)
ret0, _ := ret[0].(volume.ListResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -1797,6 +1796,20 @@ func (mr *MockAPIClientMockRecorder) VolumeRemove(arg0, arg1, arg2 interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeRemove", reflect.TypeOf((*MockAPIClient)(nil).VolumeRemove), arg0, arg1, arg2)
}
// VolumeUpdate mocks base method.
func (m *MockAPIClient) VolumeUpdate(arg0 context.Context, arg1 string, arg2 swarm.Version, arg3 volume.UpdateOptions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VolumeUpdate", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// VolumeUpdate indicates an expected call of VolumeUpdate.
func (mr *MockAPIClientMockRecorder) VolumeUpdate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeUpdate", reflect.TypeOf((*MockAPIClient)(nil).VolumeUpdate), arg0, arg1, arg2, arg3)
}
// VolumesPrune mocks base method.
func (m *MockAPIClient) VolumesPrune(arg0 context.Context, arg1 filters.Args) (types.VolumesPruneReport, error) {
m.ctrl.T.Helper()

View File

@@ -91,20 +91,6 @@ func (mr *MockCliMockRecorder) Client() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockCli)(nil).Client))
}
// ClientInfo mocks base method.
func (m *MockCli) ClientInfo() command.ClientInfo {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClientInfo")
ret0, _ := ret[0].(command.ClientInfo)
return ret0
}
// ClientInfo indicates an expected call of ClientInfo.
func (mr *MockCliMockRecorder) ClientInfo() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientInfo", reflect.TypeOf((*MockCli)(nil).ClientInfo))
}
// ConfigFile mocks base method.
func (m *MockCli) ConfigFile() *configfile.ConfigFile {
m.ctrl.T.Helper()

View File

@@ -405,15 +405,15 @@ func (m *MockLogConsumer) EXPECT() *MockLogConsumerMockRecorder {
}
// Log mocks base method.
func (m *MockLogConsumer) Log(service, container, message string) {
func (m *MockLogConsumer) Log(containerName, service, message string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Log", service, container, message)
m.ctrl.Call(m, "Log", containerName, service, message)
}
// Log indicates an expected call of Log.
func (mr *MockLogConsumerMockRecorder) Log(service, container, message interface{}) *gomock.Call {
func (mr *MockLogConsumerMockRecorder) Log(containerName, service, message interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockLogConsumer)(nil).Log), service, container, message)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockLogConsumer)(nil).Log), containerName, service, message)
}
// Register mocks base method.

View File

@@ -14,21 +14,14 @@
limitations under the License.
*/
package e2e
package utils
import (
"testing"
import "time"
"gotest.tools/v3/icmd"
)
func TestDown(t *testing.T) {
c := NewParallelCLI(t)
const projectName = "e2e-down"
t.Run("no resource to remove", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
res.Assert(t, icmd.Expected{ExitCode: 0, Err: `No resource found to remove for project "e2e-down"`})
})
func DurationSecondToInt(d *time.Duration) *int {
if d == nil {
return nil
}
timeout := int(d.Seconds())
return &timeout
}

78
pkg/utils/safebuffer.go Normal file
View File

@@ -0,0 +1,78 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"bytes"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// SafeBuffer is a thread safe version of bytes.Buffer
type SafeBuffer struct {
m sync.RWMutex
b bytes.Buffer
}
// Read is a thread safe version of bytes.Buffer::Read
func (b *SafeBuffer) Read(p []byte) (n int, err error) {
b.m.RLock()
defer b.m.RUnlock()
return b.b.Read(p)
}
// Write is a thread safe version of bytes.Buffer::Write
func (b *SafeBuffer) Write(p []byte) (n int, err error) {
b.m.Lock()
defer b.m.Unlock()
return b.b.Write(p)
}
// String is a thread safe version of bytes.Buffer::String
func (b *SafeBuffer) String() string {
b.m.RLock()
defer b.m.RUnlock()
return b.b.String()
}
// Bytes is a thread safe version of bytes.Buffer::Bytes
func (b *SafeBuffer) Bytes() []byte {
b.m.RLock()
defer b.m.RUnlock()
return b.b.Bytes()
}
// RequireEventuallyContains is a thread safe eventual checker for the buffer content
func (b *SafeBuffer) RequireEventuallyContains(t testing.TB, v string) {
t.Helper()
var bufContents strings.Builder
require.Eventuallyf(t, func() bool {
b.m.Lock()
defer b.m.Unlock()
if _, err := b.b.WriteTo(&bufContents); err != nil {
require.FailNowf(t, "Failed to copy from buffer",
"Error: %v", err)
}
return strings.Contains(bufContents.String(), v)
}, 2*time.Second, 20*time.Millisecond,
"Buffer did not contain %q\n============\n%s\n============",
v, &bufContents)
}

112
verify-go-modules.sh Executable file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# Copyright The containerd 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.
#
# verifies if the require and replace directives for two go.mod files are in sync
#
set -eu -o pipefail
ROOT=$(dirname "${BASH_SOURCE}")
if [ "$#" -ne 1 ]; then
echo "Usage: $0 dir-for-second-go-mod"
exit 1
fi
if ! command -v jq &> /dev/null ; then
echo Please install jq
exit 1
fi
# Load the requires and replaces section in the root go.mod file
declare -A map_requires_1
declare -A map_replaces_1
pushd "${ROOT}" > /dev/null
while IFS='#' read -r key value
do
map_requires_1[$key]="$value"
done<<<$(go mod edit -json | jq -r '.Require[] | .Path + " # " + .Version')
while IFS='#' read -r key value
do
[ "$key" = "" ] || map_replaces_1[$key]="$value"
done<<<$(go mod edit -json | jq -r 'try .Replace[] | .Old.Path + " # " + .New.Path + " : " + .New.Version')
popd > /dev/null
# Load the requires and replaces section in the other go.mod file
declare -A map_requires_2
declare -A map_replaces_2
pushd "${ROOT}/$1" > /dev/null
while IFS='#' read -r key value
do
[ "$key" = "" ] || map_requires_2[$key]="$value"
done<<<$(go mod edit -json | jq -r '.Require[] | .Path + " # " + .Version')
while IFS='#' read -r key value
do
map_replaces_2[$key]="$value"
done<<<$(go mod edit -json | jq -r 'try .Replace[] | .Old.Path + " # " + .New.Path + " : " + .New.Version')
popd > /dev/null
# signal for errors later
ERRORS=0
# iterate through the second go.mod's require section and ensure that all items
# have the same values in the root go.mod replace section
for k in "${!map_requires_2[@]}"
do
if [ -v "map_requires_1[$k]" ]; then
if [ "${map_requires_2[$k]}" != "${map_requires_1[$k]}" ]; then
echo "${k} has different values in the go.mod files require section:" \
"${map_requires_1[$k]} in root go.mod ${map_requires_2[$k]} in $1/go.mod"
ERRORS=$(( ERRORS + 1 ))
fi
fi
done
# iterate through the second go.mod's replace section and ensure that all items
# have the same values in the root go.mod's replace section. Except for the
# containerd/containerd which we know will be different
for k in "${!map_replaces_2[@]}"
do
if [[ "${k}" == "github.com/docker/compose"* ]]; then
continue
fi
if [ -v "map_replaces_1[$k]" ]; then
if [ "${map_replaces_2[$k]}" != "${map_replaces_1[$k]}" ]; then
echo "${k} has different values in the go.mod files replace section:" \
"${map_replaces_1[$k]} in root go.mod ${map_replaces_2[$k]} in $1/go.mod"
ERRORS=$(( ERRORS + 1 ))
fi
fi
done
# iterate through the root go.mod's replace section and ensure that all the
# same items are present in the second go.mod's replace section and nothing is missing
for k in "${!map_replaces_1[@]}"
do
if [[ "${k}" == "github.com/docker/compose"* ]]; then
continue
fi
if [ ! -v "map_replaces_2[$k]" ]; then
echo "${k} has an entry in root go.mod replace section, but is missing from" \
" replace section in $1/go.mod"
ERRORS=$(( ERRORS + 1 ))
fi
done
if [ "$ERRORS" -ne 0 ]; then
echo "Found $ERRORS error(s)."
exit 1
fi