mirror of
https://github.com/docker/compose.git
synced 2026-02-16 05:22:33 +08:00
Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab791877ef | ||
|
|
aae5ddca27 | ||
|
|
7cf6d5ec4e | ||
|
|
0ab5079c1a | ||
|
|
89ef8198f3 | ||
|
|
b8bbdcd872 | ||
|
|
9d12eec148 | ||
|
|
d0e95ccac3 | ||
|
|
1e682a40ac | ||
|
|
7bc27d441b | ||
|
|
c1ce53c972 | ||
|
|
e42673daed | ||
|
|
c37182b2c5 | ||
|
|
ffb95449a2 | ||
|
|
0eaa249222 | ||
|
|
5c1484ece6 | ||
|
|
0fedddb008 | ||
|
|
aa0720f7e5 | ||
|
|
84984864c8 | ||
|
|
8566daa96e | ||
|
|
1b1f783e99 | ||
|
|
84ea395d5d | ||
|
|
1cb5536a2e | ||
|
|
e4850d9c48 | ||
|
|
8c39b5b7fd | ||
|
|
bc568eeb9b | ||
|
|
a501ab3a2f | ||
|
|
d4a4dcf4ee | ||
|
|
05e987dd0a | ||
|
|
0368f19030 | ||
|
|
3ee2ab87bb | ||
|
|
8f991a20db | ||
|
|
0234e13454 | ||
|
|
c342891f3e | ||
|
|
40fb42e0c9 | ||
|
|
8ef3494711 | ||
|
|
be74c90f50 | ||
|
|
cc247fdb84 | ||
|
|
a4ac6ab694 | ||
|
|
a5823b12f9 | ||
|
|
b27ace6c55 | ||
|
|
a73dce44b3 | ||
|
|
804d7163a7 | ||
|
|
cc60026c7b | ||
|
|
6b4ad0d1db | ||
|
|
87a0a57f70 | ||
|
|
c80d52aded | ||
|
|
95bc6c58b7 | ||
|
|
be30c67633 | ||
|
|
57a1e1e0df | ||
|
|
02305756b3 | ||
|
|
12dad4f8d0 | ||
|
|
a0acc20d88 | ||
|
|
053f20edab | ||
|
|
6ed9a7928f | ||
|
|
9b8d520b7d | ||
|
|
113fb6732d | ||
|
|
b9e5f9e917 | ||
|
|
c74a77e895 | ||
|
|
7f975fa40b | ||
|
|
7cf5940f4a | ||
|
|
7369127650 | ||
|
|
2e7644ff21 | ||
|
|
8f2b747104 | ||
|
|
9ac4f69918 | ||
|
|
2bef9769e5 | ||
|
|
707d55c77f | ||
|
|
5edd783032 | ||
|
|
6fbef29619 | ||
|
|
7fe43a8b4a | ||
|
|
24ec0b2d09 | ||
|
|
ed38fe0da8 | ||
|
|
06e71371ff | ||
|
|
fb5b90ed47 | ||
|
|
10a5d998e6 | ||
|
|
c3e5e49957 | ||
|
|
770281e9d5 | ||
|
|
4bf98c7053 | ||
|
|
8c5d7baa7d | ||
|
|
d7a24e9c81 | ||
|
|
02818ba6c7 | ||
|
|
481ae0aa7d | ||
|
|
88c3aaf1bf | ||
|
|
19d6ca9c5d | ||
|
|
6fe03e935e | ||
|
|
35d31cc500 | ||
|
|
7c5675c306 | ||
|
|
ea32fc99e1 | ||
|
|
a077e8a24b | ||
|
|
c53539e1cc | ||
|
|
8c1e2af3e1 | ||
|
|
a9e070206e | ||
|
|
32f29b833f | ||
|
|
533fc61634 | ||
|
|
386c3554e5 | ||
|
|
bfb9e11fc2 | ||
|
|
09e742b33b | ||
|
|
754376916c | ||
|
|
306ae161e1 | ||
|
|
fd4aecefee | ||
|
|
34e945a598 | ||
|
|
df9e605b31 | ||
|
|
6e2e19d621 | ||
|
|
e189942133 | ||
|
|
369e912586 | ||
|
|
71b4976e74 | ||
|
|
bd96d032df | ||
|
|
5a1f64532d | ||
|
|
7ba9aac5da | ||
|
|
f7961cc722 | ||
|
|
6d64242f71 | ||
|
|
eaf27d9dfe | ||
|
|
36a9183950 | ||
|
|
f472ce3493 | ||
|
|
533abc3b1d | ||
|
|
e8ea3ad29f | ||
|
|
197c16904a | ||
|
|
c630c8d295 | ||
|
|
41cf5ee3dc | ||
|
|
b7053cad8e | ||
|
|
b37a6c7f23 | ||
|
|
717ace9990 | ||
|
|
8bdfc62785 | ||
|
|
2978f1a0bc | ||
|
|
dd13299ede | ||
|
|
32ae036fd0 | ||
|
|
3f0550f884 | ||
|
|
18ce1f41b7 | ||
|
|
3bf29d401c | ||
|
|
c384905d70 | ||
|
|
7424a3d3c1 | ||
|
|
7c0b8a4c96 | ||
|
|
6b7e9466c4 | ||
|
|
a6dd996988 | ||
|
|
91eae4f035 | ||
|
|
8b89721476 | ||
|
|
3892e9cbc4 | ||
|
|
f43a1e3ece | ||
|
|
fa1ae635d1 | ||
|
|
afc0263f5c | ||
|
|
b15df818c7 | ||
|
|
bb002a7688 | ||
|
|
2ccd57e01a | ||
|
|
1c14d30777 | ||
|
|
8bd487ac43 | ||
|
|
1d4cb32001 | ||
|
|
19d1ab77eb | ||
|
|
a01f62f5dc | ||
|
|
045f5ad758 | ||
|
|
b6b58d26c1 | ||
|
|
55b1b9976b | ||
|
|
34441c8e4a | ||
|
|
139a6945cb | ||
|
|
97a9d02dda | ||
|
|
25c4bcef85 | ||
|
|
4607dac19c | ||
|
|
c1f475d7bd | ||
|
|
c6109b2e5c | ||
|
|
fffe7fff57 | ||
|
|
0a5f4e62e4 | ||
|
|
d88f6805e7 | ||
|
|
266ab22d53 | ||
|
|
a7476c8eeb | ||
|
|
15ebff00b1 |
64
.github/ISSUE_TEMPLATE.md
vendored
64
.github/ISSUE_TEMPLATE.md
vendored
@@ -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
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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'
|
||||
13
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
13
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal 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
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,4 +3,4 @@
|
||||
**Related issue**
|
||||
<!-- If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" -->
|
||||
|
||||
**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
|
||||
**(not mandatory) A picture of a cute animal, if possible in relation to what you did**
|
||||
|
||||
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -12,7 +12,7 @@ onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- "enhancement ✨"
|
||||
- "kind/feature"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -176,6 +176,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' }}
|
||||
|
||||
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -18,8 +18,8 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
repository: docker/docker.github.io
|
||||
ref: master
|
||||
repository: docker/docs
|
||||
ref: main
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
|
||||
54
.github/workflows/scorecards.yml
vendored
Normal file
54
.github/workflows/scorecards.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Scorecards supply-chain security
|
||||
on:
|
||||
# Only the default branch is supported.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '44 9 * * 4'
|
||||
push:
|
||||
branches: [ "v2" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Used to receive a badge.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.6
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
# Publish the results for public repositories to enable scorecard badges. For more details, see
|
||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories, `publish_results` will automatically be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
bin/
|
||||
/.vscode/
|
||||
coverage.out
|
||||
|
||||
@@ -11,6 +11,7 @@ linters:
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
@@ -36,6 +37,12 @@ linters-settings:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- io/ioutil
|
||||
gomodguard:
|
||||
blocked:
|
||||
versions:
|
||||
- gotest.tools:
|
||||
version: "< 3.0.0"
|
||||
reason: "deprecated, pre-modules version"
|
||||
gocritic:
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
|
||||
@@ -36,7 +36,7 @@ make test
|
||||
If you need to update a golden file simply do `go test ./... -test.update-golden`.
|
||||
|
||||
### End-to-end tests
|
||||
To run e2e tests, the Compose CLI binary need to be build. All the commands to run e2e tests propose a version
|
||||
To run e2e tests, the Compose CLI binary needs to be built. All the commands to run e2e tests propose a version
|
||||
with the prefix `build-and-e2e` to first build the CLI before executing tests.
|
||||
|
||||
Note that this requires a local Docker Engine to be running.
|
||||
@@ -84,8 +84,8 @@ make build-and-e2e-compose-standalone
|
||||
## Releases
|
||||
|
||||
To create a new release:
|
||||
* Check that the CI is green on the main branch for commit you want to release
|
||||
* Run the release Github Actions workflow with a tag of the form vx.y.z following existing tags.
|
||||
* Check that the CI is green on the main branch for the commit you want to release
|
||||
* Run the release Github Actions workflow with a tag of form vx.y.z following existing tags.
|
||||
|
||||
This will automatically create a new tag, release and make binaries for
|
||||
Windows, macOS, and Linux available for download on the
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
||||
[setting up a Docker development environment and the contribution
|
||||
process](https://docs.docker.com/opensource/project/who-written-for/).
|
||||
process](https://docs.docker.com/contribute/overview/).
|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
@@ -11,23 +11,31 @@ start participating.
|
||||
|
||||
## Topics
|
||||
|
||||
* [Reporting Security Issues](#reporting-security-issues)
|
||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||
* [Reporting Issues](#reporting-other-issues)
|
||||
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
|
||||
* [Community Guidelines](#docker-community-guidelines)
|
||||
- [Contributing to Docker](#contributing-to-docker)
|
||||
- [Topics](#topics)
|
||||
- [Reporting security issues](#reporting-security-issues)
|
||||
- [Reporting other issues](#reporting-other-issues)
|
||||
- [Quick contribution tips and guidelines](#quick-contribution-tips-and-guidelines)
|
||||
- [Pull requests are always welcome](#pull-requests-are-always-welcome)
|
||||
- [Talking to other Docker users and contributors](#talking-to-other-docker-users-and-contributors)
|
||||
- [Conventions](#conventions)
|
||||
- [Merge approval](#merge-approval)
|
||||
- [Sign your work](#sign-your-work)
|
||||
- [How can I become a maintainer?](#how-can-i-become-a-maintainer)
|
||||
- [Docker community guidelines](#docker-community-guidelines)
|
||||
- [Coding Style](#coding-style)
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
The Docker maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
Please **DO NOT** file a public issue, instead, send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||
Security reports are greatly appreciated and we will publicly thank you for them.
|
||||
We also like to send gifts—if you're into Docker swag, make sure to let
|
||||
us know. We currently do not offer a paid security bounty program, but are not
|
||||
us know. We currently do not offer a paid security bounty program but are not
|
||||
ruling it out in the future.
|
||||
|
||||
|
||||
@@ -39,11 +47,11 @@ and will thank you for it!
|
||||
|
||||
Check that [our issue database](https://github.com/docker/compose/labels/Docker%20Compose%20V2)
|
||||
doesn't already include that problem or suggestion before submitting an issue.
|
||||
If you find a match, you can use the "subscribe" button to get notified on
|
||||
If you find a match, you can use the "subscribe" button to get notified of
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help to resolve it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolving the issue, please leave a comment.
|
||||
resolve the issue, please leave a comment.
|
||||
|
||||
When reporting issues, always include:
|
||||
|
||||
@@ -51,7 +59,7 @@ When reporting issues, always include:
|
||||
* The output of `docker context show`.
|
||||
* The output of `docker info`.
|
||||
|
||||
Also include the steps required to reproduce the problem if possible and
|
||||
Also, include the steps required to reproduce the problem if possible and
|
||||
applicable. This information will help us review and fix your issue faster.
|
||||
When sending lengthy log files, consider posting them as a gist
|
||||
(https://gist.github.com).
|
||||
@@ -124,7 +132,7 @@ Fork the repository and make changes on your fork in a feature branch:
|
||||
issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Also end-to-end tests are
|
||||
it! Take a look at existing tests for inspiration. Also, end-to-end tests are
|
||||
available. Run the full test suite, both unit tests and e2e tests on your
|
||||
branch before submitting a pull request. See [BUILDING.md](BUILDING.md) for
|
||||
instructions to build and run tests.
|
||||
@@ -145,7 +153,7 @@ suggested modifications and push additional commits to your feature branch. Post
|
||||
a comment after pushing. New commits show up in the pull request automatically,
|
||||
but the reviewers are notified only when you comment.
|
||||
|
||||
Pull requests must be cleanly rebased on top of master without multiple branches
|
||||
Pull requests must be cleanly rebased on top of the base branch without multiple branches
|
||||
mixed into the PR.
|
||||
|
||||
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
|
||||
@@ -165,7 +173,7 @@ changes in the same pull request so that a revert would remove all traces of
|
||||
the feature or fix.
|
||||
|
||||
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in the pull
|
||||
request description that close an issue. Including references automatically
|
||||
request description that closes an issue. Including references automatically
|
||||
closes the issue on a merge.
|
||||
|
||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||
@@ -256,7 +264,7 @@ your help to keep it that way. To help with this we've come up with some general
|
||||
guidelines for the community as a whole:
|
||||
|
||||
* Be nice: Be courteous, respectful and polite to fellow community members:
|
||||
no regional, racial, gender, or other abuse will be tolerated. We like
|
||||
no regional, racial, gender or other abuse will be tolerated. We like
|
||||
nice people way better than mean ones!
|
||||
|
||||
* Encourage diversity and participation: Make everyone in our community feel
|
||||
@@ -270,10 +278,10 @@ guidelines for the community as a whole:
|
||||
|
||||
* Stay on topic: Make sure that you are posting to the correct channel and
|
||||
avoid off-topic discussions. Remember when you update an issue or respond
|
||||
to an email you are potentially sending to a large number of people. Please
|
||||
consider this before you update. Also remember that nobody likes spam.
|
||||
to an email you are potentially sending it to a large number of people. Please
|
||||
consider this before you update. Also, remember that nobody likes spam.
|
||||
|
||||
* Don't send email to the maintainers: There's no need to send email to the
|
||||
* Don't send emails to the maintainers: There's no need to send emails to the
|
||||
maintainers to ask them to investigate an issue or to take a look at a
|
||||
pull request. Instead of sending an email, GitHub mentions should be
|
||||
used to ping maintainers to review a pull request, a proposal or an
|
||||
@@ -287,7 +295,7 @@ to result in a solid, consistent codebase.
|
||||
|
||||
It is possible that the code base does not currently comply with these
|
||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||
goes against the spirit of the guidelines. All new contributions should make a
|
||||
goes against the spirit of the guidelines. All new contributors should make their
|
||||
best effort to clean up and make the code base better than they left it.
|
||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||
code base easier for humans to navigate and understand. Always keep that in
|
||||
@@ -301,7 +309,7 @@ The rules:
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
4. Include code comments. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
|
||||
@@ -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.4
|
||||
ARG XX_VERSION=1.1.2
|
||||
ARG GOLANGCI_LINT_VERSION=v1.49.0
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
@@ -177,7 +177,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 .
|
||||
|
||||
8
Makefile
8
Makefile
@@ -33,7 +33,8 @@ 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
|
||||
@@ -61,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
|
||||
@@ -76,6 +77,7 @@ build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the co
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
mockgen --version >/dev/null 2>&1 || go install github.com/golang/mock/mockgen@v1.6.0
|
||||
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
|
||||
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
|
||||
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
|
||||
@@ -130,7 +132,7 @@ go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and g
|
||||
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
|
||||
pre-commit: validate check-dependencies lint build test e2e-compose
|
||||
|
||||
|
||||
17
README.md
17
README.md
@@ -1,15 +1,24 @@
|
||||
# Table of Contents
|
||||
- [Docker Compose v2](#docker-compose-v2)
|
||||
- [About update and backward compatibility](#about-update-and-backward-compatibility)
|
||||
- [Where to get Docker Compose](#where-to-get-docker-compose)
|
||||
+ [Windows and macOS](#windows-and-macos)
|
||||
+ [Linux](#linux)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Contributing](#contributing)
|
||||
# Docker Compose v2
|
||||
|
||||
[](https://github.com/docker/compose/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/compose/v2)
|
||||
[](https://github.com/docker/compose/actions?query=workflow%3Aci)
|
||||
[](https://goreportcard.com/report/github.com/docker/compose/v2)
|
||||
|
||||
[](https://codecov.io/gh/docker/compose)
|
||||
[](https://api.securityscorecards.dev/projects/github.com/docker/compose)
|
||||

|
||||
|
||||
Docker Compose is a tool for running multi-container applications on Docker
|
||||
defined using the [Compose file format](https://compose-spec.io).
|
||||
A Compose file is used to define how the one or more containers that make up
|
||||
A Compose file is used to define how one or more containers that make up
|
||||
your application are configured.
|
||||
Once you have a Compose file, you can create and start your application with a
|
||||
single command: `docker compose up`.
|
||||
@@ -33,7 +42,7 @@ for Windows and macOS.
|
||||
You can download Docker Compose binaries from the
|
||||
[release page](https://github.com/docker/compose/releases) on this repository.
|
||||
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
|
||||
Or copy it into one of these folders to install it system-wide:
|
||||
|
||||
@@ -46,7 +55,7 @@ Or copy it into one of these folders to install it system-wide:
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Using Docker Compose is basically a three-step process:
|
||||
Using Docker Compose is a three-step process:
|
||||
1. Define your app's environment with a `Dockerfile` so it can be
|
||||
reproduced anywhere.
|
||||
2. Define the services that make up your app in `docker-compose.yml` so
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -33,7 +34,7 @@ import (
|
||||
)
|
||||
|
||||
type buildOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
quiet bool
|
||||
pull bool
|
||||
@@ -72,9 +73,9 @@ var printerModes = []string{
|
||||
buildx.PrinterModeQuiet,
|
||||
}
|
||||
|
||||
func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func buildCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := buildOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [OPTIONS] [SERVICE...]",
|
||||
@@ -100,6 +101,9 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
|
||||
opts.ssh = "default"
|
||||
}
|
||||
if progress.Mode == progress.ModePlain && !cmd.Flags().Changed("progress") {
|
||||
opts.progress = buildx.PrinterModePlain
|
||||
}
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
@@ -125,7 +129,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, err := opts.toProject(services, cli.WithResolvedPaths(true))
|
||||
project, err := opts.ToProject(services, cli.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ func noCompletion() validArgsFn {
|
||||
}
|
||||
}
|
||||
|
||||
func completeServiceNames(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)
|
||||
project, err := p.ToProject(nil)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
type projectOptions struct {
|
||||
type ProjectOptions struct {
|
||||
ProjectName string
|
||||
Profiles []string
|
||||
ConfigPaths []string
|
||||
@@ -108,16 +108,16 @@ type ProjectFunc func(ctx context.Context, project *types.Project) error
|
||||
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
|
||||
|
||||
// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
|
||||
func (o *ProjectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
|
||||
return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
return fn(ctx, project)
|
||||
})
|
||||
}
|
||||
|
||||
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
|
||||
func (o *ProjectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
|
||||
return Adapt(func(ctx context.Context, args []string) error {
|
||||
project, err := o.toProject(args, cli.WithResolvedPaths(true))
|
||||
project, err := o.ToProject(args, cli.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
|
||||
})
|
||||
}
|
||||
|
||||
func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
|
||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||
@@ -137,11 +137,11 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
|
||||
func (o *projectOptions) projectOrName() (*types.Project, string, error) {
|
||||
func (o *ProjectOptions) projectOrName(services ...string) (*types.Project, string, error) {
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
if o.ProjectName == "" {
|
||||
p, err := o.toProject(nil)
|
||||
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
|
||||
p, err := o.ToProject(services)
|
||||
if err != nil {
|
||||
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||
if envProjectName != "" {
|
||||
@@ -155,7 +155,7 @@ func (o *projectOptions) projectOrName() (*types.Project, string, error) {
|
||||
return project, name, nil
|
||||
}
|
||||
|
||||
func (o *projectOptions) toProjectName() (string, error) {
|
||||
func (o *ProjectOptions) toProjectName() (string, error) {
|
||||
if o.ProjectName != "" {
|
||||
return o.ProjectName, nil
|
||||
}
|
||||
@@ -165,14 +165,14 @@ func (o *projectOptions) toProjectName() (string, error) {
|
||||
return envProjectName, nil
|
||||
}
|
||||
|
||||
project, err := o.toProject(nil)
|
||||
project, err := o.ToProject(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return project.Name, nil
|
||||
}
|
||||
|
||||
func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
options, err := o.toProjectOptions(po...)
|
||||
if err != nil {
|
||||
return nil, compose.WrapComposeError(err)
|
||||
@@ -187,13 +187,6 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
ef := o.EnvFile
|
||||
if ef != "" && !filepath.IsAbs(ef) {
|
||||
ef, err = filepath.Abs(ef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
@@ -203,12 +196,16 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||
api.OneoffLabel: "False", // default, will be overridden by `run` command
|
||||
}
|
||||
if ef != "" {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = ef
|
||||
if o.EnvFile != "" {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = o.EnvFile
|
||||
}
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
if profiles, ok := options.Environment["COMPOSE_PROFILES"]; ok && len(o.Profiles) == 0 {
|
||||
o.Profiles = append(o.Profiles, strings.Split(profiles, ",")...)
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
s, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
@@ -217,10 +214,6 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
o.Profiles = append(o.Profiles, s.GetProfiles()...)
|
||||
}
|
||||
|
||||
if profiles, ok := options.Environment["COMPOSE_PROFILES"]; ok {
|
||||
o.Profiles = append(o.Profiles, strings.Split(profiles, ",")...)
|
||||
}
|
||||
|
||||
project.ApplyProfiles(o.Profiles)
|
||||
|
||||
project.WithoutUnnecessaryResources()
|
||||
@@ -229,7 +222,7 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return project, err
|
||||
}
|
||||
|
||||
func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
@@ -250,7 +243,7 @@ func RunningAsStandalone() bool {
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
|
||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
|
||||
@@ -261,12 +254,13 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
|
||||
opts := projectOptions{}
|
||||
opts := ProjectOptions{}
|
||||
var (
|
||||
ansi string
|
||||
noAnsi bool
|
||||
verbose bool
|
||||
version bool
|
||||
ansi string
|
||||
noAnsi bool
|
||||
verbose bool
|
||||
version bool
|
||||
parallel int
|
||||
)
|
||||
c := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
@@ -325,6 +319,15 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts.ProjectDir = opts.WorkDir
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
|
||||
}
|
||||
if opts.EnvFile != "" && !filepath.IsAbs(opts.EnvFile) {
|
||||
opts.EnvFile, err = filepath.Abs(opts.EnvFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if parallel > 0 {
|
||||
backend.MaxConcurrency(parallel)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -370,6 +373,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
)
|
||||
|
||||
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
c.Flags().IntVar(¶llel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
|
||||
c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
c.Flags().MarkHidden("version") //nolint:errcheck
|
||||
c.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
|
||||
@@ -379,7 +383,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
return c
|
||||
}
|
||||
|
||||
func setEnvWithDotEnv(prjOpts *projectOptions) error {
|
||||
func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
|
||||
options, err := prjOpts.toProjectOptions()
|
||||
if err != nil {
|
||||
return compose.WrapComposeError(err)
|
||||
|
||||
@@ -26,12 +26,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cnabio/cnab-to-oci/remotes"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -39,7 +35,7 @@ import (
|
||||
)
|
||||
|
||||
type convertOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
Format string
|
||||
Output string
|
||||
quiet bool
|
||||
@@ -51,11 +47,12 @@ type convertOptions struct {
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
noConsistency bool
|
||||
}
|
||||
|
||||
func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func convertCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := convertOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"config"},
|
||||
@@ -101,6 +98,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
@@ -114,32 +112,20 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
|
||||
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
|
||||
var content []byte
|
||||
project, err := opts.toProject(services,
|
||||
project, err := opts.ToProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithNormalization(!opts.noNormalize),
|
||||
cli.WithConsistency(!opts.noConsistency),
|
||||
cli.WithDiscardEnvFile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.resolveImageDigests {
|
||||
configFile := cliconfig.LoadDefaultConfigFile(os.Stderr)
|
||||
|
||||
resolver := remotes.CreateResolver(configFile)
|
||||
err = project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
|
||||
_, desc, err := resolver.Resolve(ctx, named.String())
|
||||
return desc.Digest, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
content, err = backend.Convert(ctx, project, api.ConvertOptions{
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -166,7 +152,7 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
|
||||
}
|
||||
|
||||
func runServices(opts convertOptions) error {
|
||||
project, err := opts.toProject(nil)
|
||||
project, err := opts.ToProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,7 +163,7 @@ func runServices(opts convertOptions) error {
|
||||
}
|
||||
|
||||
func runVolumes(opts convertOptions) error {
|
||||
project, err := opts.toProject(nil)
|
||||
project, err := opts.ToProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,7 +178,7 @@ func runHash(opts convertOptions) error {
|
||||
if opts.hash != "*" {
|
||||
services = append(services, strings.Split(opts.hash, ",")...)
|
||||
}
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.ToProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -208,7 +194,7 @@ func runHash(opts convertOptions) error {
|
||||
|
||||
func runProfiles(opts convertOptions, services []string) error {
|
||||
set := map[string]struct{}{}
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.ToProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -229,7 +215,7 @@ func runProfiles(opts convertOptions, services []string) error {
|
||||
}
|
||||
|
||||
func runConfigImages(opts convertOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.ToProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,7 +223,7 @@ func runConfigImages(opts convertOptions, services []string) error {
|
||||
if s.Image != "" {
|
||||
fmt.Println(s.Image)
|
||||
} else {
|
||||
fmt.Printf("%s_%s\n", project.Name, s.Name)
|
||||
fmt.Printf("%s%s%s\n", project.Name, api.Separator, s.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
type copyOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
|
||||
source string
|
||||
destination string
|
||||
@@ -37,9 +37,9 @@ type copyOptions struct {
|
||||
copyUIDGID bool
|
||||
}
|
||||
|
||||
func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func copyCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := copyOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
copyCmd := &cobra.Command{
|
||||
Use: `cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-
|
||||
|
||||
@@ -43,7 +43,7 @@ type createOptions struct {
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func createCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := createOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] [SERVICE...]",
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
type downOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
removeOrphans bool
|
||||
timeChanged bool
|
||||
timeout int
|
||||
@@ -39,9 +39,9 @@ type downOptions struct {
|
||||
images string
|
||||
}
|
||||
|
||||
func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func downCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := downOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down [OPTIONS]",
|
||||
|
||||
@@ -31,10 +31,10 @@ type eventsOpts struct {
|
||||
json bool
|
||||
}
|
||||
|
||||
func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func eventsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := eventsOpts{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
|
||||
@@ -43,10 +43,10 @@ type execOpts struct {
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := execOpts{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
|
||||
@@ -34,13 +34,14 @@ import (
|
||||
)
|
||||
|
||||
type imageOptions struct {
|
||||
*projectOptions
|
||||
Quiet bool
|
||||
*ProjectOptions
|
||||
Quiet bool
|
||||
Format string
|
||||
}
|
||||
|
||||
func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func imagesCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := imageOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
imgCmd := &cobra.Command{
|
||||
Use: "images [OPTIONS] [SERVICE...]",
|
||||
@@ -50,6 +51,7 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
return imgCmd
|
||||
}
|
||||
@@ -88,7 +90,7 @@ func runImages(ctx context.Context, backend api.Service, opts imageOptions, serv
|
||||
return images[i].ContainerName < images[j].ContainerName
|
||||
})
|
||||
|
||||
return formatter.Print(images, formatter.PRETTY, os.Stdout,
|
||||
return formatter.Print(images, opts.Format, os.Stdout,
|
||||
func(w io.Writer) {
|
||||
for _, img := range images {
|
||||
id := stringid.TruncateID(img.ID)
|
||||
@@ -104,5 +106,5 @@ func runImages(ctx context.Context, backend api.Service, opts imageOptions, serv
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
|
||||
}
|
||||
},
|
||||
"Container", "Repository", "Tag", "Image Id", "Size")
|
||||
"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ import (
|
||||
)
|
||||
|
||||
type killOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
removeOrphans bool
|
||||
signal string
|
||||
}
|
||||
|
||||
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func killCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := killOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [OPTIONS] [SERVICE...]",
|
||||
@@ -54,7 +54,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,5 +65,4 @@ func runKill(ctx context.Context, backend api.Service, opts killOptions, service
|
||||
Services: services,
|
||||
Signal: opts.signal,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func listCommand(backend api.Service) *cobra.Command {
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
lsCmd.Flags().StringVar(&lsOpts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
|
||||
lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.")
|
||||
lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects")
|
||||
|
||||
@@ -20,15 +20,14 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type logsOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
follow bool
|
||||
tail string
|
||||
@@ -39,9 +38,9 @@ type logsOptions struct {
|
||||
timestamps bool
|
||||
}
|
||||
|
||||
func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func logsCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := logsOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
logsCmd := &cobra.Command{
|
||||
Use: "logs [OPTIONS] [SERVICE...]",
|
||||
@@ -63,11 +62,11 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runLogs(ctx context.Context, backend api.Service, opts logsOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix)
|
||||
consumer := formatter.NewLogConsumer(ctx, os.Stdout, os.Stderr, !opts.noColor, !opts.noPrefix, false)
|
||||
return backend.Logs(ctx, name, consumer, api.LogOptions{
|
||||
Project: project,
|
||||
Services: services,
|
||||
|
||||
@@ -25,12 +25,12 @@ import (
|
||||
)
|
||||
|
||||
type pauseOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
}
|
||||
|
||||
func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func pauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pauseOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pause [SERVICE...]",
|
||||
@@ -44,7 +44,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,12 +56,12 @@ func runPause(ctx context.Context, backend api.Service, opts pauseOptions, servi
|
||||
}
|
||||
|
||||
type unpauseOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
}
|
||||
|
||||
func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func unpauseCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := unpauseOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "unpause [SERVICE...]",
|
||||
@@ -75,7 +75,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -27,26 +28,27 @@ import (
|
||||
)
|
||||
|
||||
type portOptions struct {
|
||||
*projectOptions
|
||||
port int
|
||||
*ProjectOptions
|
||||
port uint16
|
||||
protocol string
|
||||
index int
|
||||
}
|
||||
|
||||
func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func portCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := portOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
|
||||
Short: "Print the public port for a port binding.",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
port, err := strconv.Atoi(args[1])
|
||||
port, err := strconv.ParseUint(args[1], 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.port = port
|
||||
opts.port = uint16(port)
|
||||
opts.protocol = strings.ToLower(opts.protocol)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
|
||||
@@ -24,12 +24,14 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -37,7 +39,7 @@ import (
|
||||
)
|
||||
|
||||
type psOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
Format string
|
||||
All bool
|
||||
Quiet bool
|
||||
@@ -65,9 +67,9 @@ func (p *psOptions) parseFilter() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func psCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := psOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
psCmd := &cobra.Command{
|
||||
Use: "ps [OPTIONS] [SERVICE...]",
|
||||
@@ -81,7 +83,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||
flags.StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
|
||||
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
@@ -91,7 +93,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,21 +144,18 @@ SERVICES:
|
||||
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
writer(containers),
|
||||
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
||||
"NAME", "IMAGE", "COMMAND", "SERVICE", "CREATED", "STATUS", "PORTS")
|
||||
}
|
||||
|
||||
func writer(containers []api.ContainerSummary) func(w io.Writer) {
|
||||
return func(w io.Writer) {
|
||||
for _, container := range containers {
|
||||
ports := displayablePorts(container)
|
||||
status := container.State
|
||||
if status == "running" && container.Health != "" {
|
||||
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
|
||||
} else if status == "exited" || status == "dead" {
|
||||
status = fmt.Sprintf("%s (%d)", container.State, container.ExitCode)
|
||||
}
|
||||
createdAt := time.Unix(container.Created, 0)
|
||||
created := units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
||||
status := container.Status
|
||||
command := formatter2.Ellipsis(container.Command, 20)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", container.Name, strconv.Quote(command), container.Service, status, ports)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", container.Name, container.Image, strconv.Quote(command), container.Service, created, status, ports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,15 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPsPretty(t *testing.T) {
|
||||
func TestPsTable(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
origStdout := os.Stdout
|
||||
t.Cleanup(func() {
|
||||
os.Stdout = origStdout
|
||||
})
|
||||
dir := t.TempDir()
|
||||
f, err := os.Create(filepath.Join(dir, "output.txt"))
|
||||
out := filepath.Join(dir, "output.txt")
|
||||
f, err := os.Create(out)
|
||||
if err != nil {
|
||||
t.Fatal("could not create output file")
|
||||
}
|
||||
@@ -51,8 +52,9 @@ func TestPsPretty(t *testing.T) {
|
||||
DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
return []api.ContainerSummary{
|
||||
{
|
||||
ID: "abc123",
|
||||
Name: "ABC",
|
||||
ID: "abc123",
|
||||
Name: "ABC",
|
||||
Image: "foo/bar",
|
||||
Publishers: api.PortPublishers{
|
||||
{
|
||||
TargetPort: 8080,
|
||||
@@ -69,15 +71,14 @@ func TestPsPretty(t *testing.T) {
|
||||
}, nil
|
||||
}).AnyTimes()
|
||||
|
||||
opts := psOptions{projectOptions: &projectOptions{ProjectName: "test"}}
|
||||
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
|
||||
err = runPs(ctx, backend, nil, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
output := make([]byte, 256)
|
||||
_, err = f.Read(output)
|
||||
output, err := os.ReadFile(out)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -29,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
type pullOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
quiet bool
|
||||
parallel bool
|
||||
@@ -38,9 +39,9 @@ type pullOptions struct {
|
||||
ignorePullFailures bool
|
||||
}
|
||||
|
||||
func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func pullCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pullOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull [OPTIONS] [SERVICE...]",
|
||||
@@ -67,23 +68,32 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func withSelectedServicesOnly(project *types.Project, services []string) error {
|
||||
enabled, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if !utils.StringContains(services, s.Name) {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
}
|
||||
project.Services = enabled
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.ToProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.includeDeps {
|
||||
enabled, err := project.GetServices(services...)
|
||||
err := withSelectedServicesOnly(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if !utils.StringContains(services, s.Name) {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
}
|
||||
project.Services = enabled
|
||||
}
|
||||
|
||||
return backend.Pull(ctx, project, api.PullOptions{
|
||||
|
||||
@@ -25,15 +25,16 @@ import (
|
||||
)
|
||||
|
||||
type pushOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
|
||||
IncludeDeps bool
|
||||
Ignorefailures bool
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func pushCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pushOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
pushCmd := &cobra.Command{
|
||||
Use: "push [OPTIONS] [SERVICE...]",
|
||||
@@ -44,17 +45,27 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
|
||||
pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies")
|
||||
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
|
||||
|
||||
return pushCmd
|
||||
}
|
||||
|
||||
func runPush(ctx context.Context, backend api.Service, opts pushOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.ToProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.IncludeDeps {
|
||||
err := withSelectedServicesOnly(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return backend.Push(ctx, project, api.PushOptions{
|
||||
IgnoreFailures: opts.Ignorefailures,
|
||||
Quiet: opts.Quiet,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ import (
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
force bool
|
||||
stop bool
|
||||
volumes bool
|
||||
}
|
||||
|
||||
func removeCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func removeCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := removeOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm [OPTIONS] [SERVICE...]",
|
||||
@@ -59,7 +59,7 @@ Any data which is not in a volume will be lost.`,
|
||||
}
|
||||
|
||||
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ import (
|
||||
)
|
||||
|
||||
type restartOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
timeout int
|
||||
}
|
||||
|
||||
func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func restartCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := restartOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
restartCmd := &cobra.Command{
|
||||
Use: "restart [OPTIONS] [SERVICE...]",
|
||||
@@ -49,7 +49,7 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type runOptions struct {
|
||||
@@ -107,12 +108,13 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := runOptions{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
},
|
||||
}
|
||||
createOpts := createOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
|
||||
Short: "Run a one-off command on a service.",
|
||||
@@ -135,13 +137,12 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
project, err := p.toProject([]string{opts.Service}, cgo.WithResolvedPaths(true))
|
||||
project, err := p.ToProject([]string{opts.Service}, cgo.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
opts.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||
return runRun(ctx, backend, project, opts, createOpts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
@@ -161,6 +162,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
@@ -181,12 +183,14 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions, createOpts createOptions) error {
|
||||
err := opts.apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOpts.Apply(project)
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
|
||||
})
|
||||
|
||||
@@ -24,12 +24,12 @@ import (
|
||||
)
|
||||
|
||||
type startOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
}
|
||||
|
||||
func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func startCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := startOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
startCmd := &cobra.Command{
|
||||
Use: "start [SERVICE...]",
|
||||
@@ -43,7 +43,7 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@ import (
|
||||
)
|
||||
|
||||
type stopOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
timeChanged bool
|
||||
timeout int
|
||||
}
|
||||
|
||||
func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func stopCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := stopOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "stop [OPTIONS] [SERVICE...]",
|
||||
@@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName()
|
||||
project, name, err := opts.projectOrName(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ import (
|
||||
)
|
||||
|
||||
type topOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
}
|
||||
|
||||
func topCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func topCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := topOptions{
|
||||
projectOptions: p,
|
||||
ProjectOptions: p,
|
||||
}
|
||||
topCmd := &cobra.Command{
|
||||
Use: "top [SERVICES...]",
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
|
||||
// composeOptions hold options common to `up` and `run` to run compose project
|
||||
type composeOptions struct {
|
||||
*projectOptions
|
||||
*ProjectOptions
|
||||
}
|
||||
|
||||
type upOptions struct {
|
||||
@@ -49,21 +49,16 @@ type upOptions struct {
|
||||
noPrefix bool
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
timestamp bool
|
||||
wait bool
|
||||
}
|
||||
|
||||
func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||
if opts.noDeps {
|
||||
enabled, err := project.GetServices(services...)
|
||||
err := withSelectedServicesOnly(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if !utils.StringContains(services, s.Name) {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
}
|
||||
project.Services = enabled
|
||||
}
|
||||
|
||||
if opts.exitCodeFrom != "" {
|
||||
@@ -92,7 +87,7 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
func upCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
upCmd := &cobra.Command{
|
||||
@@ -126,6 +121,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
|
||||
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
|
||||
flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps.")
|
||||
flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
|
||||
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
|
||||
@@ -176,7 +172,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
|
||||
var consumer api.LogConsumer
|
||||
if !upOptions.Detach {
|
||||
consumer = formatter.NewLogConsumer(ctx, os.Stdout, !upOptions.noColor, !upOptions.noPrefix)
|
||||
consumer = formatter.NewLogConsumer(ctx, os.Stdout, os.Stderr, !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
|
||||
}
|
||||
|
||||
attachTo := services
|
||||
@@ -214,6 +210,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
Services: services,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
package formatter
|
||||
|
||||
const (
|
||||
// JSON is the constant for Json formats on list commands
|
||||
// JSON Print in JSON format
|
||||
JSON = "json"
|
||||
// TemplateLegacyJSON the legacy json formatting value using go template
|
||||
TemplateLegacyJSON = "{{json.}}"
|
||||
// PRETTY is the constant for default formats on list commands
|
||||
// Deprecated: use TABLE
|
||||
PRETTY = "pretty"
|
||||
// TABLE Print output in table format with column headers (default)
|
||||
TABLE = "table"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// Print prints formatted lists in different formats
|
||||
func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error {
|
||||
switch strings.ToLower(format) {
|
||||
case PRETTY, "":
|
||||
case TABLE, PRETTY, "":
|
||||
return PrintPrettySection(outWriter, writerFn, headers...)
|
||||
case TemplateLegacyJSON:
|
||||
switch reflect.TypeOf(toJSON).Kind() {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
type testStruct struct {
|
||||
|
||||
@@ -23,8 +23,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
|
||||
// LogConsumer consume logs from services and format them
|
||||
@@ -32,20 +34,24 @@ type logConsumer struct {
|
||||
ctx context.Context
|
||||
presenters sync.Map // map[string]*presenter
|
||||
width int
|
||||
writer io.Writer
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
color bool
|
||||
prefix bool
|
||||
timestamp bool
|
||||
}
|
||||
|
||||
// NewLogConsumer creates a new LogConsumer
|
||||
func NewLogConsumer(ctx context.Context, w io.Writer, color bool, prefix bool) api.LogConsumer {
|
||||
func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix, timestamp bool) api.LogConsumer {
|
||||
return &logConsumer{
|
||||
ctx: ctx,
|
||||
presenters: sync.Map{},
|
||||
width: 0,
|
||||
writer: w,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
color: color,
|
||||
prefix: prefix,
|
||||
timestamp: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,20 +89,34 @@ func (l *logConsumer) getPresenter(container string) *presenter {
|
||||
}
|
||||
|
||||
// Log formats a log message as received from name/container
|
||||
func (l *logConsumer) Log(container, service, message string) {
|
||||
func (l *logConsumer) Log(container, message string) {
|
||||
l.write(l.stdout, container, message)
|
||||
}
|
||||
|
||||
// Log formats a log message as received from name/container
|
||||
func (l *logConsumer) Err(container, message string) {
|
||||
l.write(l.stderr, container, message)
|
||||
}
|
||||
|
||||
func (l *logConsumer) write(w io.Writer, container, message string) {
|
||||
if l.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
p := l.getPresenter(container)
|
||||
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line)
|
||||
if l.timestamp {
|
||||
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Status(container, msg string) {
|
||||
p := l.getPresenter(container)
|
||||
s := p.colors(fmt.Sprintf("%s %s\n", container, msg))
|
||||
l.writer.Write([]byte(s)) //nolint:errcheck
|
||||
l.stdout.Write([]byte(s)) //nolint:errcheck
|
||||
}
|
||||
|
||||
func (l *logConsumer) computeWidth() {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.19.1"
|
||||
default = "1.19.4"
|
||||
}
|
||||
|
||||
variable "BUILD_TAGS" {
|
||||
@@ -71,7 +71,7 @@ target "vendor-validate" {
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor" {
|
||||
target "vendor-update" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
output = ["."]
|
||||
|
||||
@@ -42,6 +42,7 @@ Docker Compose
|
||||
| `--compatibility` | | | Run compose in backward compatibility mode |
|
||||
| `--env-file` | `string` | | Specify an alternate environment file. |
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file) |
|
||||
@@ -55,7 +56,7 @@ Docker Compose
|
||||
You can use compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
|
||||
### Use `-f` to specify name and path of one or more Compose files
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
@@ -118,8 +119,8 @@ Each configuration has a project name. If you supply a `-p` flag, you can specif
|
||||
specify the flag, Compose uses the current directory name.
|
||||
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
||||
|
||||
Most compose subcommand can be ran without a compose file, just passing
|
||||
project name to retrieve the relevant resources.
|
||||
Many Compose subcommands can be run without a Compose file by passing
|
||||
the project name.
|
||||
|
||||
```console
|
||||
$ docker compose -p my_project ps -a
|
||||
@@ -145,10 +146,10 @@ Profiles can also be set by `COMPOSE_PROFILES` environment variable.
|
||||
You can set environment variables for various docker compose options, including the `-f`, `-p` and `--profiles` flags.
|
||||
|
||||
Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f` flag,
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same for to the `-p` flag,
|
||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same as the `-p` flag,
|
||||
and `COMPOSE_PROFILES` environment variable is equivalent to the `--profiles` flag.
|
||||
|
||||
If flags are explicitly set on command line, associated environment variable is ignored
|
||||
If flags are explicitly set on the command line, the associated environment variable is ignored.
|
||||
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
|
||||
@@ -14,6 +14,7 @@ Converts the compose file to platform's canonical format
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | | | Print the image names, one per line. |
|
||||
| `--no-consistency` | | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-interpolate` | | | Don't interpolate environment variables. |
|
||||
| `--no-normalize` | | | Don't normalize compose model. |
|
||||
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
|
||||
@@ -28,8 +29,8 @@ Converts the compose file to platform's canonical format
|
||||
|
||||
## Description
|
||||
|
||||
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||
fully defined Compose model.
|
||||
`docker compose convert` renders the actual data model to be applied on the target platform. When used with the Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
|
||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||
|
||||
@@ -22,5 +22,5 @@ Execute a command in a running container.
|
||||
|
||||
This is the equivalent of `docker exec` targeting a Compose service.
|
||||
|
||||
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
|
||||
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
|
||||
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
||||
|
||||
@@ -7,6 +7,7 @@ List images used by the created containers
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
|
||||
| `-q`, `--quiet` | | | Only display IDs |
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ List running compose projects
|
||||
| --- | --- | --- | --- |
|
||||
| `-a`, `--all` | | | Show all stopped Compose projects |
|
||||
| `--filter` | `filter` | | Filter output based on conditions provided. |
|
||||
| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json]. |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
|
||||
| `-q`, `--quiet` | | | Only display IDs. |
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@ List running compose projects
|
||||
|
||||
## Description
|
||||
|
||||
List Compose projects running on platform.
|
||||
Lists running Compose projects.
|
||||
@@ -9,7 +9,7 @@ List containers
|
||||
| --- | --- | --- | --- |
|
||||
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
|
||||
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
|
||||
| [`--format`](#format) | `string` | `pretty` | Format the output. Values: [pretty \| json] |
|
||||
| [`--format`](#format) | `string` | `table` | Format the output. Values: [table \| json] |
|
||||
| `-q`, `--quiet` | | | Only display IDs |
|
||||
| `--services` | | | Display services |
|
||||
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
|
||||
@@ -35,7 +35,7 @@ example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->8
|
||||
|
||||
By default, the `docker compose ps` command uses a table ("pretty") format to
|
||||
show the containers. The `--format` flag allows you to specify alternative
|
||||
presentations for the output. Currently supported options are `pretty` (default),
|
||||
presentations for the output. Currently, supported options are `pretty` (default),
|
||||
and `json`, which outputs information about the containers as a JSON array:
|
||||
|
||||
```console
|
||||
@@ -85,7 +85,7 @@ $ docker compose ps --format json | jq .
|
||||
### <a name="status"></a> Filter containers by status (--status)
|
||||
|
||||
Use the `--status` flag to filter the list of containers by status. For example,
|
||||
to show only containers that are running, or only containers that have exited:
|
||||
to show only containers that are running or only containers that have exited:
|
||||
|
||||
```console
|
||||
$ docker compose ps --status=running
|
||||
@@ -99,7 +99,7 @@ example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||
|
||||
### <a name="filter"></a> Filter containers by status (--filter)
|
||||
|
||||
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
|
||||
The [`--status` flag](#status) is a convenient shorthand for the `--filter status=<status>`
|
||||
flag. The example below is the equivalent to the example from the previous section,
|
||||
this time using the `--filter` flag:
|
||||
|
||||
@@ -114,4 +114,4 @@ example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||
```
|
||||
|
||||
The `docker compose ps` command currently only supports the `--filter status=<status>`
|
||||
option, but additional filter options may be added in future.
|
||||
option, but additional filter options may be added in the future.
|
||||
|
||||
@@ -8,6 +8,8 @@ Push service images
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
|
||||
| `--include-deps` | | | Also push images of services declared as dependencies |
|
||||
| `-q`, `--quiet` | | | Push without printing progress information |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -7,6 +7,7 @@ Run a one-off command on a service.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--build` | | | Build image before starting container. |
|
||||
| `-d`, `--detach` | | | Run container in background and print container ID |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
|
||||
@@ -27,6 +27,7 @@ Create and start containers
|
||||
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. |
|
||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||
| `-t`, `--timeout` | `int` | `10` | Use this timeout in seconds for container shutdown when attached or when containers are already running. |
|
||||
| `--timestamps` | | | Show timestamps. |
|
||||
| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ long: |-
|
||||
You can use compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
|
||||
### Use `-f` to specify name and path of one or more Compose files
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
@@ -67,8 +67,8 @@ long: |-
|
||||
specify the flag, Compose uses the current directory name.
|
||||
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
||||
|
||||
Most compose subcommand can be ran without a compose file, just passing
|
||||
project name to retrieve the relevant resources.
|
||||
Many Compose subcommands can be run without a Compose file by passing
|
||||
the project name.
|
||||
|
||||
```console
|
||||
$ docker compose -p my_project ps -a
|
||||
@@ -94,10 +94,10 @@ long: |-
|
||||
You can set environment variables for various docker compose options, including the `-f`, `-p` and `--profiles` flags.
|
||||
|
||||
Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f` flag,
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same for to the `-p` flag,
|
||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same as the `-p` flag,
|
||||
and `COMPOSE_PROFILES` environment variable is equivalent to the `--profiles` flag.
|
||||
|
||||
If flags are explicitly set on command line, associated environment variable is ignored
|
||||
If flags are explicitly set on the command line, the associated environment variable is ignored.
|
||||
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
@@ -208,6 +208,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: parallel
|
||||
value_type: int
|
||||
default_value: "-1"
|
||||
description: Control max parallelism, -1 for unlimited
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: profile
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
|
||||
@@ -2,9 +2,9 @@ command: docker compose convert
|
||||
aliases: docker compose convert, docker compose config
|
||||
short: Converts the compose file to platform's canonical format
|
||||
long: |-
|
||||
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||
fully defined Compose model.
|
||||
`docker compose convert` renders the actual data model to be applied on the target platform. When used with the Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
|
||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||
usage: docker compose convert [OPTIONS] [SERVICE...]
|
||||
@@ -40,6 +40,17 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-consistency
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: |
|
||||
Don't check model consistency - warning: may produce invalid Compose output
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-interpolate
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
||||
@@ -3,7 +3,7 @@ short: Execute a command in a running container.
|
||||
long: |-
|
||||
This is the equivalent of `docker exec` targeting a Compose service.
|
||||
|
||||
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
|
||||
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
|
||||
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
||||
usage: docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]
|
||||
pname: docker compose
|
||||
|
||||
@@ -5,6 +5,16 @@ usage: docker compose images [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: table
|
||||
description: 'Format the output. Values: [table | json].'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
command: docker compose ls
|
||||
short: List running compose projects
|
||||
long: List Compose projects running on platform.
|
||||
long: Lists running Compose projects.
|
||||
usage: docker compose ls [OPTIONS]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
@@ -27,8 +27,8 @@ options:
|
||||
swarm: false
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: pretty
|
||||
description: 'Format the output. Values: [pretty | json].'
|
||||
default_value: table
|
||||
description: 'Format the output. Values: [table | json].'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
|
||||
@@ -38,8 +38,8 @@ options:
|
||||
swarm: false
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: pretty
|
||||
description: 'Format the output. Values: [pretty | json]'
|
||||
default_value: table
|
||||
description: 'Format the output. Values: [table | json]'
|
||||
details_url: '#format'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
@@ -85,7 +85,7 @@ examples: |-
|
||||
|
||||
By default, the `docker compose ps` command uses a table ("pretty") format to
|
||||
show the containers. The `--format` flag allows you to specify alternative
|
||||
presentations for the output. Currently supported options are `pretty` (default),
|
||||
presentations for the output. Currently, supported options are `pretty` (default),
|
||||
and `json`, which outputs information about the containers as a JSON array:
|
||||
|
||||
```console
|
||||
@@ -135,7 +135,7 @@ examples: |-
|
||||
### Filter containers by status (--status) {#status}
|
||||
|
||||
Use the `--status` flag to filter the list of containers by status. For example,
|
||||
to show only containers that are running, or only containers that have exited:
|
||||
to show only containers that are running or only containers that have exited:
|
||||
|
||||
```console
|
||||
$ docker compose ps --status=running
|
||||
@@ -149,7 +149,7 @@ examples: |-
|
||||
|
||||
### Filter containers by status (--filter) {#filter}
|
||||
|
||||
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
|
||||
The [`--status` flag](#status) is a convenient shorthand for the `--filter status=<status>`
|
||||
flag. The example below is the equivalent to the example from the previous section,
|
||||
this time using the `--filter` flag:
|
||||
|
||||
@@ -164,7 +164,7 @@ examples: |-
|
||||
```
|
||||
|
||||
The `docker compose ps` command currently only supports the `--filter status=<status>`
|
||||
option, but additional filter options may be added in future.
|
||||
option, but additional filter options may be added in the future.
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
||||
@@ -33,6 +33,27 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: include-deps
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Also push images of services declared as dependencies
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
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
|
||||
|
||||
@@ -58,6 +58,16 @@ usage: docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: build
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Build image before starting container.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: detach
|
||||
shorthand: d
|
||||
value_type: bool
|
||||
|
||||
@@ -230,6 +230,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: timestamps
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Show timestamps.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: wait
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
||||
5
e2e/cucumber-features/down.feature
Normal file
5
e2e/cucumber-features/down.feature
Normal 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""
|
||||
15
e2e/cucumber-features/simple.feature
Normal file
15
e2e/cucumber-features/simple.feature
Normal 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"
|
||||
21
e2e/cucumber-features/start.feature
Normal file
21
e2e/cucumber-features/start.feature
Normal 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"
|
||||
31
e2e/cucumber-features/stop.feature
Normal file
31
e2e/cucumber-features/stop.feature
Normal file
@@ -0,0 +1,31 @@
|
||||
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
|
||||
init: true
|
||||
"""
|
||||
|
||||
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 143
|
||||
|
||||
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
|
||||
23
e2e/cucumber-features/volume-tmpfs.feature
Normal file
23
e2e/cucumber-features/volume-tmpfs.feature
Normal file
@@ -0,0 +1,23 @@
|
||||
Feature: Volume: tmpfs
|
||||
|
||||
Background:
|
||||
Given a compose file
|
||||
"""
|
||||
services:
|
||||
svc:
|
||||
image: busybox
|
||||
volumes:
|
||||
- type: tmpfs
|
||||
target: /volumes/tmpfs
|
||||
tmpfs:
|
||||
size: 2M
|
||||
mode: 0o647
|
||||
"""
|
||||
|
||||
Scenario: tmpfs Permissions Set
|
||||
When I run "compose run --rm svc stat -c "%a" /volumes/tmpfs"
|
||||
Then the output contains "647"
|
||||
|
||||
Scenario: tmpfs Size Set
|
||||
When I run "compose run --rm svc sh -c 'df /volumes/tmpfs | tail -n1 | awk '"'"'{print $4}'"'"'' "
|
||||
Then the output contains "2048"
|
||||
135
e2e/cucumber_test.go
Normal file
135
e2e/cucumber_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
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/mattn/go-shellwords"
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/e2e"
|
||||
)
|
||||
|
||||
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, err := shellwords.Parse(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commandArgs = append([]string{"-f", "-"}, commandArgs...)
|
||||
|
||||
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
|
||||
}
|
||||
117
go.mod
117
go.mod
@@ -5,15 +5,14 @@ go 1.19
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/cnabio/cnab-to-oci v0.3.7
|
||||
github.com/compose-spec/compose-go v1.6.0
|
||||
github.com/compose-spec/compose-go v1.8.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/containerd/containerd v1.6.14
|
||||
github.com/distribution/distribution/v3 v3.0.0-20221201083218-92d136e113cf
|
||||
github.com/docker/buildx v0.9.1 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.20+incompatible // replaced; see replace rule for actual version
|
||||
github.com/docker/cli-docs-tool v0.5.0
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/docker/docker v20.10.20+incompatible // replaced; see replace rule for actual version
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/golang/mock v1.6.0
|
||||
@@ -21,36 +20,36 @@ require (
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.10.4
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae
|
||||
github.com/moby/buildkit v0.10.4 // replaced; see replace rule for actual version
|
||||
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f
|
||||
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.1.0-rc2
|
||||
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.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||
go.opentelemetry.io/otel v1.11.2
|
||||
golang.org/x/sync v0.1.0
|
||||
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
|
||||
k8s.io/client-go v0.24.1 // replaced; see replace for the actual version used
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // 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/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8 // 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
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
@@ -61,99 +60,111 @@ require (
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // 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/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/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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/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/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/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // 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
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||
github.com/zmap/zlint v1.1.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.10.0
|
||||
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.10.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.2 // 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
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // 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.45.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
|
||||
k8s.io/client-go v0.24.1 // see replace for the actual version used
|
||||
k8s.io/api v0.24.1 // indirect; replaced; see replace for the actual version used
|
||||
k8s.io/apimachinery v0.24.1 // indirect; replaced; see replace for the actual version used
|
||||
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
|
||||
)
|
||||
|
||||
require github.com/cucumber/godog v0.0.0-00010101000000-000000000000
|
||||
|
||||
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/moby/spdystream 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
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry 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
|
||||
k8s.io/api v0.24.1 // indirect
|
||||
github.com/bugsnag/bugsnag-go v1.5.0 // indirect
|
||||
github.com/cloudflare/cfssl v1.4.1 // indirect
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/jinzhu/gorm v1.9.11 // indirect
|
||||
github.com/spf13/viper v1.4.0 // indirect
|
||||
)
|
||||
|
||||
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
|
||||
// Override for e2e tests
|
||||
github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
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.20221021173910-5aac513617f0+incompatible // 22.06 branch
|
||||
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
|
||||
|
||||
// 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
|
||||
// https://github.com/docker/buildx/blob/v0.9.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
|
||||
|
||||
@@ -72,9 +72,11 @@ type Service interface {
|
||||
// Events executes the equivalent to a `compose events`
|
||||
Events(ctx context.Context, projectName string, options EventsOptions) error
|
||||
// Port executes the equivalent to a `compose port`
|
||||
Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
|
||||
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
|
||||
// Images executes the equivalent of a `compose images`
|
||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
// MaxConcurrency defines upper limit for concurrent operations against engine API
|
||||
MaxConcurrency(parallel int)
|
||||
}
|
||||
|
||||
// BuildOptions group options of the Build API
|
||||
@@ -179,10 +181,13 @@ type ConvertOptions struct {
|
||||
Format string
|
||||
// Output defines the path to save the application model
|
||||
Output string
|
||||
// Resolve image reference to digests
|
||||
ResolveImageDigests bool
|
||||
}
|
||||
|
||||
// PushOptions group options of the Push API
|
||||
type PushOptions struct {
|
||||
Quiet bool
|
||||
IgnoreFailures bool
|
||||
}
|
||||
|
||||
@@ -313,10 +318,13 @@ type PortPublisher struct {
|
||||
type ContainerSummary struct {
|
||||
ID string
|
||||
Name string
|
||||
Image any
|
||||
Command string
|
||||
Project string
|
||||
Service string
|
||||
Created int64
|
||||
State string
|
||||
Status string
|
||||
Health string
|
||||
ExitCode int
|
||||
Publishers PortPublishers
|
||||
@@ -432,7 +440,8 @@ type Stack struct {
|
||||
|
||||
// LogConsumer is a callback to process log messages from services
|
||||
type LogConsumer interface {
|
||||
Log(containerName, service, message string)
|
||||
Log(containerName, message string)
|
||||
Err(containerName, message string)
|
||||
Status(container, msg string)
|
||||
Register(container string)
|
||||
}
|
||||
@@ -456,8 +465,10 @@ type ContainerEvent struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// ContainerEventLog is a ContainerEvent of type log. Line is set
|
||||
// ContainerEventLog is a ContainerEvent of type log on stdout. Line is set
|
||||
ContainerEventLog = iota
|
||||
// ContainerEventErr is a ContainerEvent of type log on stderr. Line is set
|
||||
ContainerEventErr
|
||||
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
|
||||
ContainerEventAttach
|
||||
// ContainerEventStopped is a ContainerEvent of type stopped.
|
||||
|
||||
@@ -48,8 +48,9 @@ type ServiceProxy struct {
|
||||
UnPauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||
TopFn func(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||
EventsFn func(ctx context.Context, project string, options EventsOptions) error
|
||||
PortFn func(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
|
||||
PortFn func(ctx context.Context, project string, service string, port uint16, options PortOptions) (string, int, error)
|
||||
ImagesFn func(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
MaxConcurrencyFn func(parallel int)
|
||||
interceptors []Interceptor
|
||||
}
|
||||
|
||||
@@ -87,6 +88,7 @@ func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
|
||||
s.EventsFn = service.Events
|
||||
s.PortFn = service.Port
|
||||
s.ImagesFn = service.Images
|
||||
s.MaxConcurrencyFn = service.MaxConcurrency
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -294,7 +296,7 @@ func (s *ServiceProxy) Events(ctx context.Context, projectName string, options E
|
||||
}
|
||||
|
||||
// Port implements Service interface
|
||||
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
|
||||
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error) {
|
||||
if s.PortFn == nil {
|
||||
return "", 0, ErrNotImplemented
|
||||
}
|
||||
@@ -308,3 +310,7 @@ func (s *ServiceProxy) Images(ctx context.Context, project string, options Image
|
||||
}
|
||||
return s.ImagesFn(ctx, project, options)
|
||||
}
|
||||
|
||||
func (s *ServiceProxy) MaxConcurrency(i int) {
|
||||
s.MaxConcurrencyFn(i)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
containers.sorted() // This enforce predictable colors assignment
|
||||
|
||||
@@ -66,7 +69,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||
Service: serviceName,
|
||||
})
|
||||
|
||||
w := utils.GetWriter(func(line string) {
|
||||
wOut := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventLog,
|
||||
Container: containerName,
|
||||
@@ -74,13 +77,21 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
wErr := utils.GetWriter(func(line string) {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventErr,
|
||||
Container: containerName,
|
||||
Service: serviceName,
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
|
||||
inspect, err := s.dockerCli.Client().ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, w, w)
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, wOut, wErr)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -48,8 +48,6 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
||||
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
|
||||
opts := map[string]build.Options{}
|
||||
var imagesToBuild []string
|
||||
|
||||
args := flatten(options.Args.Resolve(envResolver(project.Environment)))
|
||||
|
||||
services, err := project.GetServices(options.Services...)
|
||||
@@ -62,7 +60,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
continue
|
||||
}
|
||||
imageName := api.GetImageNameOrDefault(service, project.Name)
|
||||
imagesToBuild = append(imagesToBuild, imageName)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -97,12 +94,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
}
|
||||
|
||||
_, err = s.doBuild(ctx, project, opts, options.Progress)
|
||||
if err == nil {
|
||||
if len(imagesToBuild) > 0 && !options.Quiet {
|
||||
utils.DisplayScanSuggestMsg()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -136,9 +127,6 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
return err
|
||||
}
|
||||
|
||||
if len(builtImages) > 0 {
|
||||
utils.DisplayScanSuggestMsg()
|
||||
}
|
||||
for name, digest := range builtImages {
|
||||
images[name] = digest
|
||||
}
|
||||
@@ -249,7 +237,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...))
|
||||
@@ -415,8 +403,8 @@ func useDockerDefaultOrServicePlatform(project *types.Project, service types.Ser
|
||||
return plats, err
|
||||
}
|
||||
|
||||
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,15 +28,15 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"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"
|
||||
|
||||
|
||||
@@ -23,16 +23,17 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
@@ -40,12 +41,14 @@ import (
|
||||
// NewComposeService create a local implementation of the compose.Service API
|
||||
func NewComposeService(dockerCli command.Cli) api.Service {
|
||||
return &composeService{
|
||||
dockerCli: dockerCli,
|
||||
dockerCli: dockerCli,
|
||||
maxConcurrency: -1,
|
||||
}
|
||||
}
|
||||
|
||||
type composeService struct {
|
||||
dockerCli command.Cli
|
||||
dockerCli command.Cli
|
||||
maxConcurrency int
|
||||
}
|
||||
|
||||
func (s *composeService) apiClient() client.APIClient {
|
||||
@@ -56,6 +59,10 @@ func (s *composeService) configFile() *configfile.ConfigFile {
|
||||
return s.dockerCli.ConfigFile()
|
||||
}
|
||||
|
||||
func (s *composeService) MaxConcurrency(i int) {
|
||||
s.maxConcurrency = i
|
||||
}
|
||||
|
||||
func (s *composeService) stdout() *streams.Out {
|
||||
return s.dockerCli.Out()
|
||||
}
|
||||
@@ -93,13 +100,34 @@ func getContainerNameWithoutProject(c moby.Container) string {
|
||||
}
|
||||
|
||||
func (s *composeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) {
|
||||
if options.ResolveImageDigests {
|
||||
info, err := s.apiClient().Info(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
|
||||
auth, err := encodedAuth(named, info, s.configFile())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return inspect.Descriptor.Digest, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch options.Format {
|
||||
case "json":
|
||||
return json.MarshalIndent(project, "", " ")
|
||||
case "yaml":
|
||||
return yaml.Marshal(project)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format %q", options)
|
||||
return nil, fmt.Errorf("unsupported format %q", options.Format)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,3 +141,14 @@ func (containers Containers) sorted() Containers {
|
||||
})
|
||||
return containers
|
||||
}
|
||||
|
||||
func (containers Containers) remove(id string) Containers {
|
||||
for i, c := range containers {
|
||||
if c.ID == id {
|
||||
l := len(containers) - 1
|
||||
containers[i] = containers[l]
|
||||
return containers[:l]
|
||||
}
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@ 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"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -125,17 +127,13 @@ func updateServices(service *types.ServiceConfig, cnts Containers) {
|
||||
if len(cnts) == 0 {
|
||||
return
|
||||
}
|
||||
cnt := cnts[0]
|
||||
serviceName := cnt.Labels[api.ServiceLabel]
|
||||
|
||||
if d := getDependentServiceFromMode(service.NetworkMode); d == serviceName {
|
||||
service.NetworkMode = types.NetworkModeContainerPrefix + cnt.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(service.Ipc); d == serviceName {
|
||||
service.Ipc = types.NetworkModeContainerPrefix + cnt.ID
|
||||
}
|
||||
if d := getDependentServiceFromMode(service.Pid); d == serviceName {
|
||||
service.Pid = types.NetworkModeContainerPrefix + cnt.ID
|
||||
for _, str := range []*string{&service.NetworkMode, &service.Ipc, &service.Pid} {
|
||||
if d := getDependentServiceFromMode(*str); d != "" {
|
||||
if serviceContainers := cnts.filter(isService(d)); len(serviceContainers) > 0 {
|
||||
*str = types.NetworkModeContainerPrefix + serviceContainers[0].ID
|
||||
}
|
||||
}
|
||||
}
|
||||
var links []string
|
||||
for _, serviceLink := range service.Links {
|
||||
@@ -180,7 +178,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
|
||||
}
|
||||
@@ -222,10 +223,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
updated[i] = container
|
||||
}
|
||||
|
||||
next, err := nextContainerNumber(containers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next := nextContainerNumber(containers)
|
||||
for i := 0; i < expected-actual; i++ {
|
||||
// Scale UP
|
||||
number := next + i
|
||||
@@ -280,7 +278,7 @@ func containerEvents(containers Containers, eventFunc func(string) progress.Even
|
||||
return events
|
||||
}
|
||||
|
||||
// ServiceConditionRunningOrHealthy is a service condition on statys running or healthy
|
||||
// ServiceConditionRunningOrHealthy is a service condition on status running or healthy
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
|
||||
@@ -318,7 +316,8 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
case types.ServiceConditionHealthy:
|
||||
healthy, err := s.isServiceHealthy(ctx, project, dep, false)
|
||||
if err != nil {
|
||||
return err
|
||||
w.Events(containerEvents(containers, progress.ErrorEvent))
|
||||
return errors.Wrap(err, "dependency failed to start")
|
||||
}
|
||||
if healthy {
|
||||
w.Events(containerEvents(containers, progress.Healthy))
|
||||
@@ -366,18 +365,23 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func nextContainerNumber(containers []moby.Container) (int, error) {
|
||||
func nextContainerNumber(containers []moby.Container) int {
|
||||
max := 0
|
||||
for _, c := range containers {
|
||||
n, err := strconv.Atoi(c.Labels[api.ContainerNumberLabel])
|
||||
s, ok := c.Labels[api.ContainerNumberLabel]
|
||||
if !ok {
|
||||
logrus.Warnf("container %s is missing %s label", c.ID, api.ContainerNumberLabel)
|
||||
}
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
logrus.Warnf("container %s has invalid %s label: %s", c.ID, api.ContainerNumberLabel, s)
|
||||
continue
|
||||
}
|
||||
if n > max {
|
||||
max = n
|
||||
}
|
||||
}
|
||||
return max + 1, nil
|
||||
return max + 1
|
||||
|
||||
}
|
||||
|
||||
@@ -399,7 +403,7 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := "Container " + name
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
container, err = s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases, attachStdin)
|
||||
container, err = s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases, attachStdin, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -432,7 +437,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
inherited = &replaced
|
||||
}
|
||||
name = getContainerName(project.Name, service, number)
|
||||
created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false)
|
||||
created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false, w)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -470,16 +475,20 @@ func (s *composeService) startContainer(ctx context.Context, container moby.Cont
|
||||
}
|
||||
|
||||
func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
|
||||
name string, number int, inherit *moby.Container, autoRemove bool, useNetworkAliases bool, attachStdin bool) (moby.Container, error) {
|
||||
name string, number int, inherit *moby.Container, autoRemove bool, useNetworkAliases bool, attachStdin bool, w progress.Writer) (moby.Container, error) {
|
||||
var created moby.Container
|
||||
containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, inherit, autoRemove, attachStdin)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
platform := service.Platform
|
||||
if platform == "" {
|
||||
platform = project.Environment["DOCKER_DEFAULT_PLATFORM"]
|
||||
}
|
||||
var plat *specs.Platform
|
||||
if service.Platform != "" {
|
||||
if platform != "" {
|
||||
var p specs.Platform
|
||||
p, err = platforms.Parse(service.Platform)
|
||||
p, err = platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@@ -489,6 +498,13 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
for _, warning := range response.Warnings {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Warning,
|
||||
Text: warning,
|
||||
})
|
||||
}
|
||||
inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
|
||||
if err != nil {
|
||||
return created, err
|
||||
@@ -630,7 +646,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
||||
}
|
||||
|
||||
func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Project, service string, fallbackRunning bool) (bool, error) {
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, service)
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, service)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -648,6 +664,10 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||
return container.State != nil && container.State.Status == "running", nil
|
||||
}
|
||||
|
||||
if container.State.Status == "exited" {
|
||||
return false, fmt.Errorf("container for service %q exited (%d)", service, container.State.ExitCode)
|
||||
}
|
||||
|
||||
if container.State == nil || container.State.Health == nil {
|
||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||
}
|
||||
@@ -711,21 +731,17 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
}
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, container := range containers {
|
||||
if container.State == ContainerRunning {
|
||||
continue
|
||||
}
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StartingEvent(eventName))
|
||||
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
||||
if err == nil {
|
||||
w.Event(progress.StartedEvent(eventName))
|
||||
}
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StartingEvent(eventName))
|
||||
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
})
|
||||
}
|
||||
w.Event(progress.StartedEvent(eventName))
|
||||
}
|
||||
return eg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
@@ -42,6 +41,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -259,7 +260,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
stdinOpen = service.StdinOpen
|
||||
)
|
||||
|
||||
volumeMounts, binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
|
||||
binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -288,7 +289,6 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
StopSignal: service.StopSignal,
|
||||
Env: ToMobyEnv(env),
|
||||
Healthcheck: ToMobyHealthCheck(service.HealthCheck),
|
||||
Volumes: volumeMounts,
|
||||
StopTimeout: ToSeconds(service.StopGracePeriod),
|
||||
}
|
||||
|
||||
@@ -397,6 +397,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
LogConfig: logConfig,
|
||||
GroupAdd: service.GroupAdd,
|
||||
Links: links,
|
||||
OomScoreAdj: int(service.OomScoreAdj),
|
||||
}
|
||||
|
||||
return &containerConfig, &hostConfig, networkConfig, nil
|
||||
@@ -488,13 +489,27 @@ func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
|
||||
attempts = int(*policy.MaxAttempts)
|
||||
}
|
||||
restart = container.RestartPolicy{
|
||||
Name: policy.Condition,
|
||||
Name: mapRestartPolicyCondition(policy.Condition),
|
||||
MaximumRetryCount: attempts,
|
||||
}
|
||||
}
|
||||
return restart
|
||||
}
|
||||
|
||||
func mapRestartPolicyCondition(condition string) string {
|
||||
// map definitions of deploy.restart_policy to engine definitions
|
||||
switch condition {
|
||||
case "none", "no":
|
||||
return "no"
|
||||
case "on-failure", "unless-stopped":
|
||||
return condition
|
||||
case "any", "always":
|
||||
return "always"
|
||||
default:
|
||||
return condition
|
||||
}
|
||||
}
|
||||
|
||||
func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
var swappiness *int64
|
||||
if s.MemSwappiness != 0 {
|
||||
@@ -514,7 +529,8 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||
CPURealtimePeriod: s.CPURTPeriod,
|
||||
CPURealtimeRuntime: s.CPURTRuntime,
|
||||
CPUShares: s.CPUShares,
|
||||
CPUPercent: int64(s.CPUS * 100),
|
||||
NanoCPUs: int64(s.CPUS * 1e9),
|
||||
CPUPercent: int64(s.CPUPercent * 100),
|
||||
CpusetCpus: s.CPUSet,
|
||||
DeviceCgroupRules: s.DeviceCgroupRules,
|
||||
}
|
||||
@@ -578,6 +594,12 @@ func setReservations(reservations *types.Resource, resources *container.Resource
|
||||
if reservations == nil {
|
||||
return
|
||||
}
|
||||
// Cpu reservation is a swarm option and PIDs is only a limit
|
||||
// So we only need to map memory reservation and devices
|
||||
if reservations.MemoryBytes != 0 {
|
||||
resources.MemoryReservation = int64(reservations.MemoryBytes)
|
||||
}
|
||||
|
||||
for _, device := range reservations.Devices {
|
||||
resources.DeviceRequests = append(resources.DeviceRequests, container.DeviceRequest{
|
||||
Capabilities: [][]string{device.Capabilities},
|
||||
@@ -709,30 +731,32 @@ func getDependentServiceFromMode(mode string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
|
||||
inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
|
||||
var mounts = []mount.Mount{}
|
||||
func (s *composeService) buildContainerVolumes(
|
||||
ctx context.Context,
|
||||
p types.Project,
|
||||
service types.ServiceConfig,
|
||||
inherit *moby.Container,
|
||||
) ([]string, []mount.Mount, error) {
|
||||
var mounts []mount.Mount
|
||||
var binds []string
|
||||
|
||||
image := api.GetImageNameOrDefault(service, p.Name)
|
||||
imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
volumeMounts := map[string]struct{}{}
|
||||
binds := []string{}
|
||||
MOUNTS:
|
||||
for _, m := range mountOptions {
|
||||
if m.Type == mount.TypeNamedPipe {
|
||||
mounts = append(mounts, m)
|
||||
continue
|
||||
}
|
||||
volumeMounts[m.Target] = struct{}{}
|
||||
if m.Type == mount.TypeBind {
|
||||
// `Mount` is preferred but does not offer option to created host path if missing
|
||||
// so `Bind` API is used here with raw volume string
|
||||
@@ -752,7 +776,7 @@ MOUNTS:
|
||||
}
|
||||
mounts = append(mounts, m)
|
||||
}
|
||||
return volumeMounts, binds, mounts, nil
|
||||
return binds, mounts, nil
|
||||
}
|
||||
|
||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
||||
@@ -968,7 +992,7 @@ func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig)
|
||||
logrus.Warnf("mount of type `bind` should not define `volume` option")
|
||||
}
|
||||
if volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `tmpfs` option")
|
||||
logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
|
||||
}
|
||||
return buildBindOption(volume.Bind), nil, nil
|
||||
case "volume":
|
||||
@@ -1023,7 +1047,7 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
||||
}
|
||||
return &mount.TmpfsOptions{
|
||||
SizeBytes: int64(tmpfs.Size),
|
||||
// Mode: , // FIXME missing from model ?
|
||||
Mode: os.FileMode(tmpfs.Mode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,7 +1179,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,
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var project = types.Project{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,7 +19,7 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func Test_EnvResolverWithCase(t *testing.T) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
@@ -94,10 +95,13 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
|
||||
tag := ""
|
||||
repository := ""
|
||||
if len(inspect.RepoTags) > 0 {
|
||||
repotag := strings.Split(inspect.RepoTags[0], ":")
|
||||
repository = repotag[0]
|
||||
if len(repotag) > 1 {
|
||||
tag = repotag[1]
|
||||
ref, err := reference.ParseDockerRef(inspect.RepoTags[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repository = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
l.Lock()
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -66,18 +66,20 @@ func (s *composeService) Logs(
|
||||
if options.Follow {
|
||||
printer := newLogPrinter(consumer)
|
||||
eg.Go(func() error {
|
||||
for _, c := range containers {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
}
|
||||
return nil
|
||||
_, err := printer.Run(false, "", nil)
|
||||
return err
|
||||
})
|
||||
|
||||
for _, c := range containers {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
|
||||
err := s.watchContainers(ctx, projectName, options.Services, nil, printer.HandleEvent, containers, func(c types.Container) error {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
@@ -85,10 +87,7 @@ func (s *composeService) Logs(
|
||||
})
|
||||
return s.logContainers(ctx, consumer, c, options)
|
||||
})
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
_, err := printer.Run(ctx, false, "", nil)
|
||||
printer.Stop()
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -102,7 +101,6 @@ func (s *composeService) logContainers(ctx context.Context, consumer api.LogCons
|
||||
return err
|
||||
}
|
||||
|
||||
service := c.Labels[api.ServiceLabel]
|
||||
r, err := s.apiClient().ContainerLogs(ctx, cnt.ID, types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
@@ -119,7 +117,7 @@ func (s *composeService) logContainers(ctx context.Context, consumer api.LogCons
|
||||
|
||||
name := getContainerNameWithoutProject(c)
|
||||
w := utils.GetWriter(func(line string) {
|
||||
consumer.Log(name, service, line)
|
||||
consumer.Log(name, line)
|
||||
})
|
||||
if cnt.Config.Tty {
|
||||
_, err = io.Copy(w, r)
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestComposeService_Logs_Demux(t *testing.T) {
|
||||
require.Equal(
|
||||
t,
|
||||
[]string{"hello stdout", "hello stderr"},
|
||||
consumer.LogsForContainer("service", "c"),
|
||||
consumer.LogsForContainer("c"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -169,36 +169,37 @@ func TestComposeService_Logs_ServiceFiltering(t *testing.T) {
|
||||
err := tested.Logs(ctx, name, consumer, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, []string{"hello c1"}, consumer.LogsForContainer("serviceA", "c1"))
|
||||
require.Equal(t, []string{"hello c2"}, consumer.LogsForContainer("serviceA", "c2"))
|
||||
require.Empty(t, consumer.LogsForContainer("serviceB", "c3"))
|
||||
require.Equal(t, []string{"hello c4"}, consumer.LogsForContainer("serviceC", "c4"))
|
||||
require.Equal(t, []string{"hello c1"}, consumer.LogsForContainer("c1"))
|
||||
require.Equal(t, []string{"hello c2"}, consumer.LogsForContainer("c2"))
|
||||
require.Empty(t, consumer.LogsForContainer("c3"))
|
||||
require.Equal(t, []string{"hello c4"}, consumer.LogsForContainer("c4"))
|
||||
}
|
||||
|
||||
type testLogConsumer struct {
|
||||
mu sync.Mutex
|
||||
// logs is keyed by service, then container; values are log lines
|
||||
logs map[string]map[string][]string
|
||||
// logs is keyed container; values are log lines
|
||||
logs map[string][]string
|
||||
}
|
||||
|
||||
func (l *testLogConsumer) Log(containerName, service, message string) {
|
||||
func (l *testLogConsumer) Log(containerName, message string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.logs == nil {
|
||||
l.logs = make(map[string]map[string][]string)
|
||||
l.logs = make(map[string][]string)
|
||||
}
|
||||
if l.logs[service] == nil {
|
||||
l.logs[service] = make(map[string][]string)
|
||||
}
|
||||
l.logs[service][containerName] = append(l.logs[service][containerName], message)
|
||||
l.logs[containerName] = append(l.logs[containerName], message)
|
||||
}
|
||||
|
||||
func (l *testLogConsumer) Err(containerName, message string) {
|
||||
l.Log(containerName, message)
|
||||
}
|
||||
|
||||
func (l *testLogConsumer) Status(containerName, msg string) {}
|
||||
|
||||
func (l *testLogConsumer) Register(containerName string) {}
|
||||
|
||||
func (l *testLogConsumer) LogsForContainer(svc string, containerName string) []string {
|
||||
func (l *testLogConsumer) LogsForContainer(containerName string) []string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.logs[svc][containerName]
|
||||
return l.logs[containerName]
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func (s *composeService) Port(ctx context.Context, projectName string, service string, port int, options api.PortOptions) (string, int, error) {
|
||||
func (s *composeService) Port(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (string, int, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
@@ -40,13 +40,27 @@ 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 {
|
||||
if p.PrivatePort == uint16(port) && p.Type == options.Protocol {
|
||||
if p.PrivatePort == port && p.Type == options.Protocol {
|
||||
return p.IP, int(p.PublicPort), nil
|
||||
}
|
||||
}
|
||||
return "", 0, err
|
||||
return "", 0, portNotFoundError(options.Protocol, port, container)
|
||||
}
|
||||
|
||||
func portNotFoundError(protocol string, port uint16, ctr moby.Container) error {
|
||||
formatPort := func(protocol string, port uint16) string {
|
||||
return fmt.Sprintf("%d/%s", port, protocol)
|
||||
}
|
||||
|
||||
var containerPorts []string
|
||||
for _, p := range ctr.Ports {
|
||||
containerPorts = append(containerPorts, formatPort(p.Type, p.PublicPort))
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(ctr.Names[0], "/")
|
||||
return fmt.Errorf("no port %s for container %s: %s", formatPort(protocol, port), name, strings.Join(containerPorts, ", "))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -26,21 +25,25 @@ import (
|
||||
// logPrinter watch application containers an collect their logs
|
||||
type logPrinter interface {
|
||||
HandleEvent(event api.ContainerEvent)
|
||||
Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error)
|
||||
Cancel()
|
||||
Stop()
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
queue chan api.ContainerEvent
|
||||
consumer api.LogConsumer
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// newLogPrinter builds a LogPrinter passing containers logs to LogConsumer
|
||||
func newLogPrinter(consumer api.LogConsumer) logPrinter {
|
||||
queue := make(chan api.ContainerEvent)
|
||||
stopCh := make(chan struct{}, 1) // printer MAY stop on his own, so Stop MUST not be blocking
|
||||
printer := printer{
|
||||
consumer: consumer,
|
||||
queue: queue,
|
||||
stopCh: stopCh,
|
||||
}
|
||||
return &printer
|
||||
}
|
||||
@@ -51,12 +54,16 @@ func (p *printer) Cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printer) Stop() {
|
||||
p.stopCh <- struct{}{}
|
||||
}
|
||||
|
||||
func (p *printer) HandleEvent(event api.ContainerEvent) {
|
||||
p.queue <- event
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error) (int, error) {
|
||||
var (
|
||||
aborting bool
|
||||
exitCode int
|
||||
@@ -64,8 +71,8 @@ func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string
|
||||
containers := map[string]struct{}{}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return exitCode, ctx.Err()
|
||||
case <-p.stopCh:
|
||||
return exitCode, nil
|
||||
case event := <-p.queue:
|
||||
container := event.Container
|
||||
switch event.Type {
|
||||
@@ -108,7 +115,11 @@ func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string
|
||||
}
|
||||
case api.ContainerEventLog:
|
||||
if !aborting {
|
||||
p.consumer.Log(container, event.Service, event.Line)
|
||||
p.consumer.Log(container, event.Line)
|
||||
}
|
||||
case api.ContainerEventErr:
|
||||
if !aborting {
|
||||
p.consumer.Err(container, event.Line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
||||
if options.All {
|
||||
oneOff = oneOffInclude
|
||||
}
|
||||
containers, err := s.getContainers(ctx, projectName, oneOff, true, options.Services...)
|
||||
containers, err := s.getContainers(ctx, projectName, oneOff, options.All, options.Services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,10 +91,13 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
||||
summary[i] = api.ContainerSummary{
|
||||
ID: container.ID,
|
||||
Name: getCanonicalContainerName(container),
|
||||
Image: container.Image,
|
||||
Project: container.Labels[api.ProjectLabel],
|
||||
Service: container.Labels[api.ServiceLabel],
|
||||
Command: container.Command,
|
||||
State: container.State,
|
||||
Status: container.Status,
|
||||
Created: container.Created,
|
||||
Health: health,
|
||||
ExitCode: exitCode,
|
||||
Publishers: publishers,
|
||||
|
||||
@@ -46,12 +46,12 @@ func TestPs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
|
||||
args.Add("label", "com.docker.compose.oneoff=False")
|
||||
listOpts := moby.ContainerListOptions{Filters: args, All: true}
|
||||
listOpts := moby.ContainerListOptions{Filters: args, All: false}
|
||||
c1, inspect1 := containerDetails("service1", "123", "running", "healthy", 0)
|
||||
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)
|
||||
@@ -61,10 +61,13 @@ func TestPs(t *testing.T) {
|
||||
containers, err := tested.Ps(ctx, strings.ToLower(testProject), compose.PsOptions{})
|
||||
|
||||
expected := []compose.ContainerSummary{
|
||||
{ID: "123", Name: "123", Project: strings.ToLower(testProject), Service: "service1", State: "running", Health: "healthy", Publishers: nil},
|
||||
{ID: "456", Name: "456", Project: strings.ToLower(testProject), Service: "service1", State: "running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90,
|
||||
PublishedPort: 80}}},
|
||||
{ID: "789", Name: "789", Project: strings.ToLower(testProject), Service: "service2", State: "exited", Health: "", ExitCode: 130, Publishers: nil},
|
||||
{ID: "123", Name: "123", Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
State: "running", Health: "healthy", Publishers: nil},
|
||||
{ID: "456", Name: "456", Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
|
||||
State: "running", Health: "",
|
||||
Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}}},
|
||||
{ID: "789", Name: "789", Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
|
||||
State: "exited", Health: "", ExitCode: 130, Publishers: nil},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, containers, expected)
|
||||
@@ -74,6 +77,7 @@ func containerDetails(service string, id string, status string, health string, e
|
||||
container := moby.Container{
|
||||
ID: id,
|
||||
Names: []string{"/" + id},
|
||||
Image: "foo",
|
||||
Labels: containerLabels(service, false),
|
||||
State: status,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -46,7 +47,7 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project, optio
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
|
||||
func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error { //nolint:gocyclo
|
||||
info, err := s.apiClient().Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -63,13 +64,16 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(s.maxConcurrency)
|
||||
|
||||
var mustBuild []string
|
||||
var (
|
||||
mustBuild []string
|
||||
pullErrors = make([]error, len(project.Services))
|
||||
imagesBeingPulled = map[string]string{}
|
||||
)
|
||||
|
||||
imagesBeingPulled := map[string]string{}
|
||||
|
||||
for _, service := range project.Services {
|
||||
service := service
|
||||
for i, service := range project.Services {
|
||||
i, service := i, service
|
||||
if service.Image == "" {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
@@ -110,15 +114,16 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
imagesBeingPulled[service.Image] = service.Name
|
||||
|
||||
eg.Go(func() error {
|
||||
_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
|
||||
_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false, project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
if err != nil {
|
||||
if !opts.IgnoreFailures {
|
||||
if service.Build != nil {
|
||||
mustBuild = append(mustBuild, service.Name)
|
||||
}
|
||||
pullErrors[i] = err
|
||||
if service.Build != nil {
|
||||
mustBuild = append(mustBuild, service.Name)
|
||||
}
|
||||
if !opts.IgnoreFailures && service.Build == nil {
|
||||
// fail fast if image can't be pulled nor built
|
||||
return err
|
||||
}
|
||||
w.TailMsgf("Pulling %s: %s", service.Name, err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -126,11 +131,17 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
|
||||
err = eg.Wait()
|
||||
|
||||
if !opts.IgnoreFailures && len(mustBuild) > 0 {
|
||||
if len(mustBuild) > 0 {
|
||||
w.TailMsgf("WARNING: Some service image(s) must be built from source by running:\n docker compose build %s", strings.Join(mustBuild, " "))
|
||||
}
|
||||
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.IgnoreFailures {
|
||||
return nil
|
||||
}
|
||||
return multierror.Append(nil, pullErrors...).ErrorOrNil()
|
||||
}
|
||||
|
||||
func imageAlreadyPresent(serviceImage string, localImages map[string]string) bool {
|
||||
@@ -146,7 +157,8 @@ func imageAlreadyPresent(serviceImage string, localImages map[string]string) boo
|
||||
return ok && tagged.Tag() != "latest"
|
||||
}
|
||||
|
||||
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) (string, error) {
|
||||
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info,
|
||||
configFile driver.Auth, w progress.Writer, quietPull bool, defaultPlatform string) (string, error) {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Working,
|
||||
@@ -157,29 +169,19 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
return "", err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
encodedAuth, err := encodedAuth(ref, info, configFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := repoInfo.Index.Name
|
||||
if repoInfo.Index.Official {
|
||||
key = info.IndexServerAddress
|
||||
}
|
||||
|
||||
authConfig, err := configFile.GetAuthConfig(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
platform := service.Platform
|
||||
if platform == "" {
|
||||
platform = defaultPlatform
|
||||
}
|
||||
|
||||
stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
|
||||
RegistryAuth: base64.URLEncoding.EncodeToString(buf),
|
||||
Platform: service.Platform,
|
||||
RegistryAuth: encodedAuth,
|
||||
Platform: platform,
|
||||
})
|
||||
|
||||
// check if has error and the service has a build section
|
||||
@@ -231,6 +233,29 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
return inspected.ID, nil
|
||||
}
|
||||
|
||||
func encodedAuth(ref reference.Named, info moby.Info, configFile driver.Auth) (string, error) {
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := repoInfo.Index.Name
|
||||
if repoInfo.Index.Official {
|
||||
key = info.IndexServerAddress
|
||||
}
|
||||
|
||||
authConfig, err := configFile.GetAuthConfig(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
|
||||
info, err := s.apiClient().Info(ctx)
|
||||
if err != nil {
|
||||
@@ -265,32 +290,43 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(s.maxConcurrency)
|
||||
pulledImages := make([]string, len(needPull))
|
||||
for i, service := range needPull {
|
||||
i, service := i, service
|
||||
eg.Go(func() error {
|
||||
id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
|
||||
id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
pulledImages[i] = id
|
||||
if err != nil && service.Build != nil {
|
||||
if err != nil && isServiceImageToBuild(service, project.Services) {
|
||||
// image can be built, so we can ignore pull failure
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
err := eg.Wait()
|
||||
for i, service := range needPull {
|
||||
if pulledImages[i] != "" {
|
||||
images[service.Image] = pulledImages[i]
|
||||
}
|
||||
}
|
||||
err := eg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func isServiceImageToBuild(service types.ServiceConfig, services []types.ServiceConfig) bool {
|
||||
if service.Build != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, depService := range services {
|
||||
if depService.Image == service.Image && depService.Build != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
|
||||
if jm.ID == "" || jm.Progress == nil {
|
||||
return
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -44,6 +47,7 @@ func (s *composeService) Push(ctx context.Context, project *types.Project, optio
|
||||
|
||||
func (s *composeService) push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(s.maxConcurrency)
|
||||
|
||||
info, err := s.apiClient().Info(ctx)
|
||||
if err != nil {
|
||||
@@ -65,7 +69,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 +82,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 +125,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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
@@ -69,11 +70,29 @@ func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, erro
|
||||
target = "/run/secrets/" + config.Target
|
||||
}
|
||||
|
||||
var uid, gid int
|
||||
if config.UID != "" {
|
||||
v, err := strconv.Atoi(config.UID)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
uid = v
|
||||
}
|
||||
if config.GID != "" {
|
||||
v, err := strconv.Atoi(config.GID)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
gid = v
|
||||
}
|
||||
|
||||
header := &tar.Header{
|
||||
Name: target,
|
||||
Size: int64(len(value)),
|
||||
Mode: int64(mode),
|
||||
ModTime: time.Now(),
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
}
|
||||
err := tarWriter.WriteHeader(header)
|
||||
if err != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user