mirror of
https://github.com/docker/compose.git
synced 2026-02-15 21:19:30 +08:00
Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f472ce3493 | ||
|
|
533abc3b1d | ||
|
|
e8ea3ad29f | ||
|
|
197c16904a | ||
|
|
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 | ||
|
|
616777eb4a | ||
|
|
c1f475d7bd | ||
|
|
c6109b2e5c | ||
|
|
fffe7fff57 | ||
|
|
0a5f4e62e4 | ||
|
|
d88f6805e7 | ||
|
|
266ab22d53 | ||
|
|
a7476c8eeb | ||
|
|
15ebff00b1 | ||
|
|
f44ca01fcf | ||
|
|
19a1454c2d | ||
|
|
aa297a9969 | ||
|
|
0d0a02cc6b | ||
|
|
3c641ed265 | ||
|
|
f41eec4e09 | ||
|
|
140dc519d3 | ||
|
|
279225896a | ||
|
|
a95cc4074a | ||
|
|
b4420c372b | ||
|
|
ce3700d334 | ||
|
|
e2a3fe9427 | ||
|
|
94465d57cc | ||
|
|
0dc64723c9 | ||
|
|
8891d9e2b5 | ||
|
|
6cd68a4bf2 | ||
|
|
a1984ca1de | ||
|
|
118b4f07e5 | ||
|
|
8714f983ac | ||
|
|
6bc50cb457 | ||
|
|
937fa2dc8f | ||
|
|
71ab6c9eef | ||
|
|
db88241698 | ||
|
|
723078c593 | ||
|
|
a1c50ef2c9 | ||
|
|
2977f4c897 | ||
|
|
cfdec21a7f | ||
|
|
b564cc5a17 | ||
|
|
43c444e890 | ||
|
|
b25a66bbd7 | ||
|
|
0e975262da | ||
|
|
c4d79e60b6 | ||
|
|
ddc4896b10 | ||
|
|
9b863549ee | ||
|
|
801678686c | ||
|
|
403d691abf | ||
|
|
b49b9ffe7e | ||
|
|
680763f8b7 | ||
|
|
1ed37ef7bd | ||
|
|
42169db166 | ||
|
|
d05f5f5fa7 | ||
|
|
5cc2c27abb | ||
|
|
7b7189fe00 | ||
|
|
de1d969c37 | ||
|
|
ab984d91af | ||
|
|
e413c2137a | ||
|
|
61845dd781 | ||
|
|
7a8d157871 | ||
|
|
88df5ede42 | ||
|
|
a7cc406187 | ||
|
|
126cb988c6 | ||
|
|
4c474fe029 | ||
|
|
209293e449 | ||
|
|
79af3cdd85 | ||
|
|
b80222fb07 | ||
|
|
ff53411d9d | ||
|
|
0ac0e29294 | ||
|
|
bc806da712 | ||
|
|
f72a604cbd | ||
|
|
e81168197a | ||
|
|
361194472e | ||
|
|
e7b488bb94 | ||
|
|
07eb8a598d | ||
|
|
8a9eae3190 | ||
|
|
48744dbe47 | ||
|
|
44c55e89c0 | ||
|
|
e016faac33 | ||
|
|
8ed2d8ad07 | ||
|
|
537f023a3b | ||
|
|
8b1b70833e | ||
|
|
06ae6d82cb | ||
|
|
84392d52c4 | ||
|
|
c87efed6a4 | ||
|
|
36926c41c6 | ||
|
|
24bf9789a6 | ||
|
|
cc4f194295 | ||
|
|
55cf579e02 | ||
|
|
1a7c1dfe7d | ||
|
|
8301dc8314 | ||
|
|
0d2beddf20 | ||
|
|
69651136cf | ||
|
|
f3e0c386d2 | ||
|
|
88406491c9 | ||
|
|
63b126622d | ||
|
|
23deefd20c | ||
|
|
e5eb00f618 | ||
|
|
2f2460b40e | ||
|
|
3aceaa1694 | ||
|
|
c07de59a98 | ||
|
|
235734823e | ||
|
|
d23c261c7d | ||
|
|
157d38aa69 | ||
|
|
5723dee316 | ||
|
|
6eb34031f6 | ||
|
|
9e7ae6cb30 | ||
|
|
f880b4129c | ||
|
|
832eee0e8f | ||
|
|
a1d19119d2 | ||
|
|
b96b5449e5 | ||
|
|
a226fe9daf | ||
|
|
fcfcc1524e | ||
|
|
6fe34c45ca | ||
|
|
10cfd551e3 | ||
|
|
0b4cb85c84 | ||
|
|
3f4f4e5975 | ||
|
|
86c925fbd3 | ||
|
|
a64a5a61a7 | ||
|
|
09e6b0292a | ||
|
|
3c561e7017 | ||
|
|
8ad63f7150 | ||
|
|
10532201a2 | ||
|
|
3022b6479f | ||
|
|
abc73ed3d6 | ||
|
|
0ec04058cd | ||
|
|
293cf21c58 | ||
|
|
25f4cb2ee6 | ||
|
|
817e875cbf | ||
|
|
8d12042f39 | ||
|
|
f5a1bb875d | ||
|
|
3e5b8659eb | ||
|
|
79ed1290a6 | ||
|
|
51ef754387 | ||
|
|
58c477f916 | ||
|
|
4853ace155 | ||
|
|
de49bea774 | ||
|
|
5ec20296e4 | ||
|
|
e8389306ae | ||
|
|
3fc020c20a | ||
|
|
0db6dfee03 | ||
|
|
27227a8824 | ||
|
|
12ad0fddab | ||
|
|
e623b5ca1e | ||
|
|
359133b800 | ||
|
|
c47079e795 | ||
|
|
bf4ca6219a | ||
|
|
ae08f57928 | ||
|
|
ca990146e9 | ||
|
|
143a40a618 | ||
|
|
7c405706b4 | ||
|
|
e65ada3984 | ||
|
|
bb04677b0f | ||
|
|
7572bec674 | ||
|
|
1ee44a0acb | ||
|
|
8d4846f210 | ||
|
|
92f32b5c79 | ||
|
|
ca05ffe36e | ||
|
|
c586ca4d0e | ||
|
|
be495ab8e6 | ||
|
|
d2a6c2c200 | ||
|
|
f6e96dd783 | ||
|
|
765c071c89 | ||
|
|
3dfdad61df | ||
|
|
bbaaa6a9de | ||
|
|
ddd9d4b6e4 | ||
|
|
d0d06d414d | ||
|
|
b1e4cde2da | ||
|
|
9e48afb830 | ||
|
|
9db79856be | ||
|
|
5ead5d1cd6 | ||
|
|
7289e87a38 | ||
|
|
052469104f | ||
|
|
bff44ff466 | ||
|
|
57f98eff03 | ||
|
|
1eeb12fe1e | ||
|
|
c9876f4c66 | ||
|
|
4bd12e1aa3 | ||
|
|
d789b2e426 | ||
|
|
8d84a12333 | ||
|
|
ce740b1ff6 | ||
|
|
630c600929 | ||
|
|
960453fa22 | ||
|
|
e24d274bbc | ||
|
|
89dfb9140e | ||
|
|
d62c9fe842 |
@@ -1,2 +1 @@
|
||||
bin/
|
||||
dist/
|
||||
|
||||
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
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -4,3 +4,15 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
ignore:
|
||||
# docker/buildx + docker/cli + docker/docker require coordination to
|
||||
# ensure compatibility between them
|
||||
- dependency-name: "github.com/docker/buildx"
|
||||
# buildx is still 0.x
|
||||
update-types: ["version-update:semver-minor"]
|
||||
- dependency-name: "github.com/docker/cli"
|
||||
# docker/cli uses CalVer rather than SemVer
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- dependency-name: "github.com/docker/docker"
|
||||
# docker/docker uses CalVer rather than SemVer
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
|
||||
60
.github/workflows/artifacts.yml
vendored
60
.github/workflows/artifacts.yml
vendored
@@ -1,60 +0,0 @@
|
||||
name: Publish Artifacts
|
||||
env:
|
||||
GO_VERSION: 1.18.4
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
publish-artifacts:
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Build cross platform compose-plugin binaries
|
||||
run: make -f builder.Makefile cross
|
||||
|
||||
- name: Upload macos-amd64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-compose-darwin-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-darwin-amd64
|
||||
|
||||
- name: Upload macos-arm64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-compose-darwin-arm64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-darwin-arm64
|
||||
|
||||
- name: Upload linux-amd64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-compose-linux-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
||||
|
||||
- name: Upload linux-ppc64le binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-compose-linux-ppc64le
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
|
||||
|
||||
- name: Upload windows-amd64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-compose-windows-amd64.exe
|
||||
path: ${{ github.workspace }}/bin/docker-compose-windows-amd64.exe
|
||||
|
||||
- name: Update comment
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
body: |
|
||||
This PR can be tested using [binaries](https://github.com/docker/compose-cli/actions/runs/${{ github.run_id }}).
|
||||
reactions: eyes
|
||||
284
.github/workflows/ci.yml
vendored
284
.github/workflows/ci.yml
vendored
@@ -1,9 +1,15 @@
|
||||
name: Continuous integration
|
||||
name: ci
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v2
|
||||
- 'v2'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -11,113 +17,219 @@ on:
|
||||
description: 'To run with tmate enter "debug_enabled"'
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.18.4
|
||||
DESTDIR: "./bin"
|
||||
DOCKER_CLI_VERSION: "20.10.17"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Validate go-mod, license headers and docs are up-to-date
|
||||
run: make validate
|
||||
|
||||
- name: Run golangci-lint
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
args: --timeout=180s
|
||||
|
||||
# only on main branch, costs too much for the gain on every PR
|
||||
validate-cross-build:
|
||||
name: Validate cross build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
# Ensure we don't discover cross platform build issues at release time.
|
||||
# Time used to build linux here is gained back in the build for local E2E step
|
||||
- name: Build packages
|
||||
run: make -f builder.Makefile cross
|
||||
|
||||
build-plugin:
|
||||
name: Build and tests in plugin mode
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Setup docker CLI
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
echo ::set-output name=matrix::$(docker buildx bake binary-cross --print | jq -cr '.target."binary-cross".platforms')
|
||||
-
|
||||
name: Show matrix
|
||||
run: |
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
|
||||
- name: Test
|
||||
run: make -f builder.Makefile test
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- lint
|
||||
- validate-go-mod
|
||||
- validate-modules
|
||||
- validate-headers
|
||||
- validate-docs
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
make ${{ matrix.target }}
|
||||
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||
binary:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: release
|
||||
set: |
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=binary-${{ env.PLATFORM_PAIR }}
|
||||
*.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compose
|
||||
path: ${{ env.DESTDIR }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: E2E Test in plugin mode
|
||||
run: make e2e-compose
|
||||
|
||||
build-standalone:
|
||||
name: Build and tests in standalone mode
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=test
|
||||
*.cache-to=type=gha,scope=test
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DESTDIR: "./bin/build"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mode:
|
||||
- plugin
|
||||
- standalone
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- name: Setup docker CLI
|
||||
-
|
||||
name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Build for local E2E
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: binary
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=binary-linux-amd64
|
||||
*.cache-from=type=gha,scope=binary-e2e-${{ matrix.mode }}
|
||||
*.cache-to=type=gha,scope=binary-e2e-${{ matrix.mode }},mode=max
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
-
|
||||
name: Setup tmate session
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
uses: mxschmitt/action-tmate@8b4e4ac71822ed7e0ad5fb3d1c33483e9e8fb270 # v3.11
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
|
||||
- name: E2E Test in standalone mode
|
||||
-
|
||||
name: Test plugin mode
|
||||
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' }}
|
||||
run: |
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/docker-compose /usr/local/bin
|
||||
cp bin/build/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
|
||||
release:
|
||||
permissions:
|
||||
contents: write # to create a release (ncipollo/release-action)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- binary
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compose
|
||||
path: ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Create checksums
|
||||
working-directory: ${{ env.DESTDIR }}
|
||||
run: |
|
||||
find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# \*\./# *#' > $RUNNER_TEMP/checksums.txt
|
||||
shasum -a 256 -U -c $RUNNER_TEMP/checksums.txt
|
||||
mv $RUNNER_TEMP/checksums.txt .
|
||||
cat checksums.txt | while read sum file; do echo "$sum $file" > ${file#\*}.sha256; done
|
||||
-
|
||||
name: License
|
||||
run: cp packaging/* ${{ env.DESTDIR }}/
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0
|
||||
with:
|
||||
artifacts: ${{ env.DESTDIR }}/*
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
7
.github/workflows/docs.yml
vendored
7
.github/workflows/docs.yml
vendored
@@ -4,8 +4,13 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
open-pr:
|
||||
permissions:
|
||||
contents: write # to create branch (peter-evans/create-pull-request)
|
||||
pull-requests: write # to create a PR (peter-evans/create-pull-request)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
@@ -13,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
repository: docker/docker.github.io
|
||||
repository: docker/docs
|
||||
ref: master
|
||||
-
|
||||
name: Prepare
|
||||
|
||||
74
.github/workflows/merge.yml
vendored
Normal file
74
.github/workflows/merge.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: merge
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'v2'
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: Build and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [desktop-windows, desktop-macos, desktop-m1]
|
||||
# mode: [plugin, standalone]
|
||||
mode: [plugin]
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
check-latest: true
|
||||
|
||||
- name: List Docker resources on machine
|
||||
run: |
|
||||
docker ps --all
|
||||
docker volume ls
|
||||
docker network ls
|
||||
docker image ls
|
||||
- name: Remove Docker resources on machine
|
||||
continue-on-error: true
|
||||
run: |
|
||||
docker kill $(docker ps -q)
|
||||
docker rm -f $(docker ps -aq)
|
||||
docker volume rm -f $(docker volume ls -q)
|
||||
docker ps --all
|
||||
|
||||
- name: Unit tests
|
||||
run: make test
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make
|
||||
- name: Check arch of go compose binary
|
||||
run: |
|
||||
file ./bin/build/docker-compose
|
||||
if: ${{ !contains(matrix.os, 'desktop-windows') }}
|
||||
-
|
||||
name: Test plugin mode
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
run: |
|
||||
make e2e-compose
|
||||
-
|
||||
name: Test standalone mode
|
||||
if: ${{ matrix.mode == 'standalone' }}
|
||||
run: |
|
||||
make e2e-compose-standalone
|
||||
|
||||
11
.github/workflows/pr-closed.yml
vendored
11
.github/workflows/pr-closed.yml
vendored
@@ -1,11 +0,0 @@
|
||||
name: PR cleanup
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
jobs:
|
||||
delete_pr_artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: stefanluptak/delete-old-pr-artifacts@v1
|
||||
with:
|
||||
workflow_filename: ci.yaml
|
||||
19
.github/workflows/rebase.yml
vendored
19
.github/workflows/rebase.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Automatic Rebase
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
45
.github/workflows/release.yaml
vendored
45
.github/workflows/release.yaml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Releaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release Tag"
|
||||
required: true
|
||||
env:
|
||||
GO_VERSION: 1.18.4
|
||||
jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Build
|
||||
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
||||
|
||||
- name: Compute checksums
|
||||
run: cd bin; for f in *; do shasum --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
|
||||
|
||||
- name: License
|
||||
run: cp packaging/* bin/
|
||||
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "bin/*"
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
commit: "v2"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
bin/
|
||||
dist/
|
||||
/.vscode/
|
||||
coverage.out
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
linters:
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- depguard
|
||||
- errcheck
|
||||
- gocritic
|
||||
@@ -18,16 +18,19 @@ linters:
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
list-type: denylist
|
||||
include-go-root: true
|
||||
packages:
|
||||
# The io/ioutil package has been deprecated.
|
||||
|
||||
@@ -19,7 +19,8 @@ Once you have the prerequisites installed, you can build the CLI using:
|
||||
make
|
||||
```
|
||||
|
||||
This will output a `docker-compose` CLI plugin for your host machine in `./bin`.
|
||||
This will output a `docker-compose` CLI plugin for your host machine in
|
||||
`./bin/build`.
|
||||
|
||||
You can statically cross compile the CLI for Windows, macOS, and Linux using the
|
||||
`cross` target.
|
||||
@@ -38,7 +39,6 @@ If you need to update a golden file simply do `go test ./... -test.update-golden
|
||||
To run e2e tests, the Compose CLI binary need to be build. 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.
|
||||
|
||||
#### Whole end-to-end tests suite
|
||||
@@ -76,6 +76,7 @@ make e2e-compose-standalone
|
||||
```
|
||||
|
||||
Or if you need to build the CLI, run:
|
||||
|
||||
```console
|
||||
make build-and-e2e-compose-standalone
|
||||
```
|
||||
|
||||
@@ -124,9 +124,10 @@ 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. [Run the full test
|
||||
suite](README.md) on your branch before
|
||||
submitting a pull request.
|
||||
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.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
|
||||
237
Dockerfile
237
Dockerfile
@@ -1,4 +1,4 @@
|
||||
# syntax=docker/dockerfile:1.2
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
@@ -15,101 +15,174 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.18.4-alpine
|
||||
ARG GOLANGCI_LINT_VERSION=v1.46.2-alpine
|
||||
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
||||
ARG GO_VERSION=1.19.2
|
||||
ARG XX_VERSION=1.1.2
|
||||
ARG GOLANGCI_LINT_VERSION=v1.49.0
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS local-golangci-lint
|
||||
ARG BUILD_TAGS="e2e,kube"
|
||||
ARG DOCS_FORMATS="md,yaml"
|
||||
ARG LICENSE_FILES=".*\(Dockerfile\|Makefile\|\.go\|\.hcl\|\.sh\)"
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS base
|
||||
WORKDIR /compose-cli
|
||||
RUN apk add --no-cache -vv \
|
||||
git \
|
||||
docker \
|
||||
make \
|
||||
protoc \
|
||||
protobuf-dev
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=${BUILDPLATFORM} tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||
FROM ghcr.io/google/addlicense:${ADDLICENSE_VERSION} AS addlicense
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS base
|
||||
COPY --from=xx / /
|
||||
RUN apk add --no-cache \
|
||||
docker \
|
||||
file \
|
||||
git \
|
||||
make \
|
||||
protoc \
|
||||
protobuf-dev
|
||||
WORKDIR /src
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
FROM base AS build-base
|
||||
COPY go.* .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
go mod download
|
||||
|
||||
FROM base AS lint
|
||||
ENV CGO_ENABLED=0
|
||||
COPY --from=local-golangci-lint /usr/bin/golangci-lint /usr/bin/golangci-lint
|
||||
ARG BUILD_TAGS
|
||||
ARG GIT_TAG
|
||||
RUN --mount=target=. \
|
||||
FROM build-base AS vendored
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/.cache/golangci-lint \
|
||||
BUILD_TAGS=${BUILD_TAGS} \
|
||||
GIT_TAG=${GIT_TAG} \
|
||||
make -f builder.Makefile lint
|
||||
go mod tidy && mkdir /out && cp go.mod go.sum /out
|
||||
|
||||
FROM base AS make-compose-plugin
|
||||
ENV CGO_ENABLED=0
|
||||
FROM scratch AS vendor-update
|
||||
COPY --from=vendored /out /
|
||||
|
||||
FROM vendored AS vendor-validate
|
||||
RUN --mount=type=bind,target=.,rw <<EOT
|
||||
set -e
|
||||
git add -A
|
||||
cp -rf /out/* .
|
||||
diff=$(git status --porcelain -- go.mod go.sum)
|
||||
if [ -n "$diff" ]; then
|
||||
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "make go-mod-tidy"'
|
||||
echo "$diff"
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM vendored AS modules-validate
|
||||
RUN apk add --no-cache bash
|
||||
RUN apk add --no-cache jq
|
||||
RUN --mount=type=bind,target=.,rw ./verify-go-modules.sh e2e
|
||||
|
||||
FROM build-base AS build
|
||||
ARG BUILD_TAGS
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-go --wrap
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
make build GO_BUILDTAGS="$BUILD_TAGS" DESTDIR=/usr/bin && \
|
||||
xx-verify --static /usr/bin/docker-compose
|
||||
|
||||
FROM build-base AS lint
|
||||
ARG BUILD_TAGS
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
golangci-lint run --build-tags "$BUILD_TAGS" ./...
|
||||
|
||||
FROM build-base AS test
|
||||
ARG CGO_ENABLED=0
|
||||
ARG BUILD_TAGS
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go test -tags "$BUILD_TAGS" -v -coverprofile=/tmp/coverage.txt -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') && \
|
||||
go tool cover -func=/tmp/coverage.txt
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test /tmp/coverage.txt /coverage.txt
|
||||
|
||||
FROM base AS license-set
|
||||
ARG LICENSE_FILES
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=from=addlicense,source=/app/addlicense,target=/usr/bin/addlicense \
|
||||
find . -regex "${LICENSE_FILES}" | xargs addlicense -c 'Docker Compose CLI' -l apache && \
|
||||
mkdir /out && \
|
||||
find . -regex "${LICENSE_FILES}" | cpio -pdm /out
|
||||
|
||||
FROM scratch AS license-update
|
||||
COPY --from=set /out /
|
||||
|
||||
FROM base AS license-validate
|
||||
ARG LICENSE_FILES
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=from=addlicense,source=/app/addlicense,target=/usr/bin/addlicense \
|
||||
find . -regex "${LICENSE_FILES}" | xargs addlicense -check -c 'Docker Compose CLI' -l apache -ignore validate -ignore testdata -ignore resolvepath -v
|
||||
|
||||
FROM base AS docsgen
|
||||
WORKDIR /src
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/root/.cache,type=cache \
|
||||
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} alpine AS docs-build
|
||||
RUN apk add --no-cache rsync git
|
||||
WORKDIR /src
|
||||
COPY --from=docsgen /out/docsgen /usr/bin
|
||||
ARG DOCS_FORMATS
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
docsgen --formats "$DOCS_FORMATS" --source "docs/reference"
|
||||
mkdir /out
|
||||
cp -r docs/reference /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS docs-update
|
||||
COPY --from=docs-build /out /out
|
||||
|
||||
FROM docs-build AS docs-validate
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf docs/reference/*
|
||||
cp -rf /out/* ./docs/
|
||||
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
|
||||
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
|
||||
git status --porcelain -- docs/reference
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM scratch AS binary-unix
|
||||
COPY --link --from=build /usr/bin/docker-compose /
|
||||
FROM binary-unix AS binary-darwin
|
||||
FROM binary-unix AS binary-linux
|
||||
FROM scratch AS binary-windows
|
||||
COPY --link --from=build /usr/bin/docker-compose /docker-compose.exe
|
||||
FROM binary-$TARGETOS AS binary
|
||||
|
||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||
WORKDIR /work
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG BUILD_TAGS
|
||||
ARG GIT_TAG
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
GOOS=${TARGETOS} \
|
||||
GOARCH=${TARGETARCH} \
|
||||
BUILD_TAGS=${BUILD_TAGS} \
|
||||
GIT_TAG=${GIT_TAG} \
|
||||
make COMPOSE_BINARY=/out/docker-compose -f builder.Makefile compose-plugin
|
||||
ARG TARGETVARIANT
|
||||
RUN --mount=from=binary \
|
||||
mkdir -p /out && \
|
||||
# TODO: should just use standard arch
|
||||
TARGETARCH=$([ "$TARGETARCH" = "amd64" ] && echo "x86_64" || echo "$TARGETARCH"); \
|
||||
TARGETARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "$TARGETARCH"); \
|
||||
cp docker-compose* "/out/docker-compose-${TARGETOS}-${TARGETARCH}${TARGETVARIANT}$(ls docker-compose* | sed -e 's/^docker-compose//')"
|
||||
|
||||
FROM base AS make-cross
|
||||
ARG BUILD_TAGS
|
||||
ARG GIT_TAG
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
BUILD_TAGS=${BUILD_TAGS} \
|
||||
GIT_TAG=${GIT_TAG} \
|
||||
make COMPOSE_BINARY=/out/docker-compose -f builder.Makefile cross
|
||||
|
||||
FROM scratch AS compose-plugin
|
||||
COPY --from=make-compose-plugin /out/* .
|
||||
|
||||
FROM scratch AS cross
|
||||
COPY --from=make-cross /out/* .
|
||||
|
||||
FROM base AS test
|
||||
ENV CGO_ENABLED=0
|
||||
ARG BUILD_TAGS
|
||||
ARG GIT_TAG
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
BUILD_TAGS=${BUILD_TAGS} \
|
||||
GIT_TAG=${GIT_TAG} \
|
||||
make -f builder.Makefile test
|
||||
|
||||
FROM base AS check-license-headers
|
||||
RUN go install github.com/google/addlicense@latest
|
||||
RUN --mount=target=. \
|
||||
make -f builder.Makefile check-license-headers
|
||||
|
||||
FROM base AS make-go-mod-tidy
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
go mod tidy
|
||||
|
||||
FROM scratch AS go-mod-tidy
|
||||
COPY --from=make-go-mod-tidy /compose-cli/go.mod .
|
||||
COPY --from=make-go-mod-tidy /compose-cli/go.sum .
|
||||
|
||||
FROM base AS check-go-mod
|
||||
COPY . .
|
||||
RUN make -f builder.Makefile check-go-mod
|
||||
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 .
|
||||
|
||||
98
Makefile
98
Makefile
@@ -12,39 +12,57 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
export DOCKER_BUILDKIT=1
|
||||
PKG := github.com/docker/compose/v2
|
||||
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
GO_LDFLAGS ?= -s -w -X ${PKG}/internal.Version=${VERSION}
|
||||
GO_BUILDTAGS ?= e2e,kube
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DETECTED_OS = Windows
|
||||
else
|
||||
DETECTED_OS = $(shell uname -s)
|
||||
endif
|
||||
ifeq ($(DETECTED_OS),Linux)
|
||||
MOBY_DOCKER=/usr/bin/docker
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
ifeq ($(DETECTED_OS),Darwin)
|
||||
MOBY_DOCKER=/Applications/Docker.app/Contents/Resources/bin/docker
|
||||
endif
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
BINARY_EXT=.exe
|
||||
endif
|
||||
|
||||
BINARY_FOLDER=$(shell pwd)/bin
|
||||
GIT_TAG?=$(shell git describe --tags --match "v[0-9]*")
|
||||
TEST_FLAGS?=
|
||||
TEST_COVERAGE_FLAGS = -race -coverprofile=coverage.out -covermode=atomic
|
||||
TEST_FLAGS?= -timeout 15m
|
||||
E2E_TEST?=
|
||||
ifeq ($(E2E_TEST),)
|
||||
else
|
||||
TEST_FLAGS=-run $(E2E_TEST)
|
||||
endif
|
||||
|
||||
all: compose-plugin
|
||||
BUILDX_CMD ?= docker buildx
|
||||
DESTDIR ?= ./bin/build
|
||||
|
||||
.PHONY: compose-plugin
|
||||
compose-plugin: ## Compile the compose cli-plugin
|
||||
@docker build . --target compose-plugin \
|
||||
--platform local \
|
||||
--build-arg BUILD_TAGS=e2e,kube \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--output ./bin
|
||||
all: build
|
||||
|
||||
.PHONY: build ## Build the compose cli-plugin
|
||||
build:
|
||||
CGO_ENABLED=0 GO111MODULE=on go build -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(DESTDIR)/docker-compose$(BINARY_EXT)" ./cmd
|
||||
|
||||
.PHONY: binary
|
||||
binary:
|
||||
$(BUILDX_CMD) bake binary
|
||||
|
||||
.PHONY: install
|
||||
install: binary
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
install bin/build/docker-compose ~/.docker/cli-plugins/docker-compose
|
||||
|
||||
.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
|
||||
@@ -52,10 +70,10 @@ e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2
|
||||
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||
|
||||
.PHONY: build-and-e2e-compose
|
||||
build-and-e2e-compose: compose-plugin e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: build-and-e2e-compose-standalone
|
||||
build-and-e2e-compose-standalone: compose-plugin e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
@@ -67,49 +85,35 @@ mocks:
|
||||
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: build-and-e2e
|
||||
build-and-e2e: compose-plugin e2e-compose e2e-compose-standalone ## Compile the compose cli-plugin and run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
build-and-e2e: build e2e-compose e2e-compose-standalone ## Compile the compose cli-plugin and run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: cross
|
||||
cross: ## Compile the CLI for linux, darwin and windows
|
||||
@docker build . --target cross \
|
||||
--build-arg BUILD_TAGS \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--output ./bin \
|
||||
$(BUILDX_CMD) bake binary-cross
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run unit tests
|
||||
@docker build --progress=plain . \
|
||||
--build-arg BUILD_TAGS=kube \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--target test
|
||||
$(BUILDX_CMD) bake test
|
||||
|
||||
.PHONY: cache-clear
|
||||
cache-clear: ## Clear the builder cache
|
||||
@docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h
|
||||
$(BUILDX_CMD) prune --force --filter type=exec.cachemount --filter=unused-for=24h
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## run linter(s)
|
||||
@docker build . \
|
||||
--build-arg BUILD_TAGS=kube,e2e \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--target lint
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## generate documentation
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
|
||||
docker build . \
|
||||
--output type=local,dest=$($@_TMP_OUT) \
|
||||
-f ./docs/docs.Dockerfile \
|
||||
--target update
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t compose-output.XXXXXXXXXX))
|
||||
$(BUILDX_CMD) bake --set "*.output=type=local,dest=$($@_TMP_OUT)" docs-update
|
||||
rm -rf ./docs/internal
|
||||
cp -R "$($@_TMP_OUT)"/out/* ./docs/
|
||||
rm -rf "$($@_TMP_OUT)"/*
|
||||
|
||||
.PHONY: validate-docs
|
||||
validate-docs: ## validate the doc does not change
|
||||
@docker build . \
|
||||
-f ./docs/docs.Dockerfile \
|
||||
--target validate
|
||||
$(BUILDX_CMD) bake docs-validate
|
||||
|
||||
.PHONY: check-dependencies
|
||||
check-dependencies: ## check dependency updates
|
||||
@@ -117,19 +121,23 @@ check-dependencies: ## check dependency updates
|
||||
|
||||
.PHONY: validate-headers
|
||||
validate-headers: ## Check license header for all files
|
||||
@docker build . --target check-license-headers
|
||||
$(BUILDX_CMD) bake license-validate
|
||||
|
||||
.PHONY: go-mod-tidy
|
||||
go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and go.sum
|
||||
@docker build . --target go-mod-tidy --output .
|
||||
$(BUILDX_CMD) bake vendor-update
|
||||
|
||||
.PHONY: validate-go-mod
|
||||
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
||||
@docker build . --target check-go-mod
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
.PHONY: validate-modules
|
||||
validate-modules: ## Validate root and e2e go.mod are synced
|
||||
$(BUILDX_CMD) bake modules-validate
|
||||
|
||||
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
|
||||
validate: validate-go-mod validate-modules validate-headers validate-docs ## Validate sources
|
||||
|
||||
pre-commit: validate check-dependencies lint build test e2e-compose
|
||||
|
||||
help: ## Show help
|
||||
@echo Please specify a build target. The choices are:
|
||||
|
||||
10
README.md
10
README.md
@@ -1,6 +1,10 @@
|
||||
# Docker Compose v2
|
||||
|
||||
[](https://github.com/docker/compose/actions)
|
||||
[](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)
|
||||
|
||||

|
||||
|
||||
@@ -32,12 +36,12 @@ You can download Docker Compose binaries from the
|
||||
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
|
||||
Or copy it into one of these folders for installing it system-wide:
|
||||
Or copy it into one of these folders to install it system-wide:
|
||||
|
||||
* `/usr/local/lib/docker/cli-plugins` OR `/usr/local/libexec/docker/cli-plugins`
|
||||
* `/usr/lib/docker/cli-plugins` OR `/usr/libexec/docker/cli-plugins`
|
||||
|
||||
(might require to make the downloaded file executable with `chmod +x`)
|
||||
(might require making the downloaded file executable with `chmod +x`)
|
||||
|
||||
|
||||
Quick Start
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
GOOS?=$(shell go env GOOS)
|
||||
GOARCH?=$(shell go env GOARCH)
|
||||
|
||||
PKG_NAME := github.com/docker/compose/v2
|
||||
|
||||
EXTENSION:=
|
||||
ifeq ($(GOOS),windows)
|
||||
EXTENSION:=.exe
|
||||
endif
|
||||
|
||||
STATIC_FLAGS=CGO_ENABLED=0
|
||||
|
||||
GIT_TAG?=$(shell git describe --tags --match "v[0-9]*")
|
||||
|
||||
LDFLAGS="-s -w -X $(PKG_NAME)/internal.Version=${GIT_TAG}"
|
||||
GO_BUILD=$(STATIC_FLAGS) go build -trimpath -ldflags=$(LDFLAGS)
|
||||
|
||||
COMPOSE_BINARY?=bin/docker-compose
|
||||
COMPOSE_BINARY_WITH_EXTENSION=$(COMPOSE_BINARY)$(EXTENSION)
|
||||
|
||||
WORK_DIR:=$(shell mktemp -d)
|
||||
|
||||
TAGS:=
|
||||
ifdef BUILD_TAGS
|
||||
TAGS=-tags $(BUILD_TAGS)
|
||||
LINT_TAGS=--build-tags $(BUILD_TAGS)
|
||||
endif
|
||||
|
||||
.PHONY: compose-plugin
|
||||
compose-plugin:
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY_WITH_EXTENSION) ./cmd
|
||||
|
||||
.PHONY: cross
|
||||
cross:
|
||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
||||
GOOS=linux GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./cmd
|
||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
||||
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
||||
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
||||
GOOS=linux GOARCH=s390x $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-s390x ./cmd
|
||||
GOOS=darwin GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-x86_64 ./cmd
|
||||
GOOS=darwin GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-darwin-aarch64 ./cmd
|
||||
GOOS=windows GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-windows-x86_64.exe ./cmd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test $(TAGS) -cover $(shell go list $(TAGS) ./... | grep -vE 'e2e')
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run $(LINT_TAGS) --timeout 10m0s ./...
|
||||
|
||||
.PHONY: check-license-headers
|
||||
check-license-headers:
|
||||
./scripts/validate/fileheader
|
||||
|
||||
.PHONY: check-go-mod
|
||||
check-go-mod:
|
||||
./scripts/validate/check-go-mod
|
||||
@@ -23,6 +23,13 @@ import (
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
func getCompletionCommands() []string {
|
||||
return []string{
|
||||
"__complete",
|
||||
"__completeNoDesc",
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolFlags() []string {
|
||||
return []string{
|
||||
"--debug", "-D",
|
||||
@@ -50,6 +57,10 @@ func Convert(args []string) []string {
|
||||
l := len(args)
|
||||
for i := 0; i < l; i++ {
|
||||
arg := args[i]
|
||||
if contains(getCompletionCommands(), arg) {
|
||||
command = append([]string{arg}, command...)
|
||||
continue
|
||||
}
|
||||
if len(arg) > 0 && arg[0] != '-' {
|
||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||
if arg == compose.PluginName {
|
||||
|
||||
@@ -77,7 +77,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [SERVICE...]",
|
||||
Use: "build [OPTIONS] [SERVICE...]",
|
||||
Short: "Build or rebuild services",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.memory != "" {
|
||||
@@ -102,7 +102,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -27,11 +28,11 @@ type validArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]s
|
||||
|
||||
func noCompletion() validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
return []string{}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
}
|
||||
|
||||
func serviceCompletion(p *projectOptions) validArgsFn {
|
||||
func completeServiceNames(p *projectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
project, err := p.toProject(nil)
|
||||
if err != nil {
|
||||
@@ -46,3 +47,21 @@ func serviceCompletion(p *projectOptions) validArgsFn {
|
||||
return serviceNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := backend.List(cmd.Context(), api.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
var values []string
|
||||
for _, stack := range list {
|
||||
if strings.HasPrefix(stack.Name, toComplete) {
|
||||
values = append(values, stack.Name)
|
||||
}
|
||||
}
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -136,6 +137,24 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
|
||||
func (o *projectOptions) projectOrName() (*types.Project, string, error) {
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
if o.ProjectName == "" {
|
||||
p, err := o.toProject(nil)
|
||||
if err != nil {
|
||||
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||
if envProjectName != "" {
|
||||
return nil, envProjectName, nil
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
project = p
|
||||
name = p.Name
|
||||
}
|
||||
return project, name, nil
|
||||
}
|
||||
|
||||
func (o *projectOptions) toProjectName() (string, error) {
|
||||
if o.ProjectName != "" {
|
||||
return o.ProjectName, nil
|
||||
@@ -232,6 +251,16 @@ func RunningAsStandalone() bool {
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
// 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
|
||||
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||
logrus.WarnLevel,
|
||||
},
|
||||
"commandConn.CloseWrite:",
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
|
||||
opts := projectOptions{}
|
||||
var (
|
||||
ansi string
|
||||
@@ -329,6 +358,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
)
|
||||
c.Flags().SetInterspersed(false)
|
||||
opts.addProjectFlags(c.Flags())
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"project-name",
|
||||
completeProjectNames(backend),
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"file",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
|
||||
},
|
||||
)
|
||||
|
||||
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
c.Flags().MarkHidden("version") //nolint:errcheck
|
||||
@@ -354,8 +394,10 @@ func setEnvWithDotEnv(prjOpts *projectOptions) error {
|
||||
return err
|
||||
}
|
||||
for k, v := range envFromFile {
|
||||
if err := os.Setenv(k, v); err != nil { // overwrite the process env with merged OS + env file results
|
||||
return err
|
||||
if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -58,7 +59,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"config"},
|
||||
Use: "convert SERVICES",
|
||||
Use: "convert [OPTIONS] [SERVICE...]",
|
||||
Short: "Converts the compose file to platform's canonical format",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.quiet {
|
||||
@@ -92,7 +93,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
@@ -112,7 +113,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
|
||||
var json []byte
|
||||
var content []byte
|
||||
project, err := opts.toProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
@@ -136,7 +137,7 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
|
||||
}
|
||||
}
|
||||
|
||||
json, err = backend.Convert(ctx, project, api.ConvertOptions{
|
||||
content, err = backend.Convert(ctx, project, api.ConvertOptions{
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
})
|
||||
@@ -144,19 +145,23 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.noInterpolate {
|
||||
content = escapeDollarSign(content)
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out io.Writer = os.Stdout
|
||||
if opts.Output != "" && len(json) > 0 {
|
||||
if opts.Output != "" && len(content) > 0 {
|
||||
file, err := os.Create(opts.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = bufio.NewWriter(file)
|
||||
}
|
||||
_, err = fmt.Fprint(out, string(json))
|
||||
_, err = fmt.Fprint(out, string(content))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -237,3 +242,9 @@ func runConfigImages(opts convertOptions, services []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeDollarSign(marshal []byte) []byte {
|
||||
dollar := []byte{'$'}
|
||||
escDollar := []byte{'$', '$'}
|
||||
return bytes.ReplaceAll(marshal, dollar, escDollar)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts.destination = args[1]
|
||||
return runCopy(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
|
||||
@@ -31,6 +31,7 @@ type createOptions struct {
|
||||
Build bool
|
||||
noBuild bool
|
||||
Pull string
|
||||
pullChanged bool
|
||||
removeOrphans bool
|
||||
ignoreOrphans bool
|
||||
forceRecreate bool
|
||||
@@ -45,9 +46,10 @@ type createOptions struct {
|
||||
func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := createOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [SERVICE...]",
|
||||
Use: "create [OPTIONS] [SERVICE...]",
|
||||
Short: "Creates containers for a service.",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.pullChanged = cmd.Flags().Changed("pull")
|
||||
if opts.Build && opts.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
@@ -68,7 +70,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
QuietPull: false,
|
||||
})
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
|
||||
@@ -108,7 +110,7 @@ func (opts createOptions) GetTimeout() *time.Duration {
|
||||
}
|
||||
|
||||
func (opts createOptions) Apply(project *types.Project) {
|
||||
if opts.Pull != "" {
|
||||
if opts.pullChanged {
|
||||
for i, service := range project.Services {
|
||||
service.PullPolicy = opts.Pull
|
||||
project.Services[i] = service
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -45,7 +44,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Use: "down [OPTIONS]",
|
||||
Short: "Stop and remove containers, networks",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
@@ -66,7 +65,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS"))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if name == "volume" {
|
||||
@@ -79,15 +78,9 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runDown(ctx context.Context, backend api.Service, opts downOptions) error {
|
||||
name := opts.ProjectName
|
||||
var project *types.Project
|
||||
if opts.ProjectName == "" {
|
||||
p, err := opts.toProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project = p
|
||||
name = p.Name
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
|
||||
@@ -38,12 +38,12 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "events [options] [--] [SERVICE...]",
|
||||
Use: "events [OPTIONS] [SERVICE...]",
|
||||
Short: "Receive real time events from containers.",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runEvents(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
|
||||
@@ -51,12 +51,12 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
name, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Events(ctx, project, api.EventsOptions{
|
||||
return backend.Events(ctx, name, api.EventsOptions{
|
||||
Services: services,
|
||||
Consumer: func(event api.Event) error {
|
||||
if opts.json {
|
||||
|
||||
@@ -50,7 +50,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]",
|
||||
Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
|
||||
Short: "Execute a command in a running container.",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -61,7 +61,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExec(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
|
||||
|
||||
@@ -43,12 +43,12 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
imgCmd := &cobra.Command{
|
||||
Use: "images [SERVICE...]",
|
||||
Use: "images [OPTIONS] [SERVICE...]",
|
||||
Short: "List images used by the created containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runImages(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
return imgCmd
|
||||
|
||||
@@ -18,15 +18,18 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type killOptions struct {
|
||||
*projectOptions
|
||||
signal string
|
||||
removeOrphans bool
|
||||
signal string
|
||||
}
|
||||
|
||||
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
@@ -34,29 +37,33 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [options] [SERVICE...]",
|
||||
Use: "kill [OPTIONS] [SERVICE...]",
|
||||
Short: "Force stop service containers.",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS"))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Kill(ctx, projectName, api.KillOptions{
|
||||
Services: services,
|
||||
Signal: opts.signal,
|
||||
return backend.Kill(ctx, name, api.KillOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
Project: project,
|
||||
Services: services,
|
||||
Signal: opts.signal,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ type lsOptions struct {
|
||||
func listCommand(backend api.Service) *cobra.Command {
|
||||
lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
|
||||
lsCmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
Use: "ls [OPTIONS]",
|
||||
Short: "List running compose projects",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runList(ctx, backend, lsOpts)
|
||||
|
||||
@@ -44,12 +44,12 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
logsCmd := &cobra.Command{
|
||||
Use: "logs [SERVICE...]",
|
||||
Use: "logs [OPTIONS] [SERVICE...]",
|
||||
Short: "View output from containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runLogs(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := logsCmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
|
||||
@@ -63,12 +63,13 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runLogs(ctx context.Context, backend api.Service, opts logsOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix)
|
||||
return backend.Logs(ctx, projectName, consumer, api.LogOptions{
|
||||
return backend.Logs(ctx, name, consumer, api.LogOptions{
|
||||
Project: project,
|
||||
Services: services,
|
||||
Follow: opts.follow,
|
||||
Tail: opts.tail,
|
||||
|
||||
@@ -38,19 +38,20 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Pause(ctx, project, api.PauseOptions{
|
||||
return backend.Pause(ctx, name, api.PauseOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,18 +69,19 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runUnPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.UnPause(ctx, project, api.PauseOptions{
|
||||
return backend.UnPause(ctx, name, api.PauseOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "port [options] [--] SERVICE PRIVATE_PORT",
|
||||
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 {
|
||||
@@ -52,7 +52,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPort(ctx, backend, opts, args[0])
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
|
||||
|
||||
@@ -70,7 +70,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
psCmd := &cobra.Command{
|
||||
Use: "ps [SERVICE...]",
|
||||
Use: "ps [OPTIONS] [SERVICE...]",
|
||||
Short: "List containers",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.parseFilter()
|
||||
@@ -78,7 +78,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPs(ctx, backend, args, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||
@@ -91,11 +91,12 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers, err := backend.Ps(ctx, projectName, api.PsOptions{
|
||||
containers, err := backend.Ps(ctx, name, api.PsOptions{
|
||||
Project: project,
|
||||
All: opts.All,
|
||||
Services: services,
|
||||
})
|
||||
|
||||
@@ -43,7 +43,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull [SERVICE...]",
|
||||
Use: "pull [OPTIONS] [SERVICE...]",
|
||||
Short: "Pull service images",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.noParallel {
|
||||
@@ -54,7 +54,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPull(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
|
||||
|
||||
@@ -29,6 +29,7 @@ type pushOptions struct {
|
||||
composeOptions
|
||||
|
||||
Ignorefailures bool
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
@@ -36,14 +37,15 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
pushCmd := &cobra.Command{
|
||||
Use: "push [SERVICE...]",
|
||||
Use: "push [OPTIONS] [SERVICE...]",
|
||||
Short: "Push service images",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPush(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
|
||||
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
|
||||
|
||||
return pushCmd
|
||||
}
|
||||
@@ -56,5 +58,6 @@ func runPush(ctx context.Context, backend api.Service, opts pushOptions, service
|
||||
|
||||
return backend.Push(ctx, project, api.PushOptions{
|
||||
IgnoreFailures: opts.Ignorefailures,
|
||||
Quiet: opts.Quiet,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func removeCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm [SERVICE...]",
|
||||
Use: "rm [OPTIONS] [SERVICE...]",
|
||||
Short: "Removes stopped service containers",
|
||||
Long: `Removes stopped service containers
|
||||
|
||||
@@ -46,7 +46,7 @@ Any data which is not in a volume will be lost.`,
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRemove(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
|
||||
@@ -59,23 +59,25 @@ Any data which is not in a volume will be lost.`,
|
||||
}
|
||||
|
||||
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.stop {
|
||||
err := backend.Stop(ctx, project, api.StopOptions{
|
||||
err := backend.Stop(ctx, name, api.StopOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return backend.Remove(ctx, project, api.RemoveOptions{
|
||||
return backend.Remove(ctx, name, api.RemoveOptions{
|
||||
Services: services,
|
||||
Force: opts.force,
|
||||
Volumes: opts.volumes,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
restartCmd := &cobra.Command{
|
||||
Use: "restart [SERVICE...]",
|
||||
Use: "restart [OPTIONS] [SERVICE...]",
|
||||
Short: "Restart service containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRestart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := restartCmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
@@ -49,14 +49,15 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.Duration(opts.timeout) * time.Second
|
||||
return backend.Restart(ctx, projectName, api.RestartOptions{
|
||||
return backend.Restart(ctx, name, api.RestartOptions{
|
||||
Timeout: &timeout,
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]",
|
||||
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
|
||||
Short: "Run a one-off command on a service.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
@@ -143,7 +143,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
@@ -151,7 +151,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
||||
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
|
||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
||||
|
||||
@@ -37,18 +37,20 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return startCmd
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Start(ctx, projectName, api.StartOptions{
|
||||
return backend.Start(ctx, name, api.StartOptions{
|
||||
AttachTo: services,
|
||||
Project: project,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "stop [SERVICE...]",
|
||||
Use: "stop [OPTIONS] [SERVICE...]",
|
||||
Short: "Stop services",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
@@ -44,7 +44,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
@@ -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 {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, name, err := opts.projectOrName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,8 +63,9 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return backend.Stop(ctx, projectName, api.StopOptions{
|
||||
return backend.Stop(ctx, name, api.StopOptions{
|
||||
Timeout: timeout,
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func topCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runTop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return topCmd
|
||||
}
|
||||
|
||||
35
cmd/compose/tracing.go
Normal file
35
cmd/compose/tracing.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/moby/buildkit/util/tracing/detect"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
_ "github.com/moby/buildkit/util/tracing/detect/delegated" //nolint:blank-imports
|
||||
_ "github.com/moby/buildkit/util/tracing/env" //nolint:blank-imports
|
||||
)
|
||||
|
||||
func init() {
|
||||
detect.ServiceName = "compose"
|
||||
// do not log tracing errors to stdio
|
||||
otel.SetErrorHandler(skipErrors{})
|
||||
}
|
||||
|
||||
type skipErrors struct{}
|
||||
|
||||
func (skipErrors) Handle(err error) {}
|
||||
@@ -96,7 +96,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up [SERVICE...]",
|
||||
Use: "up [OPTIONS] [SERVICE...]",
|
||||
Short: "Create and start containers",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||
@@ -109,7 +109,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return runUp(ctx, backend, create, up, project, services)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := upCmd.Flags()
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
|
||||
@@ -35,13 +35,18 @@ type versionOptions struct {
|
||||
func versionCommand() *cobra.Command {
|
||||
opts := versionOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Use: "version [OPTIONS]",
|
||||
Short: "Show the Docker Compose version information",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
runVersion(opts)
|
||||
return nil
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// overwrite parent PersistentPreRunE to avoid trying to load
|
||||
// compose file on version command if COMPOSE_FILE is set
|
||||
return nil
|
||||
},
|
||||
}
|
||||
// define flags for backward compatibility with com.docker.cli
|
||||
flags := cmd.Flags()
|
||||
|
||||
@@ -89,7 +89,7 @@ func (l *logConsumer) Log(container, service, message string) {
|
||||
}
|
||||
p := l.getPresenter(container)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) //nolint:errcheck
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,13 @@ import (
|
||||
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
lazyInit := api.NewServiceProxy()
|
||||
cmd := commands.RootCommand(dockerCli, lazyInit)
|
||||
serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli))
|
||||
cmd := commands.RootCommand(dockerCli, serviceProxy)
|
||||
originalPreRun := cmd.PersistentPreRunE
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli))
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
}
|
||||
|
||||
132
docker-bake.hcl
Normal file
132
docker-bake.hcl
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.19.2"
|
||||
}
|
||||
|
||||
variable "BUILD_TAGS" {
|
||||
default = "e2e,kube"
|
||||
}
|
||||
|
||||
variable "DOCS_FORMATS" {
|
||||
default = "md,yaml"
|
||||
}
|
||||
|
||||
# Defines the output folder
|
||||
variable "DESTDIR" {
|
||||
default = ""
|
||||
}
|
||||
function "bindir" {
|
||||
params = [defaultdir]
|
||||
result = DESTDIR != "" ? DESTDIR : "./bin/${defaultdir}"
|
||||
}
|
||||
|
||||
target "_common" {
|
||||
args = {
|
||||
GO_VERSION = GO_VERSION
|
||||
BUILD_TAGS = BUILD_TAGS
|
||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
|
||||
}
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["binary"]
|
||||
}
|
||||
|
||||
group "validate" {
|
||||
targets = ["lint", "vendor-validate", "license-validate"]
|
||||
}
|
||||
|
||||
target "lint" {
|
||||
inherits = ["_common"]
|
||||
target = "lint"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "license-validate" {
|
||||
target = "license-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "license-update" {
|
||||
target = "license-update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "vendor-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "modules-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "modules-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "test" {
|
||||
inherits = ["_common"]
|
||||
target = "test-coverage"
|
||||
output = [bindir("coverage")]
|
||||
}
|
||||
|
||||
target "binary" {
|
||||
inherits = ["_common"]
|
||||
target = "binary"
|
||||
output = [bindir("build")]
|
||||
platforms = ["local"]
|
||||
}
|
||||
|
||||
target "binary-cross" {
|
||||
inherits = ["binary"]
|
||||
platforms = [
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"linux/amd64",
|
||||
"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
"linux/ppc64le",
|
||||
"linux/riscv64",
|
||||
"linux/s390x",
|
||||
"windows/amd64",
|
||||
"windows/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
target "release" {
|
||||
inherits = ["binary-cross"]
|
||||
target = "release"
|
||||
output = [bindir("release")]
|
||||
}
|
||||
|
||||
target "docs-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "docs-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "docs-update" {
|
||||
inherits = ["_common"]
|
||||
target = "docs-update"
|
||||
output = ["./docs"]
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
# syntax=docker/dockerfile:1.3-labs
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.18.4
|
||||
ARG FORMATS=md,yaml
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
|
||||
WORKDIR /src
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/root/.cache,type=cache \
|
||||
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} alpine AS gen
|
||||
RUN apk add --no-cache rsync git
|
||||
WORKDIR /src
|
||||
COPY --from=docsgen /out/docsgen /usr/bin
|
||||
ARG FORMATS
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
docsgen --formats "$FORMATS" --source "docs/reference"
|
||||
mkdir /out
|
||||
cp -r docs/reference /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS update
|
||||
COPY --from=gen /out /out
|
||||
|
||||
FROM gen AS validate
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf docs/reference/*
|
||||
cp -rf /out/* ./docs/
|
||||
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
|
||||
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
|
||||
git status --porcelain -- docs/reference
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
@@ -10,7 +10,7 @@ Stop and remove containers, networks
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
|
||||
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
|
||||
| `-v`, `--volumes` | | | Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers. |
|
||||
| `-v`, `--volumes` | | | Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -7,6 +7,7 @@ Force stop service containers.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Push service images
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
|
||||
| `-q`, `--quiet` | | | Push without printing progress information |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -12,7 +12,7 @@ Run a one-off command on a service.
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
|
||||
| `-l`, `--label` | `stringArray` | | Add or override a label |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
|
||||
| `--no-deps` | | | Don't start linked services. |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
|
||||
|
||||
@@ -10,7 +10,7 @@ long: |-
|
||||
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
run `docker compose build` to rebuild it.
|
||||
usage: docker compose build [SERVICE...]
|
||||
usage: docker compose build [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -7,7 +7,7 @@ long: |-
|
||||
fully defined Compose model.
|
||||
|
||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||
usage: docker compose convert SERVICES
|
||||
usage: docker compose convert [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose create
|
||||
short: Creates containers for a service.
|
||||
long: Creates containers for a service.
|
||||
usage: docker compose create [SERVICE...]
|
||||
usage: docker compose create [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -14,7 +14,7 @@ long: |-
|
||||
Anonymous volumes are not removed by default. However, as they don’t have a stable name, they will not be automatically
|
||||
mounted by a subsequent `up`. For data that needs to persist between updates, use explicit paths as bind mounts or
|
||||
named volumes.
|
||||
usage: docker compose down
|
||||
usage: docker compose down [OPTIONS]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -20,7 +20,7 @@ long: |-
|
||||
```
|
||||
|
||||
The events that can be received using this can be seen [here](/engine/reference/commandline/events/#object-types).
|
||||
usage: docker compose events [options] [--] [SERVICE...]
|
||||
usage: docker compose events [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -5,7 +5,7 @@ long: |-
|
||||
|
||||
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
|
||||
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
||||
usage: docker compose exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]
|
||||
usage: docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose images
|
||||
short: List images used by the created containers
|
||||
long: List images used by the created containers
|
||||
usage: docker compose images [SERVICE...]
|
||||
usage: docker compose images [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -6,10 +6,20 @@ long: |-
|
||||
```console
|
||||
$ docker-compose kill -s SIGINT
|
||||
```
|
||||
usage: docker compose kill [options] [SERVICE...]
|
||||
usage: docker compose kill [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: remove-orphans
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Remove containers for services not defined in the Compose file.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: signal
|
||||
shorthand: s
|
||||
value_type: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose logs
|
||||
short: View output from containers
|
||||
long: Displays log output from services.
|
||||
usage: docker compose logs [SERVICE...]
|
||||
usage: docker compose logs [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose ls
|
||||
short: List running compose projects
|
||||
long: List Compose projects running on platform.
|
||||
usage: docker compose ls
|
||||
usage: docker compose ls [OPTIONS]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose port
|
||||
short: Print the public port for a port binding.
|
||||
long: Prints the public port for a port binding.
|
||||
usage: docker compose port [options] [--] SERVICE PRIVATE_PORT
|
||||
usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -10,7 +10,7 @@ long: |-
|
||||
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||
```
|
||||
usage: docker compose ps [SERVICE...]
|
||||
usage: docker compose ps [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -3,7 +3,7 @@ short: Pull service images
|
||||
long: |-
|
||||
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
|
||||
those images.
|
||||
usage: docker compose pull [SERVICE...]
|
||||
usage: docker compose pull [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -19,7 +19,7 @@ long: |-
|
||||
build: .
|
||||
image: your-dockerid/yourimage ## goes to your repository on Docker Hub
|
||||
```
|
||||
usage: docker compose push [SERVICE...]
|
||||
usage: docker compose push [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
@@ -33,6 +33,17 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Push without printing progress information
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
||||
@@ -11,7 +11,7 @@ long: |-
|
||||
If you are looking to configure a service's restart policy, please refer to
|
||||
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
|
||||
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
|
||||
usage: docker compose restart [SERVICE...]
|
||||
usage: docker compose restart [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -16,7 +16,7 @@ long: |-
|
||||
Are you sure? [yN] y
|
||||
Removing djangoquickstart_web_run_1 ... done
|
||||
```
|
||||
usage: docker compose rm [SERVICE...]
|
||||
usage: docker compose rm [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -54,7 +54,7 @@ long: |-
|
||||
|
||||
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
|
||||
specified in the service configuration.
|
||||
usage: docker compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]
|
||||
usage: docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -2,7 +2,7 @@ command: docker compose stop
|
||||
short: Stop services
|
||||
long: |
|
||||
Stops running containers without removing them. They can be started again with `docker compose start`.
|
||||
usage: docker compose stop [SERVICE...]
|
||||
usage: docker compose stop [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -17,7 +17,7 @@ long: |-
|
||||
|
||||
If the process encounters an error, the exit code for this command is `1`.
|
||||
If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the containers are stopped, and the exit code is `0`.
|
||||
usage: docker compose up [SERVICE...]
|
||||
usage: docker compose up [OPTIONS] [SERVICE...]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
command: docker compose version
|
||||
short: Show the Docker Compose version information
|
||||
long: Show the Docker Compose version information
|
||||
usage: docker compose version
|
||||
usage: docker compose version [OPTIONS]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
|
||||
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"
|
||||
30
e2e/cucumber-features/stop.feature
Normal file
30
e2e/cucumber-features/stop.feature
Normal file
@@ -0,0 +1,30 @@
|
||||
Feature: Stop
|
||||
|
||||
Background:
|
||||
Given a compose file
|
||||
"""
|
||||
services:
|
||||
should_fail:
|
||||
image: alpine
|
||||
command: ls /does_not_exist
|
||||
sleep: # will be killed
|
||||
image: alpine
|
||||
command: ping localhost
|
||||
"""
|
||||
|
||||
Scenario: Cascade stop
|
||||
When I run "compose up --abort-on-container-exit"
|
||||
Then the output contains "should_fail-1 exited with code 1"
|
||||
And the output contains "Aborting on container exit..."
|
||||
And the exit code is 1
|
||||
|
||||
Scenario: Exit code from
|
||||
When I run "compose up --exit-code-from sleep"
|
||||
Then the output contains "should_fail-1 exited with code 1"
|
||||
And the output contains "Aborting on container exit..."
|
||||
And the exit code is 137
|
||||
|
||||
Scenario: Exit code from unknown service
|
||||
When I run "compose up --exit-code-from unknown"
|
||||
Then the output contains "no such service: unknown"
|
||||
And the exit code is 1
|
||||
129
e2e/cucumber_test.go
Normal file
129
e2e/cucumber_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2022 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cucumber
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/colors"
|
||||
"github.com/docker/compose/v2/pkg/e2e"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestCucumber(t *testing.T) {
|
||||
testingOptions := godog.Options{
|
||||
TestingT: t,
|
||||
Paths: []string{"./cucumber-features"},
|
||||
Output: colors.Colored(os.Stdout),
|
||||
Format: "pretty",
|
||||
}
|
||||
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
Options: &testingOptions,
|
||||
ScenarioInitializer: setup,
|
||||
}.Run()
|
||||
|
||||
if status == 2 {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
t.Fatalf("zero status code expected, %d received", status)
|
||||
}
|
||||
}
|
||||
|
||||
func setup(s *godog.ScenarioContext) {
|
||||
t := s.TestingT()
|
||||
cli := e2e.NewCLI(t, e2e.WithEnv(
|
||||
fmt.Sprintf("COMPOSE_PROJECT_NAME=%s", strings.Split(t.Name(), "/")[1]),
|
||||
))
|
||||
th := testHelper{
|
||||
T: t,
|
||||
CLI: cli,
|
||||
}
|
||||
|
||||
s.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
|
||||
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
|
||||
return ctx, nil
|
||||
})
|
||||
|
||||
s.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
|
||||
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
|
||||
return ctx, nil
|
||||
})
|
||||
|
||||
s.Step(`^a compose file$`, th.setComposeFile)
|
||||
s.Step(`^I run "compose (.*)"$`, th.runComposeCommand)
|
||||
s.Step(`service "(.*)" is "(.*)"$`, th.serviceIsStatus)
|
||||
s.Step(`output contains "(.*)"$`, th.outputContains)
|
||||
s.Step(`exit code is (\d+)$`, th.exitCodeIs)
|
||||
}
|
||||
|
||||
type testHelper struct {
|
||||
T *testing.T
|
||||
ComposeFile string
|
||||
CommandOutput string
|
||||
CommandExitCode int
|
||||
CLI *e2e.CLI
|
||||
}
|
||||
|
||||
func (th *testHelper) serviceIsStatus(service, status string) error {
|
||||
res := th.CLI.RunDockerComposeCmd(th.T, "ps", "-a")
|
||||
statusRegex := fmt.Sprintf("%s\\s+%s", service, status)
|
||||
r, _ := regexp.Compile(statusRegex)
|
||||
if !r.MatchString(res.Combined()) {
|
||||
return fmt.Errorf("Missing/incorrect ps output:\n%s", res.Combined())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) outputContains(substring string) error {
|
||||
if !strings.Contains(th.CommandOutput, substring) {
|
||||
return fmt.Errorf("Missing output substring: %s\noutput: %s", substring, th.CommandOutput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) exitCodeIs(exitCode int) error {
|
||||
if exitCode != th.CommandExitCode {
|
||||
return fmt.Errorf("Wrong exit code: %d expected: %d", th.CommandExitCode, exitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) runComposeCommand(command string) error {
|
||||
commandArgs := []string{"-f", "-"}
|
||||
commandArgs = append(commandArgs, strings.Split(command, " ")...)
|
||||
cmd := th.CLI.NewDockerComposeCmd(th.T, commandArgs...)
|
||||
cmd.Stdin = strings.NewReader(th.ComposeFile)
|
||||
res := icmd.RunCmd(cmd)
|
||||
th.CommandOutput = res.Combined()
|
||||
th.CommandExitCode = res.ExitCode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) setComposeFile(composeString string) error {
|
||||
th.ComposeFile = composeString
|
||||
return nil
|
||||
}
|
||||
171
e2e/go.mod
Normal file
171
e2e/go.mod
Normal file
@@ -0,0 +1,171 @@
|
||||
module github.com/docker/compose/v2/e2e
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/docker/compose/v2 => ../
|
||||
|
||||
replace github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
|
||||
|
||||
require (
|
||||
github.com/cucumber/godog v0.12.5
|
||||
github.com/docker/compose/v2 v2.11.1
|
||||
gotest.tools/v3 v3.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cnabio/cnab-go v0.23.4 // indirect
|
||||
github.com/cnabio/cnab-to-oci v0.3.7 // indirect
|
||||
github.com/compose-spec/compose-go v1.6.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.6.8 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31 // indirect
|
||||
github.com/docker/buildx v0.9.1 // indirect
|
||||
github.com/docker/cli v20.10.19+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.19+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.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/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/buildkit v0.10.4 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 // indirect
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cobra v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/weppos/publicsuffix-go v0.20.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.11.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.24.1 // indirect
|
||||
k8s.io/apimachinery v0.24.1 // indirect
|
||||
k8s.io/client-go v0.24.1 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221021173910-5aac513617f0+incompatible // 22.06 branch
|
||||
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
|
||||
// For k8s dependencies, we use a replace directive, to prevent them being
|
||||
// upgraded to the version specified in containerd, which is not relevant to the
|
||||
// version needed.
|
||||
// See https://github.com/docker/buildx/pull/948 for details.
|
||||
// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
|
||||
k8s.io/api => k8s.io/api v0.22.4
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||
)
|
||||
1041
e2e/go.sum
Normal file
1041
e2e/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
88
go.mod
88
go.mod
@@ -1,41 +1,41 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/cnabio/cnab-to-oci v0.3.5
|
||||
github.com/compose-spec/compose-go v1.3.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.7
|
||||
github.com/compose-spec/compose-go v1.6.0
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.6
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb
|
||||
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.8
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31
|
||||
github.com/docker/buildx v0.9.1 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.19+incompatible // v22.06.x - see "replace" for the 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.19+incompatible // v22.06.x - see "replace" for the actual version
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||
github.com/moby/buildkit v0.10.4 // see "replace" for the actual version
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.3.0
|
||||
gotest.tools/v3 v3.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -45,7 +45,7 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // 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.2 // 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
|
||||
@@ -55,13 +55,13 @@ require (
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/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/gorilla/mux v1.8.0 // indirect
|
||||
@@ -69,30 +69,30 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/signal v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
@@ -101,29 +101,28 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel v1.11.1
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // 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-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // 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 // indirect; see replace for the actual version used
|
||||
k8s.io/client-go v0.24.1 // 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
|
||||
@@ -131,14 +130,27 @@ require (
|
||||
)
|
||||
|
||||
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/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||
github.com/zmap/zlint v1.1.0 // indirect
|
||||
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
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221021173910-5aac513617f0+incompatible // 22.06 branch
|
||||
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
type Service interface {
|
||||
// Build executes the equivalent to a `compose build`
|
||||
Build(ctx context.Context, project *types.Project, options BuildOptions) error
|
||||
// Push executes the equivalent ot a `compose push`
|
||||
// Push executes the equivalent to a `compose push`
|
||||
Push(ctx context.Context, project *types.Project, options PushOptions) error
|
||||
// Pull executes the equivalent of a `compose pull`
|
||||
Pull(ctx context.Context, project *types.Project, options PullOptions) error
|
||||
@@ -117,7 +117,7 @@ type CreateOptions struct {
|
||||
|
||||
// StartOptions group options of the Start API
|
||||
type StartOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
// Attach to container and forward logs if not nil
|
||||
Attach LogConsumer
|
||||
@@ -129,10 +129,14 @@ type StartOptions struct {
|
||||
ExitCodeFrom string
|
||||
// Wait won't return until containers reached the running|healthy state
|
||||
Wait bool
|
||||
// Services passed in the command line to be started
|
||||
Services []string
|
||||
}
|
||||
|
||||
// RestartOptions group options of the Restart API
|
||||
type RestartOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
// Timeout override container restart timeout
|
||||
Timeout *time.Duration
|
||||
// Services passed in the command line to be restarted
|
||||
@@ -141,6 +145,8 @@ type RestartOptions struct {
|
||||
|
||||
// StopOptions group options of the Stop API
|
||||
type StopOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
// Timeout override container stop timeout
|
||||
Timeout *time.Duration
|
||||
// Services passed in the command line to be stopped
|
||||
@@ -177,6 +183,7 @@ type ConvertOptions struct {
|
||||
|
||||
// PushOptions group options of the Push API
|
||||
type PushOptions struct {
|
||||
Quiet bool
|
||||
IgnoreFailures bool
|
||||
}
|
||||
|
||||
@@ -193,6 +200,10 @@ type ImagesOptions struct {
|
||||
|
||||
// KillOptions group options of the Kill API
|
||||
type KillOptions struct {
|
||||
// RemoveOrphans will cleanup containers that are not declared on the compose model but own the same labels
|
||||
RemoveOrphans bool
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
// Services passed in the command line to be killed
|
||||
Services []string
|
||||
// Signal to send to containers
|
||||
@@ -201,6 +212,8 @@ type KillOptions struct {
|
||||
|
||||
// RemoveOptions group options of the Remove API
|
||||
type RemoveOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
// DryRun just list removable resources
|
||||
DryRun bool
|
||||
// Volumes remove anonymous volumes
|
||||
@@ -213,6 +226,8 @@ type RemoveOptions struct {
|
||||
|
||||
// RunOptions group options of the Run API
|
||||
type RunOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
Name string
|
||||
Service string
|
||||
Command []string
|
||||
@@ -272,6 +287,7 @@ type ListOptions struct {
|
||||
|
||||
// PsOptions group options of the Ps API
|
||||
type PsOptions struct {
|
||||
Project *types.Project
|
||||
All bool
|
||||
Services []string
|
||||
}
|
||||
@@ -365,6 +381,7 @@ type ServiceStatus struct {
|
||||
|
||||
// LogOptions defines optional parameters for the `Log` API
|
||||
type LogOptions struct {
|
||||
Project *types.Project
|
||||
Services []string
|
||||
Tail string
|
||||
Since string
|
||||
@@ -377,6 +394,8 @@ type LogOptions struct {
|
||||
type PauseOptions struct {
|
||||
// Services passed in the command line to be started
|
||||
Services []string
|
||||
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
|
||||
Project *types.Project
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -414,7 +433,7 @@ type Stack struct {
|
||||
|
||||
// LogConsumer is a callback to process log messages from services
|
||||
type LogConsumer interface {
|
||||
Log(service, container, message string)
|
||||
Log(containerName, service, message string)
|
||||
Status(container, msg string)
|
||||
Register(container string)
|
||||
}
|
||||
@@ -424,7 +443,11 @@ type ContainerEventListener func(event ContainerEvent)
|
||||
|
||||
// ContainerEvent notify an event has been collected on source container implementing Service
|
||||
type ContainerEvent struct {
|
||||
Type int
|
||||
Type int
|
||||
// Container is the name of the container _without the project prefix_.
|
||||
//
|
||||
// This is only suitable for display purposes within Compose, as it's
|
||||
// not guaranteed to be unique across services.
|
||||
Container string
|
||||
Service string
|
||||
Line string
|
||||
|
||||
@@ -51,8 +51,10 @@ const (
|
||||
ImageDigestLabel = "com.docker.compose.image"
|
||||
// DependenciesLabel stores service dependencies
|
||||
DependenciesLabel = "com.docker.compose.depends_on"
|
||||
// VersionLabel stores the compose tool version used to run application
|
||||
// VersionLabel stores the compose tool version used to build/run application
|
||||
VersionLabel = "com.docker.compose.version"
|
||||
// ImageBuilderLabel stores the builder (classic or BuildKit) used to produce the image.
|
||||
ImageBuilderLabel = "com.docker.compose.image.builder"
|
||||
)
|
||||
|
||||
// ComposeVersion is the compose tool version as declared by label VersionLabel
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
var _ Service = &ServiceProxy{}
|
||||
|
||||
// ServiceProxy implements Service by delegating to implementation functions. This allows lazy init and per-method overrides
|
||||
type ServiceProxy struct {
|
||||
BuildFn func(ctx context.Context, project *types.Project, options BuildOptions) error
|
||||
@@ -59,8 +61,6 @@ func NewServiceProxy() *ServiceProxy {
|
||||
// Interceptor allow to customize the compose types.Project before the actual Service method is executed
|
||||
type Interceptor func(ctx context.Context, project *types.Project)
|
||||
|
||||
var _ Service = &ServiceProxy{}
|
||||
|
||||
// WithService configure proxy to use specified Service as delegate
|
||||
func (s *ServiceProxy) WithService(service Service) *ServiceProxy {
|
||||
s.BuildFn = service.Build
|
||||
|
||||
@@ -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"
|
||||
@@ -50,10 +50,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
opts := map[string]build.Options{}
|
||||
var imagesToBuild []string
|
||||
|
||||
args := flatten(options.Args.Resolve(func(s string) (string, bool) {
|
||||
s, ok := project.Environment[s]
|
||||
return s, ok
|
||||
}))
|
||||
args := flatten(options.Args.Resolve(envResolver(project.Environment)))
|
||||
|
||||
services, err := project.GetServices(options.Services...)
|
||||
if err != nil {
|
||||
@@ -84,6 +81,18 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
buildOptions.Exports = []bclient.ExportEntry{{
|
||||
Type: "docker",
|
||||
Attrs: map[string]string{
|
||||
"load": "true",
|
||||
},
|
||||
}}
|
||||
if len(buildOptions.Platforms) > 1 {
|
||||
buildOptions.Exports = []bclient.ExportEntry{{
|
||||
Type: "image",
|
||||
Attrs: map[string]string{},
|
||||
}}
|
||||
}
|
||||
opts[imageName] = buildOptions
|
||||
}
|
||||
|
||||
@@ -141,7 +150,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
if project.Services[i].Labels == nil {
|
||||
project.Services[i].Labels = types.Labels{}
|
||||
}
|
||||
project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
|
||||
project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -164,6 +173,15 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt.Exports = []bclient.ExportEntry{{
|
||||
Type: "docker",
|
||||
Attrs: map[string]string{
|
||||
"load": "true",
|
||||
},
|
||||
}}
|
||||
if opt.Platforms, err = useDockerDefaultOrServicePlatform(project, service, true); err != nil {
|
||||
opt.Platforms = []specs.Platform{}
|
||||
}
|
||||
opts[imageName] = opt
|
||||
continue
|
||||
}
|
||||
@@ -207,32 +225,18 @@ func (s *composeService) doBuild(ctx context.Context, project *types.Project, op
|
||||
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
|
||||
return s.doBuildClassic(ctx, project, opts)
|
||||
}
|
||||
return s.doBuildBuildkit(ctx, project, opts, mode)
|
||||
return s.doBuildBuildkit(ctx, opts, mode)
|
||||
}
|
||||
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
|
||||
var tags []string
|
||||
tags = append(tags, imageTag)
|
||||
|
||||
buildArgs := flatten(service.Build.Args.Resolve(func(s string) (string, bool) {
|
||||
s, ok := project.Environment[s]
|
||||
return s, ok
|
||||
}))
|
||||
buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
|
||||
|
||||
var plats []specs.Platform
|
||||
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
plats = append(plats, p)
|
||||
}
|
||||
if service.Platform != "" {
|
||||
p, err := platforms.Parse(service.Platform)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
plats = append(plats, p)
|
||||
plats, err := addPlatforms(project, service)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
|
||||
@@ -245,7 +249,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
}
|
||||
|
||||
sessionConfig := []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(s.stderr()),
|
||||
authprovider.NewDockerAuthProvider(s.configFile()),
|
||||
}
|
||||
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
|
||||
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
|
||||
@@ -267,6 +271,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
tags = append(tags, service.Build.Tags...)
|
||||
}
|
||||
|
||||
imageLabels := getImageBuildLabels(project, service)
|
||||
|
||||
return build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: service.Build.Context,
|
||||
@@ -281,7 +287,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
Target: service.Build.Target,
|
||||
Exports: []bclient.ExportEntry{{Type: "image", Attrs: map[string]string{}}},
|
||||
Platforms: plats,
|
||||
Labels: service.Build.Labels,
|
||||
Labels: imageLabels,
|
||||
NetworkMode: service.Build.Network,
|
||||
ExtraHosts: service.Build.ExtraHosts.AsList(),
|
||||
Session: sessionConfig,
|
||||
@@ -331,7 +337,6 @@ func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
|
||||
}
|
||||
|
||||
func addSecretsConfig(project *types.Project, service types.ServiceConfig) (session.Attachable, error) {
|
||||
|
||||
var sources []secretsprovider.Source
|
||||
for _, secret := range service.Build.Secrets {
|
||||
config := project.Secrets[secret.Source]
|
||||
@@ -356,3 +361,70 @@ func addSecretsConfig(project *types.Project, service types.ServiceConfig) (sess
|
||||
}
|
||||
return secretsprovider.NewSecretProvider(store), nil
|
||||
}
|
||||
|
||||
func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.Platform, error) {
|
||||
plats, err := useDockerDefaultOrServicePlatform(project, service, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, buildPlatform := range service.Build.Platforms {
|
||||
p, err := platforms.Parse(buildPlatform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !utils.Contains(plats, p) {
|
||||
plats = append(plats, p)
|
||||
}
|
||||
}
|
||||
return plats, nil
|
||||
}
|
||||
|
||||
func getImageBuildLabels(project *types.Project, service types.ServiceConfig) types.Labels {
|
||||
ret := make(types.Labels)
|
||||
if service.Build != nil {
|
||||
for k, v := range service.Build.Labels {
|
||||
ret.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
ret.Add(api.VersionLabel, api.ComposeVersion)
|
||||
ret.Add(api.ProjectLabel, project.Name)
|
||||
ret.Add(api.ServiceLabel, service.Name)
|
||||
return ret
|
||||
}
|
||||
|
||||
func useDockerDefaultPlatform(project *types.Project, platformList types.StringList) ([]specs.Platform, error) {
|
||||
var plats []specs.Platform
|
||||
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
|
||||
if len(platformList) > 0 && !utils.StringContains(platformList, platform) {
|
||||
return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM %q value should be part of the service.build.platforms: %q", platform, platformList)
|
||||
}
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plats = append(plats, p)
|
||||
}
|
||||
return plats, nil
|
||||
}
|
||||
|
||||
func useDockerDefaultOrServicePlatform(project *types.Project, service types.ServiceConfig, useOnePlatform bool) ([]specs.Platform, error) {
|
||||
plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
|
||||
if (len(plats) > 0 && useOnePlatform) || err != nil {
|
||||
return plats, err
|
||||
}
|
||||
|
||||
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
return nil, fmt.Errorf("service.platform %q should be part of the service.build.platforms: %q", service.Platform, service.Build.Platforms)
|
||||
}
|
||||
// User defined a service platform and no build platforms, so we should keep the one define on the service level
|
||||
p, err := platforms.Parse(service.Platform)
|
||||
if !utils.Contains(plats, p) {
|
||||
plats = append(plats, p)
|
||||
}
|
||||
return plats, err
|
||||
}
|
||||
return plats, nil
|
||||
}
|
||||
|
||||
@@ -18,27 +18,36 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
ctxstore "github.com/docker/cli/cli/context/store"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/driver"
|
||||
_ "github.com/docker/buildx/driver/docker" //nolint:blank-imports
|
||||
_ "github.com/docker/buildx/driver/docker-container" //nolint:blank-imports
|
||||
_ "github.com/docker/buildx/driver/kubernetes" //nolint:blank-imports
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
const drivername = "default"
|
||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient(), s.configFile(), nil, nil, nil, nil, nil, project.WorkingDir)
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
dis, err := s.getDrivers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverInfo := []build.DriverInfo{
|
||||
{
|
||||
Name: drivername,
|
||||
Driver: d,
|
||||
},
|
||||
}
|
||||
|
||||
// Progress needs its own context that lives longer than the
|
||||
// build one otherwise it won't read all the messages from
|
||||
@@ -47,8 +56,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
|
||||
defer cancel()
|
||||
w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
|
||||
|
||||
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
||||
response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile().Filename), w)
|
||||
response, err := build.Build(ctx, dis, opts, &internalAPI{dockerCli: s.dockerCli}, filepath.Dir(s.configFile().Filename), w)
|
||||
errW := w.Wait()
|
||||
if err == nil {
|
||||
err = errW
|
||||
@@ -71,3 +79,187 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
|
||||
|
||||
return imagesBuilt, err
|
||||
}
|
||||
|
||||
func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, error) { //nolint:gocyclo
|
||||
txn, release, err := storeutil.GetStore(s.dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
ng, err := storeutil.GetCurrentInstance(txn, s.dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dis := make([]build.DriverInfo, len(ng.Nodes))
|
||||
var f driver.Factory
|
||||
if ng.Driver != "" {
|
||||
factories := driver.GetFactories(true)
|
||||
for _, fac := range factories {
|
||||
if fac.Name() == ng.Driver {
|
||||
f = fac
|
||||
continue
|
||||
}
|
||||
}
|
||||
if f == nil {
|
||||
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 {
|
||||
ep := ng.Nodes[0].Endpoint
|
||||
dockerapi, err := clientForEndpoint(s.dockerCli, ep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ng.Driver = f.Name()
|
||||
}
|
||||
|
||||
imageopt, err := storeutil.GetImageConfig(s.dockerCli, ng)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for i, n := range ng.Nodes {
|
||||
func(i int, n store.Node) {
|
||||
eg.Go(func() error {
|
||||
di := build.DriverInfo{
|
||||
Name: n.Name,
|
||||
Platform: n.Platforms,
|
||||
ProxyConfig: storeutil.GetProxyConfig(s.dockerCli),
|
||||
}
|
||||
defer func() {
|
||||
dis[i] = di
|
||||
}()
|
||||
|
||||
dockerapi, err := clientForEndpoint(s.dockerCli, n.Endpoint)
|
||||
if err != nil {
|
||||
di.Err = err
|
||||
return nil
|
||||
}
|
||||
// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
|
||||
dockerapi.NegotiateAPIVersion(ctx)
|
||||
|
||||
contextStore := s.dockerCli.ContextStore()
|
||||
|
||||
var kcc driver.KubeClientConfig
|
||||
kcc, err = configFromContext(n.Endpoint, contextStore)
|
||||
if err != nil {
|
||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||
// try again with name="default".
|
||||
// FIXME: n should retain real context name.
|
||||
kcc, err = configFromContext("default", contextStore)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
tryToUseKubeConfigInCluster := false
|
||||
if kcc == nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
} else {
|
||||
if _, err := kcc.ClientConfig(); err != nil {
|
||||
tryToUseKubeConfigInCluster = true
|
||||
}
|
||||
}
|
||||
if tryToUseKubeConfigInCluster {
|
||||
kccInCluster := driver.KubeClientConfigInCluster{}
|
||||
if _, err := kccInCluster.ClientConfig(); err == nil {
|
||||
logrus.Debug("using kube config in cluster")
|
||||
kcc = kccInCluster
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
di.Driver = d
|
||||
di.ImageOpt = imageopt
|
||||
return nil
|
||||
})
|
||||
}(i, n)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dis, nil
|
||||
}
|
||||
|
||||
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, l := range list {
|
||||
if l.Name != name {
|
||||
continue
|
||||
}
|
||||
dep, ok := l.Endpoints["docker"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("context %q does not have a Docker endpoint", name)
|
||||
}
|
||||
epm, ok := dep.(docker.EndpointMeta)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
|
||||
}
|
||||
ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOpts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
ep := docker.Endpoint{
|
||||
EndpointMeta: docker.EndpointMeta{
|
||||
Host: name,
|
||||
},
|
||||
}
|
||||
|
||||
clientOpts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
|
||||
if strings.HasPrefix(endpointName, "kubernetes://") {
|
||||
u, _ := url.Parse(endpointName)
|
||||
if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
|
||||
_ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
|
||||
}
|
||||
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
apiConfig, err := rules.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
|
||||
}
|
||||
return ctxkube.ConfigFromContext(endpointName, s)
|
||||
}
|
||||
|
||||
type internalAPI struct {
|
||||
dockerCli command.Cli
|
||||
}
|
||||
|
||||
func (a *internalAPI) DockerAPI(name string) (dockerclient.APIClient, error) {
|
||||
if name == "" {
|
||||
name = a.dockerCli.CurrentContext()
|
||||
}
|
||||
return clientForEndpoint(a.dockerCli, name)
|
||||
}
|
||||
|
||||
@@ -29,17 +29,18 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, opts map[string]buildx.Options) (map[string]string, error) {
|
||||
@@ -65,7 +66,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
|
||||
return nameDigests, errs
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
@@ -88,6 +89,15 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
}
|
||||
}
|
||||
|
||||
if len(options.Platforms) > 1 {
|
||||
return "", errors.Errorf("this builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use multi-arch builder")
|
||||
}
|
||||
|
||||
if options.Labels == nil {
|
||||
options.Labels = make(map[string]string)
|
||||
}
|
||||
options.Labels[api.ImageBuilderLabel] = "classic"
|
||||
|
||||
switch {
|
||||
case isLocalDir(specifiedContext):
|
||||
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName)
|
||||
|
||||
@@ -17,23 +17,24 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sanathkr/go-yaml"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
// NewComposeService create a local implementation of the compose.Service API
|
||||
@@ -94,28 +95,14 @@ func getContainerNameWithoutProject(c moby.Container) string {
|
||||
func (s *composeService) Convert(ctx context.Context, project *types.Project, options api.ConvertOptions) ([]byte, error) {
|
||||
switch options.Format {
|
||||
case "json":
|
||||
marshal, err := json.MarshalIndent(project, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return escapeDollarSign(marshal), nil
|
||||
return json.MarshalIndent(project, "", " ")
|
||||
case "yaml":
|
||||
marshal, err := yaml.Marshal(project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return escapeDollarSign(marshal), nil
|
||||
return yaml.Marshal(project)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format %q", options)
|
||||
}
|
||||
}
|
||||
|
||||
func escapeDollarSign(marshal []byte) []byte {
|
||||
dollar := []byte{'$'}
|
||||
escDollar := []byte{'$', '$'}
|
||||
return bytes.ReplaceAll(marshal, dollar, escDollar)
|
||||
}
|
||||
|
||||
// projectFromName builds a types.Project based on actual resources with compose labels set
|
||||
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
|
||||
project := &types.Project{
|
||||
@@ -172,26 +159,6 @@ SERVICES:
|
||||
return project, nil
|
||||
}
|
||||
|
||||
// actualState list resources labelled by projectName to rebuild compose project model
|
||||
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
|
||||
var containers Containers
|
||||
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
project, err := s.projectFromName(containers, projectName, services...)
|
||||
if err != nil && !api.IsNotFoundError(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
containers = containers.filter(isService(services...))
|
||||
}
|
||||
return containers, project, nil
|
||||
}
|
||||
|
||||
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
|
||||
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
|
||||
@@ -30,13 +30,13 @@ const (
|
||||
// ContainerRunning running status
|
||||
ContainerRunning = "running"
|
||||
// ContainerRemoving removing status
|
||||
ContainerRemoving = "removing" //nolint
|
||||
ContainerRemoving = "removing"
|
||||
// ContainerPaused paused status
|
||||
ContainerPaused = "paused" //nolint
|
||||
ContainerPaused = "paused"
|
||||
// ContainerExited exited status
|
||||
ContainerExited = "exited" //nolint
|
||||
ContainerExited = "exited"
|
||||
// ContainerDead dead status
|
||||
ContainerDead = "dead" //nolint
|
||||
ContainerDead = "dead"
|
||||
)
|
||||
|
||||
var _ io.ReadCloser = ContainerStdout{}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -180,7 +181,10 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
// Scale Down
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{
|
||||
Timeout: timeoutInSecond,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -352,6 +356,12 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
|
||||
return false, nil
|
||||
}
|
||||
if service, err := project.GetService(serviceName); err != nil {
|
||||
for _, ds := range project.DisabledServices {
|
||||
if ds.Name == serviceName {
|
||||
// don't wait for disabled service (--no-deps)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
} else if service.Scale == 0 {
|
||||
// don't wait for the dependency which configured to have 0 containers running
|
||||
@@ -406,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
|
||||
}
|
||||
@@ -605,8 +616,9 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
||||
ipv4Address = cfg.Ipv4Address
|
||||
ipv6Address = cfg.Ipv6Address
|
||||
ipam = &network.EndpointIPAMConfig{
|
||||
IPv4Address: ipv4Address,
|
||||
IPv6Address: ipv6Address,
|
||||
IPv4Address: ipv4Address,
|
||||
IPv6Address: ipv6Address,
|
||||
LinkLocalIPs: cfg.LinkLocalIPs,
|
||||
}
|
||||
}
|
||||
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
||||
|
||||
@@ -23,12 +23,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
)
|
||||
|
||||
func TestContainerName(t *testing.T) {
|
||||
@@ -77,7 +78,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db"}
|
||||
@@ -99,7 +102,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:db"}
|
||||
@@ -121,7 +126,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
@@ -143,7 +150,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
@@ -169,7 +178,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{}
|
||||
@@ -203,7 +214,9 @@ func TestWaitDependencies(t *testing.T) {
|
||||
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
tested := composeService{
|
||||
dockerCli: cli,
|
||||
}
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
t.Run("should skip dependencies with scale 0", func(t *testing.T) {
|
||||
|
||||
@@ -125,7 +125,8 @@ func prepareVolumes(p *types.Project) error {
|
||||
p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
|
||||
}
|
||||
for _, service := range p.Services {
|
||||
if utils.StringContains(dependServices, service.Name) {
|
||||
if utils.StringContains(dependServices, service.Name) &&
|
||||
p.Services[i].DependsOn[service.Name].Condition == "" {
|
||||
p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
|
||||
Condition: types.ServiceConditionStarted,
|
||||
}
|
||||
@@ -146,6 +147,9 @@ func prepareNetworks(project *types.Project) {
|
||||
}
|
||||
|
||||
func prepareServicesDependsOn(p *types.Project) error {
|
||||
allServices := types.Project{}
|
||||
allServices.Services = p.AllServices()
|
||||
|
||||
for i, service := range p.Services {
|
||||
var dependencies []string
|
||||
networkDependency := getDependentServiceFromMode(service.NetworkMode)
|
||||
@@ -178,20 +182,24 @@ func prepareServicesDependsOn(p *types.Project) error {
|
||||
dependencies = append(dependencies, strings.Split(link, ":")[0])
|
||||
}
|
||||
|
||||
for d := range service.DependsOn {
|
||||
dependencies = append(dependencies, d)
|
||||
}
|
||||
|
||||
if len(dependencies) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify dependencies exist in the project, whether disabled or not
|
||||
deps, err := allServices.GetServices(dependencies...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if service.DependsOn == nil {
|
||||
service.DependsOn = make(types.DependsOnConfig)
|
||||
}
|
||||
|
||||
// Verify dependencies exist in the project, whether disabled or not
|
||||
projAllServices := types.Project{}
|
||||
projAllServices.Services = p.AllServices()
|
||||
deps, err := projAllServices.GetServices(dependencies...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range deps {
|
||||
if _, ok := service.DependsOn[d.Name]; !ok {
|
||||
service.DependsOn[d.Name] = types.ServiceDependency{
|
||||
@@ -306,8 +314,9 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
ipv4Address = config.Ipv4Address
|
||||
ipv6Address = config.Ipv6Address
|
||||
ipam = &network.EndpointIPAMConfig{
|
||||
IPv4Address: ipv4Address,
|
||||
IPv6Address: ipv6Address,
|
||||
IPv4Address: ipv4Address,
|
||||
IPv6Address: ipv6Address,
|
||||
LinkLocalIPs: config.LinkLocalIPs,
|
||||
}
|
||||
}
|
||||
networkConfig = &network.NetworkingConfig{
|
||||
@@ -1146,7 +1155,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volume_api.CreateOptions{
|
||||
Labels: volume.Labels,
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
|
||||
@@ -96,6 +96,46 @@ func TestPrepareNetworkLabels(t *testing.T) {
|
||||
}))
|
||||
}
|
||||
|
||||
func TestPrepareVolumes(t *testing.T) {
|
||||
t.Run("adds dependency condition if service depends on volume from another service", func(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
{
|
||||
Name: "aService",
|
||||
VolumesFrom: []string{"anotherService"},
|
||||
},
|
||||
{
|
||||
Name: "anotherService",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := prepareVolumes(&project)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionStarted)
|
||||
})
|
||||
t.Run("doesn't overwrite existing dependency condition", func(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
{
|
||||
Name: "aService",
|
||||
VolumesFrom: []string{"anotherService"},
|
||||
DependsOn: map[string]composetypes.ServiceDependency{
|
||||
"anotherService": {Condition: composetypes.ServiceConditionHealthy},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "anotherService",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := prepareVolumes(&project)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, project.Services[0].DependsOn["anotherService"].Condition, composetypes.ServiceConditionHealthy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildContainerMountOptions(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
|
||||
@@ -37,80 +37,110 @@ const (
|
||||
ServiceStarted
|
||||
)
|
||||
|
||||
type graphTraversalConfig struct {
|
||||
type graphTraversal struct {
|
||||
mu sync.Mutex
|
||||
seen map[string]struct{}
|
||||
|
||||
extremityNodesFn func(*Graph) []*Vertex // leaves or roots
|
||||
adjacentNodesFn func(*Vertex) []*Vertex // getParents or getChildren
|
||||
filterAdjacentByStatusFn func(*Graph, string, ServiceStatus) []*Vertex // filterChildren or filterParents
|
||||
targetServiceStatus ServiceStatus
|
||||
adjacentServiceStatusToSkip ServiceStatus
|
||||
|
||||
visitorFn func(context.Context, string) error
|
||||
}
|
||||
|
||||
var (
|
||||
upDirectionTraversalConfig = graphTraversalConfig{
|
||||
func upDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
|
||||
return &graphTraversal{
|
||||
extremityNodesFn: leaves,
|
||||
adjacentNodesFn: getParents,
|
||||
filterAdjacentByStatusFn: filterChildren,
|
||||
adjacentServiceStatusToSkip: ServiceStopped,
|
||||
targetServiceStatus: ServiceStarted,
|
||||
visitorFn: visitorFn,
|
||||
}
|
||||
downDirectionTraversalConfig = graphTraversalConfig{
|
||||
}
|
||||
|
||||
func downDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
|
||||
return &graphTraversal{
|
||||
extremityNodesFn: roots,
|
||||
adjacentNodesFn: getChildren,
|
||||
filterAdjacentByStatusFn: filterParents,
|
||||
adjacentServiceStatusToSkip: ServiceStarted,
|
||||
targetServiceStatus: ServiceStopped,
|
||||
visitorFn: visitorFn,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// InDependencyOrder applies the function to the services of the project taking in account the dependency order
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
|
||||
return visit(ctx, project, upDirectionTraversalConfig, fn, ServiceStopped)
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversal)) error {
|
||||
graph, err := NewGraph(project.Services, ServiceStopped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := upDirectionTraversal(fn)
|
||||
return t.visit(ctx, graph)
|
||||
}
|
||||
|
||||
// InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
|
||||
func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error) error {
|
||||
return visit(ctx, project, downDirectionTraversalConfig, fn, ServiceStarted)
|
||||
}
|
||||
|
||||
func visit(ctx context.Context, project *types.Project, traversalConfig graphTraversalConfig, fn func(context.Context, string) error, initialStatus ServiceStatus) error {
|
||||
g := NewGraph(project.Services, initialStatus)
|
||||
if b, err := g.HasCycles(); b {
|
||||
graph, err := NewGraph(project.Services, ServiceStarted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := downDirectionTraversal(fn)
|
||||
return t.visit(ctx, graph)
|
||||
}
|
||||
|
||||
nodes := traversalConfig.extremityNodesFn(g)
|
||||
func (t *graphTraversal) visit(ctx context.Context, g *Graph) error {
|
||||
nodes := t.extremityNodesFn(g)
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
eg.Go(func() error {
|
||||
return run(ctx, g, eg, nodes, traversalConfig, fn)
|
||||
})
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
t.run(ctx, g, eg, nodes)
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
// Note: this could be `graph.walk` or whatever
|
||||
func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
|
||||
func (t *graphTraversal) run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex) {
|
||||
for _, node := range nodes {
|
||||
// Don't start this service yet if all of its children have
|
||||
// not been started yet.
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
if len(t.filterAdjacentByStatusFn(graph, node.Key, t.adjacentServiceStatusToSkip)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
node := node
|
||||
if !t.consume(node.Key) {
|
||||
// another worker already visited this node
|
||||
continue
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
err := fn(ctx, node.Service)
|
||||
err := t.visitorFn(ctx, node.Service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
|
||||
graph.UpdateStatus(node.Key, t.targetServiceStatus)
|
||||
|
||||
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
|
||||
t.run(ctx, graph, eg, t.adjacentNodesFn(node))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (t *graphTraversal) consume(nodeKey string) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.seen == nil {
|
||||
t.seen = make(map[string]struct{})
|
||||
}
|
||||
if _, ok := t.seen[nodeKey]; ok {
|
||||
return false
|
||||
}
|
||||
t.seen[nodeKey] = struct{}{}
|
||||
return true
|
||||
}
|
||||
|
||||
// Graph represents project as service dependencies
|
||||
@@ -155,7 +185,7 @@ func (v *Vertex) GetChildren() []*Vertex {
|
||||
}
|
||||
|
||||
// NewGraph returns the dependency graph of the services
|
||||
func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
|
||||
func NewGraph(services types.Services, initialStatus ServiceStatus) (*Graph, error) {
|
||||
graph := &Graph{
|
||||
lock: sync.RWMutex{},
|
||||
Vertices: map[string]*Vertex{},
|
||||
@@ -171,7 +201,11 @@ func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
|
||||
}
|
||||
}
|
||||
|
||||
return graph
|
||||
if b, err := graph.HasCycles(); b {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
// NewVertex is the constructor function for the Vertex
|
||||
|
||||
@@ -18,10 +18,13 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"gotest.tools/v3/assert"
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
var project = types.Project{
|
||||
@@ -44,26 +47,251 @@ var project = types.Project{
|
||||
},
|
||||
}
|
||||
|
||||
func TestInDependencyUpCommandOrder(t *testing.T) {
|
||||
order := make(chan string)
|
||||
//nolint:errcheck, unparam
|
||||
go InDependencyOrder(context.TODO(), &project, func(ctx context.Context, config string) error {
|
||||
order <- config
|
||||
func TestTraversalWithMultipleParents(t *testing.T) {
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent",
|
||||
DependsOn: make(types.DependsOnConfig),
|
||||
}
|
||||
|
||||
project := types.Project{
|
||||
Services: []types.ServiceConfig{dependent},
|
||||
}
|
||||
|
||||
for i := 1; i <= 100; i++ {
|
||||
name := fmt.Sprintf("svc_%d", i)
|
||||
dependent.DependsOn[name] = types.ServiceDependency{}
|
||||
|
||||
svc := types.ServiceConfig{Name: name}
|
||||
project.Services = append(project.Services, svc)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
svc := make(chan string, 10)
|
||||
seen := make(map[string]int)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for service := range svc {
|
||||
seen[service]++
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
err := InDependencyOrder(ctx, &project, func(ctx context.Context, service string) error {
|
||||
svc <- service
|
||||
return nil
|
||||
})
|
||||
assert.Equal(t, <-order, "test3")
|
||||
assert.Equal(t, <-order, "test2")
|
||||
assert.Equal(t, <-order, "test1")
|
||||
require.NoError(t, err, "Error during iteration")
|
||||
close(svc)
|
||||
<-done
|
||||
|
||||
testify.Len(t, seen, 101)
|
||||
for svc, count := range seen {
|
||||
assert.Equal(t, 1, count, "Service: %s", svc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInDependencyUpCommandOrder(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
var order []string
|
||||
err := InDependencyOrder(ctx, &project, func(ctx context.Context, service string) error {
|
||||
order = append(order, service)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err, "Error during iteration")
|
||||
require.Equal(t, []string{"test3", "test2", "test1"}, order)
|
||||
}
|
||||
|
||||
func TestInDependencyReverseDownCommandOrder(t *testing.T) {
|
||||
order := make(chan string)
|
||||
//nolint:errcheck, unparam
|
||||
go InReverseDependencyOrder(context.TODO(), &project, func(ctx context.Context, config string) error {
|
||||
order <- config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
var order []string
|
||||
err := InReverseDependencyOrder(ctx, &project, func(ctx context.Context, service string) error {
|
||||
order = append(order, service)
|
||||
return nil
|
||||
})
|
||||
assert.Equal(t, <-order, "test1")
|
||||
assert.Equal(t, <-order, "test2")
|
||||
assert.Equal(t, <-order, "test3")
|
||||
require.NoError(t, err, "Error during iteration")
|
||||
require.Equal(t, []string{"test1", "test2", "test3"}, order)
|
||||
}
|
||||
|
||||
func TestBuildGraph(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
services types.Services
|
||||
expectedVertices map[string]*Vertex
|
||||
}{
|
||||
{
|
||||
desc: "builds graph with single service",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with two separate services",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with a service and a dependency",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{
|
||||
"test": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "builds graph with multiple dependency levels",
|
||||
services: types.Services{
|
||||
{
|
||||
Name: "test",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another",
|
||||
DependsOn: types.DependsOnConfig{
|
||||
"another_dep": types.ServiceDependency{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "another_dep",
|
||||
DependsOn: types.DependsOnConfig{},
|
||||
},
|
||||
},
|
||||
expectedVertices: map[string]*Vertex{
|
||||
"test": {
|
||||
Key: "test",
|
||||
Service: "test",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{},
|
||||
},
|
||||
"another": {
|
||||
Key: "another",
|
||||
Service: "another",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{
|
||||
"another_dep": {},
|
||||
},
|
||||
Parents: map[string]*Vertex{
|
||||
"test": {},
|
||||
},
|
||||
},
|
||||
"another_dep": {
|
||||
Key: "another_dep",
|
||||
Service: "another_dep",
|
||||
Status: ServiceStopped,
|
||||
Children: map[string]*Vertex{},
|
||||
Parents: map[string]*Vertex{
|
||||
"another": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tC := range testCases {
|
||||
t.Run(tC.desc, func(t *testing.T) {
|
||||
project := types.Project{
|
||||
Services: tC.services,
|
||||
}
|
||||
|
||||
graph, err := NewGraph(project.Services, ServiceStopped)
|
||||
assert.NilError(t, err, fmt.Sprintf("failed to build graph for: %s", tC.desc))
|
||||
|
||||
for k, vertex := range graph.Vertices {
|
||||
expected, ok := tC.expectedVertices[k]
|
||||
assert.Equal(t, true, ok)
|
||||
assert.Equal(t, true, isVertexEqual(*expected, *vertex))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isVertexEqual(a, b Vertex) bool {
|
||||
childrenEquality := true
|
||||
for c := range a.Children {
|
||||
if _, ok := b.Children[c]; !ok {
|
||||
childrenEquality = false
|
||||
}
|
||||
}
|
||||
parentEquality := true
|
||||
for p := range a.Parents {
|
||||
if _, ok := b.Parents[p]; !ok {
|
||||
parentEquality = false
|
||||
}
|
||||
}
|
||||
return a.Key == b.Key &&
|
||||
a.Service == b.Service &&
|
||||
childrenEquality &&
|
||||
parentEquality
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user