mirror of
https://github.com/docker/compose.git
synced 2026-02-10 18:49:28 +08:00
Compare commits
4 Commits
v2.24.4
...
dispatch-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b2ca93981 | ||
|
|
963e2e8757 | ||
|
|
342ddc5ee5 | ||
|
|
3b7f5ffc6b |
@@ -1 +1,2 @@
|
||||
bin/
|
||||
dist/
|
||||
|
||||
64
.github/ISSUE_TEMPLATE.md
vendored
Normal file
64
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
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
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,49 +0,0 @@
|
||||
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
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +0,0 @@
|
||||
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
13
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Feature request
|
||||
description: Missing functionality? Come tell us about it!
|
||||
labels:
|
||||
- kind/feature
|
||||
- status/0-triage
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What is the feature you want to see?
|
||||
validations:
|
||||
required: true
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,4 +3,4 @@
|
||||
**Related issue**
|
||||
<!-- If this is a bug fix, make sure your description includes "fixes #xxxx", or "closes #xxxx" -->
|
||||
|
||||
**(not mandatory) A picture of a cute animal, if possible in relation to what you did**
|
||||
**(not mandatory) A picture of a cute animal, if possible in relation with what you did**
|
||||
|
||||
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
@@ -4,21 +4,3 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
ignore:
|
||||
# docker + moby deps require coordination
|
||||
- dependency-name: "github.com/docker/buildx"
|
||||
# buildx is still 0.x
|
||||
update-types: ["version-update:semver-minor"]
|
||||
- dependency-name: "github.com/moby/buildkit"
|
||||
# buildkit is still 0.x
|
||||
update-types: [ "version-update:semver-minor" ]
|
||||
- dependency-name: "github.com/docker/cli"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "github.com/docker/docker"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "github.com/containerd/containerd"
|
||||
# containerd major/minor must be kept in sync with moby
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
- dependency-name: "go.opentelemetry.io/otel/*"
|
||||
# OTEL is v1.x but has some parts that are not API stable yet
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor"]
|
||||
|
||||
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -12,7 +12,7 @@ onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- "kind/feature"
|
||||
- "enhancement ✨"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
63
.github/workflows/artifacts.yml
vendored
Normal file
63
.github/workflows/artifacts.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Publish Artifacts
|
||||
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: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build cross platform compose-plugin binaries
|
||||
run: make -f builder.Makefile cross
|
||||
|
||||
- name: Upload macos-amd64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-darwin-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-darwin-amd64
|
||||
|
||||
- name: Upload macos-arm64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-darwin-arm64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-darwin-arm64
|
||||
|
||||
- name: Upload linux-amd64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-linux-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
||||
|
||||
- name: Upload linux-ppc64le binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-linux-ppc64le
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
|
||||
|
||||
- name: Upload windows-amd64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
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
|
||||
376
.github/workflows/ci.yml
vendored
376
.github/workflows/ci.yml
vendored
@@ -1,15 +1,9 @@
|
||||
name: ci
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
name: Continuous integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
- v2
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -18,266 +12,134 @@ on:
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
DOCKER_CLI_VERSION: "25.0.1"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
run: |
|
||||
echo matrix=$(docker buildx bake binary-cross --print | jq -cr '.target."binary-cross".platforms') >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Show matrix
|
||||
run: |
|
||||
echo ${{ steps.platforms.outputs.matrix }}
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- lint
|
||||
- validate-go-mod
|
||||
- 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: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
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: ./bin/release/*
|
||||
if-no-files-found: error
|
||||
- name: Validate go-mod, license headers and docs are up-to-date
|
||||
run: make validate
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
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: Gather coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-data-unit
|
||||
path: bin/coverage/unit/
|
||||
if-no-files-found: error
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
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-file: 'go.mod'
|
||||
check-latest: true
|
||||
cache: true
|
||||
-
|
||||
name: Setup docker CLI
|
||||
run: |
|
||||
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
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: binary-with-coverage
|
||||
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
|
||||
- name: Run golangci-lint
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
-
|
||||
name: Setup tmate session
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
uses: mxschmitt/action-tmate@8b4e4ac71822ed7e0ad5fb3d1c33483e9e8fb270 # v3.11
|
||||
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'
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
# 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
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Test
|
||||
run: make -f builder.Makefile test
|
||||
|
||||
- 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
|
||||
|
||||
- name: E2E Test in plugin mode
|
||||
run: make e2e-compose
|
||||
|
||||
build-standalone:
|
||||
name: Build and tests in standalone mode
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Test plugin mode
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
run: |
|
||||
rm -rf ./bin/coverage/e2e
|
||||
mkdir -p ./bin/coverage/e2e
|
||||
make e2e-compose GOCOVERDIR=bin/coverage/e2e TEST_FLAGS="-v"
|
||||
-
|
||||
name: Gather coverage data
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-data-e2e
|
||||
path: bin/coverage/e2e/
|
||||
if-no-files-found: error
|
||||
-
|
||||
name: Test standalone mode
|
||||
if: ${{ matrix.mode == 'standalone' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
|
||||
- name: E2E Test in standalone mode
|
||||
run: |
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/build/docker-compose /usr/local/bin
|
||||
cp bin/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
- e2e
|
||||
steps:
|
||||
# codecov won't process the report without the source code available
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
- name: Download unit test coverage
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage-data-unit
|
||||
path: coverage/unit
|
||||
- name: Download E2E test coverage
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage-data-e2e
|
||||
path: coverage/e2e
|
||||
- name: Merge coverage reports
|
||||
run: |
|
||||
go tool covdata textfmt -i=./coverage/unit,./coverage/e2e -o ./coverage.txt
|
||||
- name: Store coverage report in GitHub Actions
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: go-covdata-txt
|
||||
path: ./coverage.txt
|
||||
if-no-files-found: error
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./coverage.txt
|
||||
|
||||
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: bin/release
|
||||
-
|
||||
name: Create checksums
|
||||
working-directory: bin/release
|
||||
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/* bin/release/
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh bin/release
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find bin/release -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: bin/release/*
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
58
.github/workflows/codeql.yml
vendored
58
.github/workflows/codeql.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- go
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
22
.github/workflows/dispatch-release.yml
vendored
Normal file
22
.github/workflows/dispatch-release.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: dispatch release event to downstream repositories
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dispatch_release_version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.PAT_DOC_TOKEN }}
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'docker',
|
||||
repo: 'docker.github.io',
|
||||
workflow_id: 'compose_release',
|
||||
ref: 'compose-api-upstream',
|
||||
inputs: {
|
||||
release: ${{ GITHUB_REF }},
|
||||
},
|
||||
})
|
||||
42
.github/workflows/docs-upstream.yml
vendored
42
.github/workflows/docs-upstream.yml
vendored
@@ -1,42 +0,0 @@
|
||||
# this workflow runs the remote validate bake target from docker/docs
|
||||
# to check if yaml reference docs used in this repo are valid
|
||||
name: docs-upstream
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'v[0-9]*'
|
||||
paths:
|
||||
- '.github/workflows/docs-upstream.yml'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/docs-upstream.yml'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
docs-yaml:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Upload reference YAML docs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-yaml
|
||||
path: docs/reference
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@919a9b9104a34a40b30d116529bcce589a544d1c # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
module-name: docker/compose
|
||||
151
.github/workflows/merge.yml
vendored
151
.github/workflows/merge.yml
vendored
@@ -1,151 +0,0 @@
|
||||
name: merge
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
REPO_SLUG: "docker/compose-bin"
|
||||
|
||||
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
|
||||
|
||||
bin-image:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
digest: ${{ fromJSON(steps.bake.outputs.metadata).image-cross['containerimage.digest'] }}
|
||||
steps:
|
||||
-
|
||||
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: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REPO_SLUG }}
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=edge
|
||||
bake-target: meta-helper
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v2
|
||||
id: bake
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
${{ steps.meta.outputs.bake-file }}
|
||||
targets: image-cross
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=bin-image
|
||||
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||
*.attest=type=sbom
|
||||
*.attest=type=provenance,mode=max,builder-id=https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}
|
||||
|
||||
desktop-edge-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: bin-image
|
||||
steps:
|
||||
-
|
||||
name: Generate Token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ vars.DOCKERDESKTOP_APP_ID }}
|
||||
private_key: ${{ secrets.DOCKERDESKTOP_APP_PRIVATEKEY }}
|
||||
repository: docker/${{ secrets.DOCKERDESKTOP_REPO }}
|
||||
-
|
||||
name: Trigger Docker Desktop e2e with edge version
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'docker',
|
||||
repo: '${{ secrets.DOCKERDESKTOP_REPO }}',
|
||||
workflow_id: 'compose-edge-integration.yml',
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
"image-tag": "${{ needs.bin-image.outputs.digest }}"
|
||||
}
|
||||
})
|
||||
11
.github/workflows/pr-closed.yml
vendored
Normal file
11
.github/workflows/pr-closed.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
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
Normal file
19
.github/workflows/rebase.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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@v2
|
||||
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 }}
|
||||
51
.github/workflows/release.yaml
vendored
Normal file
51
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Releaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release Tag"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.2
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- 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 }}
|
||||
54
.github/workflows/scorecards.yml
vendored
54
.github/workflows/scorecards.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Scorecards supply-chain security
|
||||
on:
|
||||
# Only the default branch is supported.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '44 9 * * 4'
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
# Used to receive a badge.
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.6
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
# Publish the results for public repositories to enable scorecard badges. For more details, see
|
||||
# https://github.com/ossf/scorecard-action#publishing-results.
|
||||
# For private repositories, `publish_results` will automatically be set to `false`, regardless
|
||||
# of the value entered here.
|
||||
publish_results: true
|
||||
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,3 @@
|
||||
bin/
|
||||
dist/
|
||||
/.vscode/
|
||||
coverage.out
|
||||
covdatafiles/
|
||||
.DS_Store
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
linters:
|
||||
run:
|
||||
concurrency: 2
|
||||
skip-dirs:
|
||||
- tests/composefiles
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
enable:
|
||||
- depguard
|
||||
- deadcode
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
@@ -20,49 +18,14 @@ linters:
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: 'io/ioutil package has been deprecated'
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: 'compose-go uses yaml.v3'
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: "use distribution/reference"
|
||||
- gotest.tools:
|
||||
version: "< 3.0.0"
|
||||
reason: "deprecated, pre-modules version"
|
||||
gocritic:
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
lll:
|
||||
@@ -71,3 +34,5 @@ issues:
|
||||
# golangci hides some golint warnings (the warning about exported things
|
||||
# withtout documentation for example), this will make it show them anyway.
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- should not use dot imports
|
||||
|
||||
49
BUILDING.md
49
BUILDING.md
@@ -19,8 +19,7 @@ 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/build`.
|
||||
This will output a `docker-compose` CLI plugin for your host machine in `./bin`.
|
||||
|
||||
You can statically cross compile the CLI for Windows, macOS, and Linux using the
|
||||
`cross` target.
|
||||
@@ -35,57 +34,21 @@ make test
|
||||
|
||||
If you need to update a golden file simply do `go test ./... -test.update-golden`.
|
||||
|
||||
### End-to-end tests
|
||||
To run e2e tests, the Compose CLI binary needs to be built. All the commands to run e2e tests propose a version
|
||||
with the prefix `build-and-e2e` to first build the CLI before executing tests.
|
||||
### End to end tests
|
||||
|
||||
Note that this requires a local Docker Engine to be running.
|
||||
|
||||
#### Whole end-to-end tests suite
|
||||
|
||||
To execute both CLI and standalone e2e tests, run :
|
||||
|
||||
```console
|
||||
make e2e
|
||||
```
|
||||
|
||||
Or if you need to build the CLI, run:
|
||||
```console
|
||||
make build-and-e2e
|
||||
```
|
||||
|
||||
#### Plugin end-to-end tests suite
|
||||
|
||||
To execute CLI plugin e2e tests, run :
|
||||
To run the end to end tests, run:
|
||||
|
||||
```console
|
||||
make e2e-compose
|
||||
```
|
||||
|
||||
Or if you need to build the CLI, run:
|
||||
```console
|
||||
make build-and-e2e-compose
|
||||
```
|
||||
|
||||
#### Standalone end-to-end tests suite
|
||||
|
||||
To execute the standalone CLI e2e tests, run :
|
||||
|
||||
```console
|
||||
make e2e-compose-standalone
|
||||
```
|
||||
|
||||
Or if you need to build the CLI, run:
|
||||
|
||||
```console
|
||||
make build-and-e2e-compose-standalone
|
||||
```
|
||||
Note that this requires a local Docker Engine to be running.
|
||||
|
||||
## Releases
|
||||
|
||||
To create a new release:
|
||||
* Check that the CI is green on the main branch for the commit you want to release
|
||||
* Run the release Github Actions workflow with a tag of form vx.y.z following existing tags.
|
||||
* Check that the CI is green on the main branch for commit you want to release
|
||||
* Run the release Github Actions workflow with a tag of the form vx.y.z following existing tags.
|
||||
|
||||
This will automatically create a new tag, release and make binaries for
|
||||
Windows, macOS, and Linux available for download on the
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
||||
[setting up a Docker development environment and the contribution
|
||||
process](https://docs.docker.com/contribute/overview/).
|
||||
process](https://docs.docker.com/opensource/project/who-written-for/).
|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
@@ -11,31 +11,23 @@ start participating.
|
||||
|
||||
## Topics
|
||||
|
||||
- [Contributing to Docker](#contributing-to-docker)
|
||||
- [Topics](#topics)
|
||||
- [Reporting security issues](#reporting-security-issues)
|
||||
- [Reporting other issues](#reporting-other-issues)
|
||||
- [Quick contribution tips and guidelines](#quick-contribution-tips-and-guidelines)
|
||||
- [Pull requests are always welcome](#pull-requests-are-always-welcome)
|
||||
- [Talking to other Docker users and contributors](#talking-to-other-docker-users-and-contributors)
|
||||
- [Conventions](#conventions)
|
||||
- [Merge approval](#merge-approval)
|
||||
- [Sign your work](#sign-your-work)
|
||||
- [How can I become a maintainer?](#how-can-i-become-a-maintainer)
|
||||
- [Docker community guidelines](#docker-community-guidelines)
|
||||
- [Coding Style](#coding-style)
|
||||
* [Reporting Security Issues](#reporting-security-issues)
|
||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||
* [Reporting Issues](#reporting-other-issues)
|
||||
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
|
||||
* [Community Guidelines](#docker-community-guidelines)
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
The Docker maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead, send your report privately to
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for them.
|
||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||
We also like to send gifts—if you're into Docker swag, make sure to let
|
||||
us know. We currently do not offer a paid security bounty program but are not
|
||||
us know. We currently do not offer a paid security bounty program, but are not
|
||||
ruling it out in the future.
|
||||
|
||||
|
||||
@@ -47,11 +39,11 @@ and will thank you for it!
|
||||
|
||||
Check that [our issue database](https://github.com/docker/compose/labels/Docker%20Compose%20V2)
|
||||
doesn't already include that problem or suggestion before submitting an issue.
|
||||
If you find a match, you can use the "subscribe" button to get notified of
|
||||
If you find a match, you can use the "subscribe" button to get notified on
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help to resolve it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolve the issue, please leave a comment.
|
||||
resolving the issue, please leave a comment.
|
||||
|
||||
When reporting issues, always include:
|
||||
|
||||
@@ -59,18 +51,13 @@ When reporting issues, always include:
|
||||
* The output of `docker context show`.
|
||||
* The output of `docker info`.
|
||||
|
||||
Also, include the steps required to reproduce the problem if possible and
|
||||
Also include the steps required to reproduce the problem if possible and
|
||||
applicable. This information will help us review and fix your issue faster.
|
||||
When sending lengthy log files, consider posting them as a gist
|
||||
(https://gist.github.com).
|
||||
Don't forget to remove sensitive data from your log files before posting (you
|
||||
can replace those parts with "REDACTED").
|
||||
|
||||
_Note:_
|
||||
Maintainers might request additional information to diagnose an issue,
|
||||
if initial reporter doesn't answer within a reasonable delay (a few weeks),
|
||||
issue will be closed.
|
||||
|
||||
## Quick contribution tips and guidelines
|
||||
|
||||
This section gives the experienced contributor some tips and guidelines.
|
||||
@@ -137,10 +124,9 @@ Fork the repository and make changes on your fork in a feature branch:
|
||||
issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Also, end-to-end tests are
|
||||
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.
|
||||
it! Take a look at existing tests for inspiration. [Run the full test
|
||||
suite](README.md) on your branch before
|
||||
submitting a pull request.
|
||||
|
||||
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
|
||||
@@ -158,7 +144,7 @@ suggested modifications and push additional commits to your feature branch. Post
|
||||
a comment after pushing. New commits show up in the pull request automatically,
|
||||
but the reviewers are notified only when you comment.
|
||||
|
||||
Pull requests must be cleanly rebased on top of the base branch without multiple branches
|
||||
Pull requests must be cleanly rebased on top of master without multiple branches
|
||||
mixed into the PR.
|
||||
|
||||
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
|
||||
@@ -178,7 +164,7 @@ changes in the same pull request so that a revert would remove all traces of
|
||||
the feature or fix.
|
||||
|
||||
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in the pull
|
||||
request description that closes an issue. Including references automatically
|
||||
request description that close an issue. Including references automatically
|
||||
closes the issue on a merge.
|
||||
|
||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||
@@ -269,7 +255,7 @@ your help to keep it that way. To help with this we've come up with some general
|
||||
guidelines for the community as a whole:
|
||||
|
||||
* Be nice: Be courteous, respectful and polite to fellow community members:
|
||||
no regional, racial, gender or other abuse will be tolerated. We like
|
||||
no regional, racial, gender, or other abuse will be tolerated. We like
|
||||
nice people way better than mean ones!
|
||||
|
||||
* Encourage diversity and participation: Make everyone in our community feel
|
||||
@@ -283,10 +269,10 @@ guidelines for the community as a whole:
|
||||
|
||||
* Stay on topic: Make sure that you are posting to the correct channel and
|
||||
avoid off-topic discussions. Remember when you update an issue or respond
|
||||
to an email you are potentially sending it to a large number of people. Please
|
||||
consider this before you update. Also, remember that nobody likes spam.
|
||||
to an email you are potentially sending to a large number of people. Please
|
||||
consider this before you update. Also remember that nobody likes spam.
|
||||
|
||||
* Don't send emails to the maintainers: There's no need to send emails to the
|
||||
* Don't send email to the maintainers: There's no need to send email to the
|
||||
maintainers to ask them to investigate an issue or to take a look at a
|
||||
pull request. Instead of sending an email, GitHub mentions should be
|
||||
used to ping maintainers to review a pull request, a proposal or an
|
||||
@@ -300,7 +286,7 @@ to result in a solid, consistent codebase.
|
||||
|
||||
It is possible that the code base does not currently comply with these
|
||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||
goes against the spirit of the guidelines. All new contributors should make their
|
||||
goes against the spirit of the guidelines. All new contributions should make a
|
||||
best effort to clean up and make the code base better than they left it.
|
||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||
code base easier for humans to navigate and understand. Always keep that in
|
||||
@@ -314,7 +300,7 @@ The rules:
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Include code comments. Tell us the why, the history and the context.
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
|
||||
243
Dockerfile
243
Dockerfile
@@ -1,4 +1,4 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1.2
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
@@ -15,180 +15,93 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.21.6
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.55.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
ARG GO_VERSION=1.18.2-alpine
|
||||
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
|
||||
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
||||
|
||||
ARG BUILD_TAGS="e2e"
|
||||
ARG DOCS_FORMATS="md,yaml"
|
||||
ARG LICENSE_FILES=".*\(Dockerfile\|Makefile\|\.go\|\.hcl\|\.sh\)"
|
||||
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=${BUILDPLATFORM} tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
# osxcross contains the MacOSX cross toolchain for xx
|
||||
FROM crazymax/osxcross:11.3-alpine AS osxcross
|
||||
|
||||
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 \
|
||||
clang \
|
||||
docker \
|
||||
file \
|
||||
findutils \
|
||||
git \
|
||||
make \
|
||||
protoc \
|
||||
protobuf-dev
|
||||
WORKDIR /src
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
FROM base AS build-base
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS base
|
||||
WORKDIR /compose-cli
|
||||
RUN apk add --no-cache -vv \
|
||||
git \
|
||||
docker \
|
||||
make \
|
||||
protoc \
|
||||
protobuf-dev
|
||||
COPY go.* .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
go mod download
|
||||
|
||||
FROM build-base AS vendored
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go mod tidy && mkdir /out && cp go.mod go.sum /out
|
||||
|
||||
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 build-base AS build
|
||||
FROM base AS lint
|
||||
ENV CGO_ENABLED=0
|
||||
COPY --from=golangci/golangci-lint /usr/bin/golangci-lint /usr/bin/golangci-lint
|
||||
ARG BUILD_TAGS
|
||||
ARG BUILD_FLAGS
|
||||
ARG TARGETPLATFORM
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \
|
||||
xx-go --wrap && \
|
||||
if [ "$(xx-info os)" == "darwin" ]; then export CGO_ENABLED=1; fi && \
|
||||
make build GO_BUILDTAGS="$BUILD_TAGS" DESTDIR=/out && \
|
||||
xx-verify --static /out/docker-compose
|
||||
|
||||
FROM build-base AS lint
|
||||
ARG BUILD_TAGS
|
||||
ENV GOLANGCI_LINT_CACHE=/cache/golangci-lint
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/cache/golangci-lint \
|
||||
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
golangci-lint cache status && \
|
||||
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 \
|
||||
rm -rf /tmp/coverage && \
|
||||
mkdir -p /tmp/coverage && \
|
||||
go test -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \
|
||||
go tool covdata percent -i=/tmp/coverage
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test --link /tmp/coverage /
|
||||
|
||||
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
|
||||
ARG GIT_TAG
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/root/.cache,type=cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||
--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
|
||||
|
||||
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 /out/docker-compose /
|
||||
FROM binary-unix AS binary-darwin
|
||||
FROM binary-unix AS binary-linux
|
||||
FROM scratch AS binary-windows
|
||||
COPY --link --from=build /out/docker-compose /docker-compose.exe
|
||||
FROM binary-$TARGETOS AS binary
|
||||
# enable scanning for this stage
|
||||
ARG BUILDKIT_SBOM_SCAN_STAGE=true
|
||||
|
||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||
WORKDIR /work
|
||||
FROM base AS make-compose-plugin
|
||||
ENV CGO_ENABLED=0
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
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//')"
|
||||
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
|
||||
|
||||
FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
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/kunalkushwaha/ltag@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
|
||||
|
||||
73
MAINTAINERS
73
MAINTAINERS
@@ -1,6 +1,6 @@
|
||||
# Docker maintainers file
|
||||
#
|
||||
# This file describes who runs the docker/compose project and how.
|
||||
# This file describes who runs the docker/compose-cli project and how.
|
||||
# This is a living document - if you see something out of date or missing, speak up!
|
||||
#
|
||||
# It is structured to be consumable by both humans and programs.
|
||||
@@ -22,26 +22,11 @@
|
||||
# subsystem maintainers accountable. If ownership is unclear, they are the de facto owners.
|
||||
|
||||
people = [
|
||||
"glours",
|
||||
"milas",
|
||||
"ndeloof",
|
||||
"nicksieger",
|
||||
"StefanScherer",
|
||||
"ulyssessouza"
|
||||
]
|
||||
|
||||
[Org."Regular maintainers"]
|
||||
# The Regular maintainers are people who aren't Core maintainers but are around
|
||||
# to help reviewing and fixing bugs, just on a less regular basis than previously.
|
||||
# Most of them were previously Core maintainers of Compose.
|
||||
people = [
|
||||
"aiordache",
|
||||
"chris-crone",
|
||||
"gtardif",
|
||||
"laurazard",
|
||||
"maxcleme",
|
||||
"rumpl",
|
||||
"thaJeztah"
|
||||
"gtardif",
|
||||
"ndeloof",
|
||||
"chris-crone",
|
||||
"ulyssessouza"
|
||||
]
|
||||
|
||||
[people]
|
||||
@@ -52,46 +37,16 @@
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aiordache]
|
||||
Name = "Anca Iordache"
|
||||
Email = "anca.iordache@docker.com"
|
||||
GitHub = "aiordache "
|
||||
|
||||
[people.chris-crone]
|
||||
Name = "Christopher Crone"
|
||||
Email = "christopher.crone@docker.com"
|
||||
GitHub = "chris-crone"
|
||||
|
||||
[people.glours]
|
||||
Name = "Guillaume Lours"
|
||||
Email = "guillaume.lours@docker.com"
|
||||
GitHub = "glours"
|
||||
|
||||
[people.gtardif]
|
||||
Name = "Guillaume Tardif"
|
||||
Email = "guillaume.tardif@docker.com"
|
||||
GitHub = "gtardif"
|
||||
|
||||
[people.laurazard]
|
||||
Name = "Laura Brehm"
|
||||
Email = "laura.brehm@docker.com"
|
||||
GitHub = "laurazard"
|
||||
|
||||
[people.maxcleme]
|
||||
Name = "Maxime Clement"
|
||||
Email = "maxime.clement@docker.com"
|
||||
GitHub = "maxcleme"
|
||||
|
||||
[people.milas]
|
||||
Name = "Milas Bowman"
|
||||
Email = "milas.bowman@docker.com"
|
||||
GitHub = "milas"
|
||||
|
||||
[people.nicksieger]
|
||||
Name = "Nick Sieger"
|
||||
Email = "nick.sieger@docker.com"
|
||||
GitHub = "nicksieger"
|
||||
|
||||
[people.ndeloof]
|
||||
Name = "Nicolas Deloof"
|
||||
Email = "nicolas.deloof@docker.com"
|
||||
@@ -102,17 +57,7 @@
|
||||
Email = "djordje.lukic@docker.com"
|
||||
GitHub = "rumpl"
|
||||
|
||||
[people.thaJeztah]
|
||||
Name = "Sebastiaan van Stijn"
|
||||
Email = "sebastiaan.vanstijn@docker.com"
|
||||
GitHub = "thaJeztah "
|
||||
|
||||
[people.StefanScherer]
|
||||
Name = "Stefan Scherer"
|
||||
Email = "stefan.scherer@docker.com"
|
||||
GitHub = "StefanScherer"
|
||||
|
||||
[people.ulyssessouza]
|
||||
Name = "Ulysses Souza"
|
||||
Email = "<ulysses.souza@docker.com"
|
||||
Github = "ulyssessouza"
|
||||
[people.ulyssessouza]
|
||||
Name = "Ulysses Souza"
|
||||
Email = "<ulysses.souza@docker.com"
|
||||
Github = "ulyssessouza"
|
||||
129
Makefile
129
Makefile
@@ -12,121 +12,94 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
PKG := github.com/docker/compose/v2
|
||||
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
GO_LDFLAGS ?= -w -X ${PKG}/internal.Version=${VERSION}
|
||||
GO_BUILDTAGS ?= e2e
|
||||
DRIVE_PREFIX?=
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DETECTED_OS = Windows
|
||||
DRIVE_PREFIX=C:
|
||||
else
|
||||
DETECTED_OS = $(shell uname -s)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
MOBY_DOCKER=/usr/bin/docker
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
MOBY_DOCKER=/Applications/Docker.app/Contents/Resources/bin/docker
|
||||
endif
|
||||
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
BINARY_EXT=.exe
|
||||
endif
|
||||
|
||||
BUILD_FLAGS?=
|
||||
BINARY_FOLDER=$(shell pwd)/bin
|
||||
GIT_TAG?=$(shell git describe --tags --match "v[0-9]*")
|
||||
TEST_FLAGS?=
|
||||
E2E_TEST?=
|
||||
ifneq ($(E2E_TEST),)
|
||||
TEST_FLAGS:=$(TEST_FLAGS) -run '$(E2E_TEST)'
|
||||
ifeq ($(E2E_TEST),)
|
||||
else
|
||||
TEST_FLAGS=-run $(E2E_TEST)
|
||||
endif
|
||||
|
||||
EXCLUDE_E2E_TESTS?=
|
||||
ifneq ($(EXCLUDE_E2E_TESTS),)
|
||||
TEST_FLAGS:=$(TEST_FLAGS) -skip '$(EXCLUDE_E2E_TESTS)'
|
||||
endif
|
||||
all: compose-plugin
|
||||
|
||||
BUILDX_CMD ?= docker buildx
|
||||
|
||||
# DESTDIR overrides the output path for binaries and other artifacts
|
||||
# this is used by docker/docker-ce-packaging for the apt/rpm builds,
|
||||
# so it's important that the resulting binary ends up EXACTLY at the
|
||||
# path $DESTDIR/docker-compose when specified.
|
||||
#
|
||||
# See https://github.com/docker/docker-ce-packaging/blob/e43fbd37e48fde49d907b9195f23b13537521b94/rpm/SPECS/docker-compose-plugin.spec#L47
|
||||
#
|
||||
# By default, all artifacts go to subdirectories under ./bin/ in the
|
||||
# repo root, e.g. ./bin/build, ./bin/coverage, ./bin/release.
|
||||
DESTDIR ?=
|
||||
|
||||
all: build
|
||||
|
||||
.PHONY: build ## Build the compose cli-plugin
|
||||
build:
|
||||
GO111MODULE=on go build $(BUILD_FLAGS) -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(or $(DESTDIR),./bin/build)/docker-compose$(BINARY_EXT)" ./cmd
|
||||
|
||||
.PHONY: binary
|
||||
binary:
|
||||
$(BUILDX_CMD) bake binary
|
||||
|
||||
.PHONY: binary-with-coverage
|
||||
binary-with-coverage:
|
||||
$(BUILDX_CMD) bake binary-with-coverage
|
||||
|
||||
.PHONY: install
|
||||
install: binary
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose
|
||||
.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
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go test -v $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
docker compose version
|
||||
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
docker-compose version
|
||||
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||
|
||||
.PHONY: build-and-e2e-compose
|
||||
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: 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:
|
||||
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.3.0
|
||||
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
|
||||
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
|
||||
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
|
||||
|
||||
.PHONY: e2e
|
||||
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: build-and-e2e
|
||||
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
|
||||
$(BUILDX_CMD) bake binary-cross
|
||||
@docker build . --target cross \
|
||||
--build-arg BUILD_TAGS \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--output ./bin \
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run unit tests
|
||||
$(BUILDX_CMD) bake test
|
||||
@docker build --progress=plain . \
|
||||
--build-arg BUILD_TAGS=kube \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--target test
|
||||
|
||||
.PHONY: cache-clear
|
||||
cache-clear: ## Clear the builder cache
|
||||
$(BUILDX_CMD) prune --force --filter type=exec.cachemount --filter=unused-for=24h
|
||||
@docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## run linter(s)
|
||||
$(BUILDX_CMD) bake lint
|
||||
@docker build . \
|
||||
--build-arg BUILD_TAGS=kube,e2e \
|
||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||
--target lint
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## generate documentation
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t compose-output.XXXXXXXXXX))
|
||||
$(BUILDX_CMD) bake --set "*.output=type=local,dest=$($@_TMP_OUT)" docs-update
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
|
||||
docker build . \
|
||||
--output type=local,dest=$($@_TMP_OUT) \
|
||||
-f ./docs/docs.Dockerfile \
|
||||
--target update
|
||||
rm -rf ./docs/internal
|
||||
cp -R "$(DRIVE_PREFIX)$($@_TMP_OUT)"/out/* ./docs/
|
||||
rm -rf "$(DRIVE_PREFIX)$($@_TMP_OUT)"/*
|
||||
cp -R "$($@_TMP_OUT)"/out/* ./docs/
|
||||
rm -rf "$($@_TMP_OUT)"/*
|
||||
|
||||
.PHONY: validate-docs
|
||||
validate-docs: ## validate the doc does not change
|
||||
$(BUILDX_CMD) bake docs-validate
|
||||
@docker build . \
|
||||
-f ./docs/docs.Dockerfile \
|
||||
--target validate
|
||||
|
||||
.PHONY: check-dependencies
|
||||
check-dependencies: ## check dependency updates
|
||||
@@ -134,19 +107,19 @@ check-dependencies: ## check dependency updates
|
||||
|
||||
.PHONY: validate-headers
|
||||
validate-headers: ## Check license header for all files
|
||||
$(BUILDX_CMD) bake license-validate
|
||||
@docker build . --target check-license-headers
|
||||
|
||||
.PHONY: go-mod-tidy
|
||||
go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and go.sum
|
||||
$(BUILDX_CMD) bake vendor-update
|
||||
@docker build . --target go-mod-tidy --output .
|
||||
|
||||
.PHONY: validate-go-mod
|
||||
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
@docker build . --target check-go-mod
|
||||
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
|
||||
pre-commit: validate check-dependencies lint build test e2e-compose
|
||||
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
|
||||
|
||||
help: ## Show help
|
||||
@echo Please specify a build target. The choices are:
|
||||
|
||||
39
README.md
39
README.md
@@ -1,28 +1,22 @@
|
||||
# Table of Contents
|
||||
- [Docker Compose v2](#docker-compose-v2)
|
||||
- [Where to get Docker Compose](#where-to-get-docker-compose)
|
||||
+ [Windows and macOS](#windows-and-macos)
|
||||
+ [Linux](#linux)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Contributing](#contributing)
|
||||
- [Legacy](#legacy)
|
||||
# Docker Compose v2
|
||||
|
||||
[](https://github.com/docker/compose/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/compose/v2)
|
||||
[](https://github.com/docker/compose/actions?query=workflow%3Aci)
|
||||
[](https://goreportcard.com/report/github.com/docker/compose/v2)
|
||||
[](https://codecov.io/gh/docker/compose)
|
||||
[](https://api.securityscorecards.dev/projects/github.com/docker/compose)
|
||||
[](https://github.com/docker/compose/actions)
|
||||
|
||||

|
||||
|
||||
Docker Compose is a tool for running multi-container applications on Docker
|
||||
defined using the [Compose file format](https://compose-spec.io).
|
||||
A Compose file is used to define how one or more containers that make up
|
||||
A Compose file is used to define how the one or more containers that make up
|
||||
your application are configured.
|
||||
Once you have a Compose file, you can create and start your application with a
|
||||
single command: `docker compose up`.
|
||||
|
||||
# About update and backward compatibility
|
||||
|
||||
Docker Compose V2 is a major version bump release of Docker Compose. It has been completely rewritten from scratch in Golang (V1 was in Python). The installation instructions for Compose V2 differ from V1. V2 is not a standalone binary anymore, and installation scripts will have to be adjusted. Some commands are different.
|
||||
|
||||
For a smooth transition from legacy docker-compose 1.xx, please consider installing [compose-switch](https://github.com/docker/compose-switch) to translate `docker-compose ...` commands into Compose V2's `docker compose .... `. Also check V2's `--compatibility` flag.
|
||||
|
||||
# Where to get Docker Compose
|
||||
|
||||
### Windows and macOS
|
||||
@@ -36,23 +30,23 @@ for Windows and macOS.
|
||||
You can download Docker Compose binaries from the
|
||||
[release page](https://github.com/docker/compose/releases) on this repository.
|
||||
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
Rename the relevant binary for your OS to `docker-compose` and copy it to `$HOME/.docker/cli-plugins`
|
||||
|
||||
Or copy it into one of these folders to install it system-wide:
|
||||
Or copy it into one of these folders for installing it system-wide:
|
||||
|
||||
* `/usr/local/lib/docker/cli-plugins` OR `/usr/local/libexec/docker/cli-plugins`
|
||||
* `/usr/lib/docker/cli-plugins` OR `/usr/libexec/docker/cli-plugins`
|
||||
|
||||
(might require making the downloaded file executable with `chmod +x`)
|
||||
(might require to make the downloaded file executable with `chmod +x`)
|
||||
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Using Docker Compose is a three-step process:
|
||||
Using Docker Compose is basically a three-step process:
|
||||
1. Define your app's environment with a `Dockerfile` so it can be
|
||||
reproduced anywhere.
|
||||
2. Define the services that make up your app in `compose.yaml` so
|
||||
2. Define the services that make up your app in `docker-compose.yml` so
|
||||
they can be run together in an isolated environment.
|
||||
3. Lastly, run `docker compose up` and Compose will start and run your entire
|
||||
app.
|
||||
@@ -79,8 +73,3 @@ Want to help develop Docker Compose? Check out our
|
||||
|
||||
If you find an issue, please report it on the
|
||||
[issue tracker](https://github.com/docker/compose/issues/new/choose).
|
||||
|
||||
Legacy
|
||||
-------------
|
||||
|
||||
The Python version of Compose is available under the `v1` [branch](https://github.com/docker/compose/tree/v1).
|
||||
|
||||
73
builder.Makefile
Normal file
73
builder.Makefile
Normal file
@@ -0,0 +1,73 @@
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
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
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 cmdtrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Setup should be called as part of the command's PersistentPreRunE
|
||||
// as soon as possible after initializing the dockerCli.
|
||||
//
|
||||
// It initializes the tracer for the CLI using both auto-detection
|
||||
// from the Docker context metadata as well as standard OTEL_ env
|
||||
// vars, creates a root span for the command, and wraps the actual
|
||||
// command invocation to ensure the span is properly finalized and
|
||||
// exported before exit.
|
||||
func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
|
||||
tracingShutdown, err := tracing.InitTracing(dockerCli)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing tracing: %w", err)
|
||||
}
|
||||
|
||||
ctx := cmd.Context()
|
||||
ctx, cmdSpan := tracing.Tracer.Start(
|
||||
ctx,
|
||||
"cli/"+strings.Join(commandName(cmd), "-"),
|
||||
)
|
||||
cmdSpan.SetAttributes(attribute.StringSlice("cli.args", args))
|
||||
cmdSpan.SetAttributes(attribute.StringSlice("cli.flags", getFlags(cmd.Flags())))
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
wrapRunE(cmd, cmdSpan, tracingShutdown)
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrapRunE injects a wrapper function around the command's actual RunE (or Run)
|
||||
// method. This is necessary to capture the command result for reporting as well
|
||||
// as flushing any spans before exit.
|
||||
//
|
||||
// Unfortunately, PersistentPostRun(E) can't be used for this purpose because it
|
||||
// only runs if RunE does _not_ return an error, but this should run unconditionally.
|
||||
func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.ShutdownFunc) {
|
||||
origRunE := c.RunE
|
||||
if origRunE == nil {
|
||||
origRun := c.Run
|
||||
//nolint:unparam // wrapper function for RunE, always returns nil by design
|
||||
origRunE = func(cmd *cobra.Command, args []string) error {
|
||||
origRun(cmd, args)
|
||||
return nil
|
||||
}
|
||||
c.Run = nil
|
||||
}
|
||||
|
||||
c.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
cmdErr := origRunE(cmd, args)
|
||||
if cmdSpan != nil {
|
||||
if cmdErr != nil && !errors.Is(cmdErr, context.Canceled) {
|
||||
// default exit code is 1 if a more descriptive error
|
||||
// wasn't returned
|
||||
exitCode := 1
|
||||
var statusErr dockercli.StatusError
|
||||
if errors.As(cmdErr, &statusErr) {
|
||||
exitCode = statusErr.StatusCode
|
||||
}
|
||||
cmdSpan.SetStatus(codes.Error, "CLI command returned error")
|
||||
cmdSpan.RecordError(cmdErr, trace.WithAttributes(
|
||||
attribute.Int("exit_code", exitCode),
|
||||
))
|
||||
|
||||
} else {
|
||||
cmdSpan.SetStatus(codes.Ok, "")
|
||||
}
|
||||
cmdSpan.End()
|
||||
}
|
||||
if tracingShutdown != nil {
|
||||
// use background for root context because the cmd's context might have
|
||||
// been canceled already
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
// TODO(milas): add an env var to enable logging from the
|
||||
// OTel components for debugging purposes
|
||||
_ = tracingShutdown(ctx)
|
||||
}
|
||||
return cmdErr
|
||||
}
|
||||
}
|
||||
|
||||
// commandName returns the path components for a given command.
|
||||
//
|
||||
// The root Compose command and anything before (i.e. "docker")
|
||||
// are not included.
|
||||
//
|
||||
// For example:
|
||||
// - docker compose alpha watch -> [alpha, watch]
|
||||
// - docker-compose up -> [up]
|
||||
func commandName(cmd *cobra.Command) []string {
|
||||
var name []string
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
if c.Name() == commands.PluginName {
|
||||
break
|
||||
}
|
||||
name = append(name, c.Name())
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(name)))
|
||||
return name
|
||||
}
|
||||
|
||||
func getFlags(fs *flag.FlagSet) []string {
|
||||
var result []string
|
||||
fs.Visit(func(flag *flag.Flag) {
|
||||
result = append(result, flag.Name)
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmdtrace
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func TestGetFlags(t *testing.T) {
|
||||
// Initialize flagSet with flags
|
||||
fs := flag.NewFlagSet("up", flag.ContinueOnError)
|
||||
var (
|
||||
detach string
|
||||
timeout string
|
||||
)
|
||||
fs.StringVar(&detach, "detach", "d", "")
|
||||
fs.StringVar(&timeout, "timeout", "t", "")
|
||||
_ = fs.Set("detach", "detach")
|
||||
_ = fs.Set("timeout", "timeout")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *flag.FlagSet
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "NoFlags",
|
||||
input: flag.NewFlagSet("NoFlags", flag.ContinueOnError),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "Flags",
|
||||
input: fs,
|
||||
expected: []string{"detach", "timeout"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := getFlags(test.input)
|
||||
if !reflect.DeepEqual(result, test.expected) {
|
||||
t.Errorf("Expected %v, but got %v", test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,13 +23,6 @@ import (
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
func getCompletionCommands() []string {
|
||||
return []string{
|
||||
"__complete",
|
||||
"__completeNoDesc",
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolFlags() []string {
|
||||
return []string{
|
||||
"--debug", "-D",
|
||||
@@ -57,26 +50,25 @@ 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] != '-' {
|
||||
if arg[0] != '-' {
|
||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||
if arg == compose.PluginName {
|
||||
i++
|
||||
}
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
|
||||
switch arg {
|
||||
case "--verbose":
|
||||
if arg == "--verbose" {
|
||||
arg = "--debug"
|
||||
case "-h":
|
||||
}
|
||||
if arg == "-h" {
|
||||
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
||||
arg = "--help"
|
||||
case "--version", "-v":
|
||||
}
|
||||
if arg == "--version" || arg == "-v" {
|
||||
// redirect --version pseudo-command to actual command
|
||||
arg = "version"
|
||||
}
|
||||
|
||||
if contains(getBoolFlags(), arg) {
|
||||
rootFlags = append(rootFlags, arg)
|
||||
continue
|
||||
|
||||
@@ -43,21 +43,11 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "compose --verbose",
|
||||
args: []string{"--verbose"},
|
||||
want: []string{"--debug", "compose"},
|
||||
},
|
||||
{
|
||||
name: "compose --version",
|
||||
args: []string{"--version"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "compose -v",
|
||||
args: []string{"-v"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
args: []string{"-h"},
|
||||
@@ -78,16 +68,6 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--log-level", "INFO", "up"},
|
||||
want: []string{"--log-level", "INFO", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "empty string argument",
|
||||
args: []string{"--project-directory", "", "ps"},
|
||||
want: []string{"compose", "--project-directory", "", "ps"},
|
||||
},
|
||||
{
|
||||
name: "compose as project name",
|
||||
args: []string{"--project-name", "compose", "down", "--remove-orphans"},
|
||||
want: []string{"compose", "--project-name", "compose", "down", "--remove-orphans"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// alphaCommand groups all experimental subcommands
|
||||
func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Short: "Experimental commands",
|
||||
Use: "alpha [COMMAND]",
|
||||
Hidden: true,
|
||||
Annotations: map[string]string{
|
||||
"experimentalCLI": "true",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
vizCommand(p, dockerCli, backend),
|
||||
publishCommand(p, dockerCli, backend),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type attachOpts struct {
|
||||
*composeOptions
|
||||
|
||||
service string
|
||||
index int
|
||||
|
||||
detachKeys string
|
||||
noStdin bool
|
||||
proxy bool
|
||||
}
|
||||
|
||||
func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := attachOpts{
|
||||
composeOptions: &composeOptions{
|
||||
ProjectOptions: p,
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "attach [OPTIONS] SERVICE",
|
||||
Short: "Attach local standard input, output, and error streams to a service's running container.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.service = args[0]
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runAttach(ctx, dockerCli, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
runCmd.Flags().StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching from a container.")
|
||||
|
||||
runCmd.Flags().BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
|
||||
runCmd.Flags().BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
||||
return runCmd
|
||||
}
|
||||
|
||||
func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachOpts := api.AttachOptions{
|
||||
Service: opts.service,
|
||||
Index: opts.index,
|
||||
DetachKeys: opts.detachKeys,
|
||||
NoStdin: opts.noStdin,
|
||||
Proxy: opts.proxy,
|
||||
}
|
||||
return backend.Attach(ctx, projectName, attachOpts)
|
||||
}
|
||||
@@ -22,134 +22,117 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type buildOptions struct {
|
||||
*ProjectOptions
|
||||
quiet bool
|
||||
pull bool
|
||||
push bool
|
||||
args []string
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
*projectOptions
|
||||
composeOptions
|
||||
quiet bool
|
||||
pull bool
|
||||
progress string
|
||||
args []string
|
||||
noCache bool
|
||||
memory string
|
||||
ssh string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
var SSHKeys []types.SSHKey
|
||||
var err error
|
||||
if opts.ssh != "" {
|
||||
id, path, found := strings.Cut(opts.ssh, "=")
|
||||
if !found && id != "default" {
|
||||
return api.BuildOptions{}, fmt.Errorf("invalid ssh key %q", opts.ssh)
|
||||
}
|
||||
SSHKeys = append(SSHKeys, types.SSHKey{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
|
||||
if err != nil {
|
||||
return api.BuildOptions{}, err
|
||||
}
|
||||
}
|
||||
builderName := opts.builder
|
||||
if builderName == "" {
|
||||
builderName = os.Getenv("BUILDX_BUILDER")
|
||||
}
|
||||
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: ui.Mode,
|
||||
Progress: opts.progress,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
var printerModes = []string{
|
||||
buildx.PrinterModeAuto,
|
||||
buildx.PrinterModeTty,
|
||||
buildx.PrinterModePlain,
|
||||
buildx.PrinterModeQuiet,
|
||||
}
|
||||
|
||||
func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := buildOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [OPTIONS] [SERVICE...]",
|
||||
Use: "build [SERVICE...]",
|
||||
Short: "Build or rebuild services",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.memory != "" {
|
||||
fmt.Println("WARNING --memory is ignored as not supported in buildkit.")
|
||||
}
|
||||
if opts.quiet {
|
||||
ui.Mode = ui.ModeQuiet
|
||||
opts.progress = buildx.PrinterModeQuiet
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if !utils.StringContains(printerModes, opts.progress) {
|
||||
return fmt.Errorf("unsupported --progress value %q", opts.progress)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
|
||||
opts.ssh = "default"
|
||||
}
|
||||
if cmd.Flags().Changed("progress") && opts.ssh == "" {
|
||||
fmt.Fprint(os.Stderr, "--progress is a global compose flag, better use `docker compose --progress xx build ...\n")
|
||||
}
|
||||
return runBuild(ctx, dockerCli, backend, opts, args)
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.push, "push", false, "Push service images.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
flags.StringVar(&opts.builder, "builder", "", "Set builder to use.")
|
||||
flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively).")
|
||||
|
||||
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
flags.Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
flags.MarkHidden("compress") //nolint:errcheck
|
||||
flags.Bool("force-rm", true, "Always remove intermediate containers. DEPRECATED")
|
||||
flags.MarkHidden("force-rm") //nolint:errcheck
|
||||
flags.BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
|
||||
flags.Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
flags.MarkHidden("no-rm") //nolint:errcheck
|
||||
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("compress") //nolint:errcheck
|
||||
cmd.Flags().Bool("force-rm", true, "Always remove intermediate containers. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("force-rm") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
|
||||
cmd.Flags().Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("no-rm") //nolint:errcheck
|
||||
cmd.Flags().StringVarP(&opts.memory, "memory", "m", "", "Set memory limit for the build container. Not supported on buildkit yet.")
|
||||
cmd.Flags().MarkHidden("memory") //nolint:errcheck
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
func runBuild(ctx context.Context, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, err := opts.toProject(services, cli.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyPlatforms(project, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions.Memory = int64(opts.memory)
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,8 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -30,19 +27,18 @@ 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 []string{}, cobra.ShellCompDirectiveNoSpace
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
|
||||
func serviceCompletion(p *projectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
p.Offline = true
|
||||
project, err := p.ToProject(dockerCli, nil)
|
||||
project, err := p.toProject(nil)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
serviceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
|
||||
for _, s := range serviceNames {
|
||||
var serviceNames []string
|
||||
for _, s := range project.ServiceNames() {
|
||||
if toComplete == "" || strings.HasPrefix(s, toComplete) {
|
||||
serviceNames = append(serviceNames, s)
|
||||
}
|
||||
@@ -50,42 +46,3 @@ func completeServiceNames(dockerCli command.Cli, 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
|
||||
}
|
||||
}
|
||||
|
||||
func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
p.Offline = true
|
||||
project, err := p.ToProject(dockerCli, nil)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
allProfileNames := project.AllServices().GetProfiles()
|
||||
sort.Strings(allProfileNames)
|
||||
|
||||
var values []string
|
||||
for _, profileName := range allProfileNames {
|
||||
if strings.HasPrefix(profileName, toComplete) {
|
||||
values = append(values, profileName)
|
||||
}
|
||||
}
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,26 +18,20 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/v2/utils"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/remote"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -45,25 +39,10 @@ import (
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// ComposeParallelLimit set the limit running concurrent operation on docker engine
|
||||
ComposeParallelLimit = "COMPOSE_PARALLEL_LIMIT"
|
||||
// ComposeProjectName define the project name to be used, instead of guessing from parent directory
|
||||
ComposeProjectName = "COMPOSE_PROJECT_NAME"
|
||||
// ComposeCompatibility try to mimic compose v1 as much as possible
|
||||
ComposeCompatibility = "COMPOSE_COMPATIBILITY"
|
||||
// ComposeRemoveOrphans remove “orphaned" containers, i.e. containers tagged for current project but not declared as service
|
||||
ComposeRemoveOrphans = "COMPOSE_REMOVE_ORPHANS"
|
||||
// ComposeIgnoreOrphans ignore "orphaned" containers
|
||||
ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
|
||||
// ComposeEnvFiles defines the env files to use if --env-file isn't used
|
||||
ComposeEnvFiles = "COMPOSE_ENV_FILES"
|
||||
)
|
||||
|
||||
// Command defines a compose CLI command as a func with args
|
||||
type Command func(context.Context, []string) error
|
||||
|
||||
@@ -73,17 +52,18 @@ type CobraCommand func(context.Context, *cobra.Command, []string) error
|
||||
// AdaptCmd adapt a CobraCommand func to cobra library
|
||||
func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
<-s
|
||||
cancel()
|
||||
signal.Stop(s)
|
||||
close(s)
|
||||
}()
|
||||
|
||||
ctx := cmd.Context()
|
||||
contextString := fmt.Sprintf("%s", ctx)
|
||||
if !strings.HasSuffix(contextString, ".WithCancel") { // need to handle cancel
|
||||
cancellableCtx, cancel := context.WithCancel(cmd.Context())
|
||||
ctx = cancellableCtx
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
<-s
|
||||
cancel()
|
||||
}()
|
||||
}
|
||||
err := fn(ctx, cmd, args)
|
||||
var composeErr compose.Error
|
||||
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
|
||||
@@ -109,16 +89,14 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
type ProjectOptions struct {
|
||||
type projectOptions struct {
|
||||
ProjectName string
|
||||
Profiles []string
|
||||
ConfigPaths []string
|
||||
WorkDir string
|
||||
ProjectDir string
|
||||
EnvFiles []string
|
||||
EnvFile string
|
||||
Compatibility bool
|
||||
Progress string
|
||||
Offline bool
|
||||
}
|
||||
|
||||
// ProjectFunc does stuff within a types.Project
|
||||
@@ -128,22 +106,16 @@ type ProjectFunc func(ctx context.Context, project *types.Project) error
|
||||
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
|
||||
|
||||
// WithProject creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
func (o *ProjectOptions) WithProject(fn ProjectFunc, dockerCli command.Cli) func(cmd *cobra.Command, args []string) error {
|
||||
return o.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
|
||||
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
|
||||
return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
return fn(ctx, project)
|
||||
})
|
||||
}
|
||||
|
||||
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
|
||||
func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
|
||||
func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
|
||||
return Adapt(func(ctx context.Context, args []string) error {
|
||||
options := []cli.ProjectOptionsFn{
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithContext(ctx),
|
||||
}
|
||||
|
||||
project, err := o.ToProject(dockerCli, args, options...)
|
||||
project, err := o.toProject(args, cli.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -152,120 +124,100 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
})
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
|
||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||
f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file.")
|
||||
f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
|
||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
f.StringVar(&o.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string) (*types.Project, string, error) {
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
|
||||
p, err := o.ToProject(dockerCli, services, cli.WithDiscardEnvFile)
|
||||
if err != nil {
|
||||
envProjectName := os.Getenv(ComposeProjectName)
|
||||
if envProjectName != "" {
|
||||
return nil, envProjectName, nil
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
project = p
|
||||
name = p.Name
|
||||
}
|
||||
return project, name, nil
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
|
||||
func (o *projectOptions) toProjectName() (string, error) {
|
||||
if o.ProjectName != "" {
|
||||
return o.ProjectName, nil
|
||||
}
|
||||
|
||||
envProjectName := os.Getenv(ComposeProjectName)
|
||||
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||
if envProjectName != "" {
|
||||
return envProjectName, nil
|
||||
}
|
||||
|
||||
project, err := o.ToProject(dockerCli, nil)
|
||||
project, err := o.toProject(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return project.Name, nil
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
if !o.Offline {
|
||||
po = o.configureRemoteLoaders(dockerCli, po)
|
||||
}
|
||||
|
||||
func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
options, err := o.toProjectOptions(po...)
|
||||
if err != nil {
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
|
||||
api.Separator = "_"
|
||||
}
|
||||
|
||||
project, err := cli.ProjectFromOptions(options)
|
||||
if err != nil {
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if project.Name == "" {
|
||||
return nil, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
|
||||
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
|
||||
project, err = project.WithServicesEnabled(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
ef := o.EnvFile
|
||||
if ef != "" && !filepath.IsAbs(ef) {
|
||||
ef, err = filepath.Abs(ef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for name, s := range project.Services {
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
api.ServiceLabel: name,
|
||||
api.ServiceLabel: s.Name,
|
||||
api.VersionLabel: api.ComposeVersion,
|
||||
api.WorkingDirLabel: project.WorkingDir,
|
||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||
api.OneoffLabel: "False", // default, will be overridden by `run` command
|
||||
}
|
||||
if len(o.EnvFiles) != 0 {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
|
||||
if ef != "" {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = ef
|
||||
}
|
||||
project.Services[name] = s
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
project = project.WithoutUnnecessaryResources()
|
||||
if len(services) > 0 {
|
||||
s, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Profiles = append(o.Profiles, s.GetProfiles()...)
|
||||
}
|
||||
|
||||
project, err = project.WithSelectedServices(services)
|
||||
if profiles, ok := options.Environment["COMPOSE_PROFILES"]; ok {
|
||||
o.Profiles = append(o.Profiles, strings.Split(profiles, ",")...)
|
||||
}
|
||||
|
||||
project.ApplyProfiles(o.Profiles)
|
||||
|
||||
project.WithoutUnnecessaryResources()
|
||||
|
||||
err = project.ForServices(services)
|
||||
return project, err
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) configureRemoteLoaders(dockerCli command.Cli, po []cli.ProjectOptionsFn) []cli.ProjectOptionsFn {
|
||||
git := remote.NewGitRemoteLoader(o.Offline)
|
||||
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
|
||||
|
||||
po = append(po, cli.WithResourceLoader(git), cli.WithResourceLoader(oci))
|
||||
return po
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
cli.WithOsEnv,
|
||||
cli.WithEnvFiles(o.EnvFiles...),
|
||||
cli.WithEnvFile(o.EnvFile),
|
||||
cli.WithDotEnv,
|
||||
cli.WithConfigFileEnv,
|
||||
cli.WithDefaultConfigPath,
|
||||
cli.WithDefaultProfiles(o.Profiles...),
|
||||
cli.WithName(o.ProjectName))...)
|
||||
}
|
||||
|
||||
@@ -278,38 +230,25 @@ func RunningAsStandalone() bool {
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
|
||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
|
||||
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||
logrus.WarnLevel,
|
||||
},
|
||||
"commandConn.CloseWrite:",
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
|
||||
opts := ProjectOptions{}
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := projectOptions{}
|
||||
var (
|
||||
ansi string
|
||||
noAnsi bool
|
||||
verbose bool
|
||||
version bool
|
||||
parallel int
|
||||
dryRun bool
|
||||
ansi string
|
||||
noAnsi bool
|
||||
verbose bool
|
||||
version bool
|
||||
)
|
||||
c := &cobra.Command{
|
||||
command := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
Long: "Define and run multi-container applications with Docker.",
|
||||
Use: PluginName,
|
||||
TraverseChildren: true,
|
||||
// By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
|
||||
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return cmd.Help()
|
||||
}
|
||||
if version {
|
||||
return versionCommand(dockerCli).Execute()
|
||||
return versionCommand().Execute()
|
||||
}
|
||||
_ = cmd.Help()
|
||||
return dockercli.StatusError{
|
||||
@@ -318,10 +257,6 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
}
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := setEnvWithDotEnv(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent := cmd.Root()
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
@@ -337,49 +272,18 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
|
||||
}
|
||||
ansi = "never"
|
||||
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
|
||||
}
|
||||
if verbose {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") {
|
||||
ansi = v
|
||||
}
|
||||
|
||||
formatter.SetANSIMode(dockerCli, ansi)
|
||||
|
||||
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
|
||||
ui.NoColor()
|
||||
formatter.SetANSIMode(dockerCli, formatter.Never)
|
||||
}
|
||||
|
||||
formatter.SetANSIMode(ansi)
|
||||
switch ansi {
|
||||
case "never":
|
||||
ui.Mode = ui.ModePlain
|
||||
case "always":
|
||||
ui.Mode = ui.ModeTTY
|
||||
progress.Mode = progress.ModePlain
|
||||
case "tty":
|
||||
progress.Mode = progress.ModeTTY
|
||||
}
|
||||
|
||||
switch opts.Progress {
|
||||
case ui.ModeAuto:
|
||||
ui.Mode = ui.ModeAuto
|
||||
case ui.ModeTTY:
|
||||
if ansi == "never" {
|
||||
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
|
||||
}
|
||||
ui.Mode = ui.ModeTTY
|
||||
case ui.ModePlain:
|
||||
if ansi == "always" {
|
||||
return fmt.Errorf("can't use --progress plain while ANSI support is forced")
|
||||
}
|
||||
ui.Mode = ui.ModePlain
|
||||
case ui.ModeQuiet, "none":
|
||||
ui.Mode = ui.ModeQuiet
|
||||
default:
|
||||
return fmt.Errorf("unsupported --progress value %q", opts.Progress)
|
||||
}
|
||||
|
||||
if opts.WorkDir != "" {
|
||||
if opts.ProjectDir != "" {
|
||||
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
|
||||
@@ -387,143 +291,45 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
opts.ProjectDir = opts.WorkDir
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--workdir' is DEPRECATED at root level! Please use '--project-directory' instead.\n", aec.RedF))
|
||||
}
|
||||
for i, file := range opts.EnvFiles {
|
||||
if !filepath.IsAbs(file) {
|
||||
file, err = filepath.Abs(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.EnvFiles[i] = file
|
||||
}
|
||||
}
|
||||
|
||||
composeCmd := cmd
|
||||
for {
|
||||
if composeCmd.Name() == PluginName {
|
||||
break
|
||||
}
|
||||
if !composeCmd.HasParent() {
|
||||
return fmt.Errorf("error parsing command line, expected %q", PluginName)
|
||||
}
|
||||
composeCmd = composeCmd.Parent()
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv(ComposeParallelLimit); ok && !composeCmd.Flags().Changed("parallel") {
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s must be an integer (found: %q)", ComposeParallelLimit, v)
|
||||
}
|
||||
parallel = i
|
||||
}
|
||||
if parallel > 0 {
|
||||
backend.MaxConcurrency(parallel)
|
||||
}
|
||||
ctx, err := backend.DryRunMode(cmd.Context(), dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.SetContext(ctx)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
upCommand(&opts, dockerCli, backend),
|
||||
downCommand(&opts, dockerCli, backend),
|
||||
startCommand(&opts, dockerCli, backend),
|
||||
restartCommand(&opts, dockerCli, backend),
|
||||
stopCommand(&opts, dockerCli, backend),
|
||||
psCommand(&opts, dockerCli, backend),
|
||||
listCommand(dockerCli, backend),
|
||||
logsCommand(&opts, dockerCli, backend),
|
||||
configCommand(&opts, dockerCli, backend),
|
||||
killCommand(&opts, dockerCli, backend),
|
||||
command.AddCommand(
|
||||
upCommand(&opts, backend),
|
||||
downCommand(&opts, backend),
|
||||
startCommand(&opts, backend),
|
||||
restartCommand(&opts, backend),
|
||||
stopCommand(&opts, backend),
|
||||
psCommand(&opts, backend),
|
||||
listCommand(backend),
|
||||
logsCommand(&opts, backend),
|
||||
convertCommand(&opts, backend),
|
||||
killCommand(&opts, backend),
|
||||
runCommand(&opts, dockerCli, backend),
|
||||
removeCommand(&opts, dockerCli, backend),
|
||||
removeCommand(&opts, backend),
|
||||
execCommand(&opts, dockerCli, backend),
|
||||
attachCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, dockerCli, backend),
|
||||
unpauseCommand(&opts, dockerCli, backend),
|
||||
topCommand(&opts, dockerCli, backend),
|
||||
eventsCommand(&opts, dockerCli, backend),
|
||||
portCommand(&opts, dockerCli, backend),
|
||||
imagesCommand(&opts, dockerCli, backend),
|
||||
versionCommand(dockerCli),
|
||||
buildCommand(&opts, dockerCli, backend),
|
||||
pushCommand(&opts, dockerCli, backend),
|
||||
pullCommand(&opts, dockerCli, backend),
|
||||
createCommand(&opts, dockerCli, backend),
|
||||
copyCommand(&opts, dockerCli, backend),
|
||||
waitCommand(&opts, dockerCli, backend),
|
||||
scaleCommand(&opts, dockerCli, backend),
|
||||
statsCommand(&opts, dockerCli),
|
||||
watchCommand(&opts, dockerCli, backend),
|
||||
alphaCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, backend),
|
||||
unpauseCommand(&opts, backend),
|
||||
topCommand(&opts, backend),
|
||||
eventsCommand(&opts, backend),
|
||||
portCommand(&opts, backend),
|
||||
imagesCommand(&opts, backend),
|
||||
versionCommand(),
|
||||
buildCommand(&opts, backend),
|
||||
pushCommand(&opts, backend),
|
||||
pullCommand(&opts, backend),
|
||||
createCommand(&opts, backend),
|
||||
copyCommand(&opts, backend),
|
||||
)
|
||||
|
||||
c.Flags().SetInterspersed(false)
|
||||
opts.addProjectFlags(c.Flags())
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"project-name",
|
||||
completeProjectNames(backend),
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"project-directory",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{}, cobra.ShellCompDirectiveFilterDirs
|
||||
},
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"file",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
|
||||
},
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"profile",
|
||||
completeProfileNames(dockerCli, &opts),
|
||||
)
|
||||
|
||||
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
c.Flags().IntVar(¶llel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
|
||||
c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
c.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Execute command in dry run mode")
|
||||
c.Flags().MarkHidden("version") //nolint:errcheck
|
||||
c.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
|
||||
c.Flags().MarkHidden("no-ansi") //nolint:errcheck
|
||||
c.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
|
||||
c.Flags().MarkHidden("verbose") //nolint:errcheck
|
||||
return c
|
||||
}
|
||||
|
||||
func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
|
||||
if len(prjOpts.EnvFiles) == 0 {
|
||||
if envFiles := os.Getenv(ComposeEnvFiles); envFiles != "" {
|
||||
prjOpts.EnvFiles = strings.Split(envFiles, ",")
|
||||
}
|
||||
}
|
||||
options, err := prjOpts.toProjectOptions()
|
||||
if err != nil {
|
||||
return compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range envFromFile {
|
||||
if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var printerModes = []string{
|
||||
ui.ModeAuto,
|
||||
ui.ModeTTY,
|
||||
ui.ModePlain,
|
||||
ui.ModeQuiet,
|
||||
command.Flags().SetInterspersed(false)
|
||||
opts.addProjectFlags(command.Flags())
|
||||
command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
command.Flags().MarkHidden("version") //nolint:errcheck
|
||||
command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
|
||||
command.Flags().MarkHidden("no-ansi") //nolint:errcheck
|
||||
command.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
|
||||
command.Flags().MarkHidden("verbose") //nolint:errcheck
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -19,32 +19,30 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestFilterServices(t *testing.T) {
|
||||
p := &types.Project{
|
||||
Services: types.Services{
|
||||
"foo": {
|
||||
Services: []types.ServiceConfig{
|
||||
{
|
||||
Name: "foo",
|
||||
Links: []string{"bar"},
|
||||
},
|
||||
"bar": {
|
||||
Name: "bar",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"zot": {},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
NetworkMode: types.NetworkModeServicePrefix + "zot",
|
||||
},
|
||||
"zot": {
|
||||
{
|
||||
Name: "zot",
|
||||
},
|
||||
"qix": {
|
||||
{
|
||||
Name: "qix",
|
||||
},
|
||||
},
|
||||
}
|
||||
p, err := p.WithSelectedServices([]string{"bar"})
|
||||
err := p.ForServices([]string{"bar"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(p.Services), 2)
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
type configOptions struct {
|
||||
*ProjectOptions
|
||||
Format string
|
||||
Output string
|
||||
quiet bool
|
||||
resolveImageDigests bool
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
noResolvePath bool
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
noConsistency bool
|
||||
}
|
||||
|
||||
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
po = append(po,
|
||||
cli.WithInterpolation(!o.noInterpolate),
|
||||
cli.WithResolvedPaths(!o.noResolvePath),
|
||||
cli.WithNormalization(!o.noNormalize),
|
||||
cli.WithConsistency(!o.noConsistency),
|
||||
cli.WithDefaultProfiles(o.Profiles...),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithContext(ctx))
|
||||
return o.ProjectOptions.ToProject(dockerCli, services, po...)
|
||||
}
|
||||
|
||||
func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := configOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"convert"}, // for backward compatibility with Cloud integrations
|
||||
Use: "config [OPTIONS] [SERVICE...]",
|
||||
Short: "Parse, resolve and render compose file in canonical format",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.quiet {
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.services {
|
||||
return runServices(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.volumes {
|
||||
return runVolumes(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.hash != "" {
|
||||
return runHash(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.profiles {
|
||||
return runProfiles(ctx, dockerCli, opts, args)
|
||||
}
|
||||
if opts.images {
|
||||
return runConfigImages(ctx, dockerCli, opts, args)
|
||||
}
|
||||
|
||||
return runConfig(ctx, dockerCli, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths.")
|
||||
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error {
|
||||
var content []byte
|
||||
project, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err = backend.Config(ctx, project, api.ConfigOptions{
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.noInterpolate {
|
||||
content = escapeDollarSign(content)
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.Output != "" && len(content) > 0 {
|
||||
return os.WriteFile(opts.Output, content, 0o666)
|
||||
}
|
||||
_, err = fmt.Fprint(dockerCli.Out(), string(content))
|
||||
return err
|
||||
}
|
||||
|
||||
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = project.ForEachService(project.ServiceNames(), func(serviceName string, _ *types.ServiceConfig) error {
|
||||
fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for n := range project.Volumes {
|
||||
fmt.Fprintln(dockerCli.Out(), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
var services []string
|
||||
if opts.hash != "*" {
|
||||
services = append(services, strings.Split(opts.hash, ",")...)
|
||||
}
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyPlatforms(project, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
services = project.ServiceNames()
|
||||
}
|
||||
|
||||
sorted := services
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i] < sorted[j]
|
||||
})
|
||||
|
||||
for _, name := range sorted {
|
||||
s, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash, err := compose.ServiceHash(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "%s %s\n", name, hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
|
||||
set := map[string]struct{}{}
|
||||
project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.AllServices() {
|
||||
for _, p := range s.Profiles {
|
||||
set[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
profiles := make([]string, 0, len(set))
|
||||
for p := range set {
|
||||
profiles = append(profiles, p)
|
||||
}
|
||||
sort.Strings(profiles)
|
||||
for _, p := range profiles {
|
||||
fmt.Fprintln(dockerCli.Out(), p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func escapeDollarSign(marshal []byte) []byte {
|
||||
dollar := []byte{'$'}
|
||||
escDollar := []byte{'$', '$'}
|
||||
return bytes.ReplaceAll(marshal, dollar, escDollar)
|
||||
}
|
||||
239
cmd/compose/convert.go
Normal file
239
cmd/compose/convert.go
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cnabio/cnab-to-oci/remotes"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
type convertOptions struct {
|
||||
*projectOptions
|
||||
Format string
|
||||
Output string
|
||||
quiet bool
|
||||
resolveImageDigests bool
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
}
|
||||
|
||||
func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := convertOptions{
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"config"},
|
||||
Use: "convert SERVICES",
|
||||
Short: "Converts the compose file to platform's canonical format",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.quiet {
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.services {
|
||||
return runServices(opts)
|
||||
}
|
||||
if opts.volumes {
|
||||
return runVolumes(opts)
|
||||
}
|
||||
if opts.hash != "" {
|
||||
return runHash(opts)
|
||||
}
|
||||
if opts.profiles {
|
||||
return runProfiles(opts, args)
|
||||
}
|
||||
if opts.images {
|
||||
return runConfigImages(opts, args)
|
||||
}
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
|
||||
var json []byte
|
||||
project, err := opts.toProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithNormalization(!opts.noNormalize),
|
||||
cli.WithDiscardEnvFile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.resolveImageDigests {
|
||||
configFile := cliconfig.LoadDefaultConfigFile(os.Stderr)
|
||||
|
||||
resolver := remotes.CreateResolver(configFile)
|
||||
err = project.ResolveImages(func(named reference.Named) (digest.Digest, error) {
|
||||
_, desc, err := resolver.Resolve(ctx, named.String())
|
||||
return desc.Digest, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
json, err = backend.Convert(ctx, project, api.ConvertOptions{
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out io.Writer = os.Stdout
|
||||
if opts.Output != "" && len(json) > 0 {
|
||||
file, err := os.Create(opts.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = bufio.NewWriter(file)
|
||||
}
|
||||
_, err = fmt.Fprint(out, string(json))
|
||||
return err
|
||||
}
|
||||
|
||||
func runServices(opts convertOptions) error {
|
||||
project, err := opts.toProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return project.WithServices(project.ServiceNames(), func(s types.ServiceConfig) error {
|
||||
fmt.Println(s.Name)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runVolumes(opts convertOptions) error {
|
||||
project, err := opts.toProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for n := range project.Volumes {
|
||||
fmt.Println(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHash(opts convertOptions) error {
|
||||
var services []string
|
||||
if opts.hash != "*" {
|
||||
services = append(services, strings.Split(opts.hash, ",")...)
|
||||
}
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
hash, err := compose.ServiceHash(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s %s\n", s.Name, hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runProfiles(opts convertOptions, services []string) error {
|
||||
set := map[string]struct{}{}
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.AllServices() {
|
||||
for _, p := range s.Profiles {
|
||||
set[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
profiles := make([]string, 0, len(set))
|
||||
for p := range set {
|
||||
profiles = append(profiles, p)
|
||||
}
|
||||
sort.Strings(profiles)
|
||||
for _, p := range profiles {
|
||||
fmt.Println(p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigImages(opts convertOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if s.Image != "" {
|
||||
fmt.Println(s.Image)
|
||||
} else {
|
||||
fmt.Printf("%s_%s\n", project.Name, s.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -21,14 +21,13 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type copyOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
|
||||
source string
|
||||
destination string
|
||||
@@ -38,9 +37,9 @@ type copyOptions struct {
|
||||
copyUIDGID bool
|
||||
}
|
||||
|
||||
func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := copyOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
copyCmd := &cobra.Command{
|
||||
Use: `cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-
|
||||
@@ -59,24 +58,24 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.source = args[0]
|
||||
opts.destination = args[1]
|
||||
return runCopy(ctx, dockerCli, backend, opts)
|
||||
return runCopy(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
flags.BoolVar(&opts.all, "all", false, "copy to all the containers of the service.")
|
||||
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
|
||||
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
|
||||
flags.MarkHidden("all") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
return copyCmd
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error {
|
||||
name, err := opts.toProjectName(dockerCli)
|
||||
func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
|
||||
name, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,13 +19,9 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -34,8 +30,6 @@ import (
|
||||
type createOptions struct {
|
||||
Build bool
|
||||
noBuild bool
|
||||
Pull string
|
||||
pullChanged bool
|
||||
removeOrphans bool
|
||||
ignoreOrphans bool
|
||||
forceRecreate bool
|
||||
@@ -45,19 +39,14 @@ type createOptions struct {
|
||||
timeChanged bool
|
||||
timeout int
|
||||
quietPull bool
|
||||
scale []string
|
||||
}
|
||||
|
||||
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := createOptions{}
|
||||
buildOpts := buildOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] [SERVICE...]",
|
||||
Use: "create [SERVICE...]",
|
||||
Short: "Creates containers for a service.",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.pullChanged = cmd.Flags().Changed("pull")
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.Build && opts.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
@@ -66,49 +55,27 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
|
||||
return runCreate(ctx, dockerCli, backend, opts, buildOpts, project, services)
|
||||
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
|
||||
return backend.Create(ctx, project, api.CreateOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
IgnoreOrphans: opts.ignoreOrphans,
|
||||
Recreate: opts.recreateStrategy(),
|
||||
RecreateDependencies: opts.dependenciesRecreateStrategy(),
|
||||
Inherit: !opts.noInherit,
|
||||
Timeout: opts.GetTimeout(),
|
||||
QuietPull: false,
|
||||
})
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
|
||||
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
|
||||
flags.StringVar(&opts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never"|"build")`)
|
||||
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
|
||||
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
||||
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error {
|
||||
if err := createOpts.Apply(project); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var build *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
bo, err := buildOpts.toAPIBuildOptions(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
build = &bo
|
||||
}
|
||||
|
||||
return backend.Create(ctx, project, api.CreateOptions{
|
||||
Build: build,
|
||||
Services: services,
|
||||
RemoveOrphans: createOpts.removeOrphans,
|
||||
IgnoreOrphans: createOpts.ignoreOrphans,
|
||||
Recreate: createOpts.recreateStrategy(),
|
||||
RecreateDependencies: createOpts.dependenciesRecreateStrategy(),
|
||||
Inherit: !createOpts.noInherit,
|
||||
Timeout: createOpts.GetTimeout(),
|
||||
QuietPull: false,
|
||||
})
|
||||
}
|
||||
|
||||
func (opts createOptions) recreateStrategy() string {
|
||||
if opts.noRecreate {
|
||||
return api.RecreateNever
|
||||
@@ -137,19 +104,7 @@ func (opts createOptions) GetTimeout() *time.Duration {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts createOptions) Apply(project *types.Project) error {
|
||||
if opts.pullChanged {
|
||||
if !opts.isPullPolicyValid() {
|
||||
return fmt.Errorf("invalid --pull option %q", opts.Pull)
|
||||
}
|
||||
for i, service := range project.Services {
|
||||
service.PullPolicy = opts.Pull
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
// N.B. opts.Build means "force build all", but images can still be built
|
||||
// when this is false
|
||||
// e.g. if a service has pull_policy: build or its local image is policy
|
||||
func (opts createOptions) Apply(project *types.Project) {
|
||||
if opts.Build {
|
||||
for i, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
@@ -159,39 +114,10 @@ func (opts createOptions) Apply(project *types.Project) error {
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
if err := applyPlatforms(project, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := applyScaleOpts(project, opts.scale)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyScaleOpts(project *types.Project, opts []string) error {
|
||||
for _, scale := range opts {
|
||||
split := strings.Split(scale, "=")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
|
||||
}
|
||||
name := split[0]
|
||||
replicas, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setServiceScale(project, name, replicas)
|
||||
if err != nil {
|
||||
return err
|
||||
if opts.noBuild {
|
||||
for i, service := range project.Services {
|
||||
service.Build = nil
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts createOptions) isPullPolicyValid() bool {
|
||||
pullPolicies := []string{types.PullPolicyAlways, types.PullPolicyNever, types.PullPolicyBuild,
|
||||
types.PullPolicyMissing, types.PullPolicyIfNotPresent}
|
||||
return slices.Contains(pullPolicies, opts.Pull)
|
||||
}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestRunCreate(t *testing.T) {
|
||||
ctrl, ctx := gomock.WithContext(context.Background(), t)
|
||||
backend := mocks.NewMockService(ctrl)
|
||||
backend.EXPECT().Create(
|
||||
gomock.Eq(ctx),
|
||||
pullPolicy(""),
|
||||
deepEqual(defaultCreateOptions(true)),
|
||||
)
|
||||
|
||||
createOpts := createOptions{}
|
||||
buildOpts := buildOptions{}
|
||||
project := sampleProject()
|
||||
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRunCreate_Build(t *testing.T) {
|
||||
ctrl, ctx := gomock.WithContext(context.Background(), t)
|
||||
backend := mocks.NewMockService(ctrl)
|
||||
backend.EXPECT().Create(
|
||||
gomock.Eq(ctx),
|
||||
pullPolicy("build"),
|
||||
deepEqual(defaultCreateOptions(true)),
|
||||
)
|
||||
|
||||
createOpts := createOptions{
|
||||
Build: true,
|
||||
}
|
||||
buildOpts := buildOptions{}
|
||||
project := sampleProject()
|
||||
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRunCreate_NoBuild(t *testing.T) {
|
||||
ctrl, ctx := gomock.WithContext(context.Background(), t)
|
||||
backend := mocks.NewMockService(ctrl)
|
||||
backend.EXPECT().Create(
|
||||
gomock.Eq(ctx),
|
||||
pullPolicy(""),
|
||||
deepEqual(defaultCreateOptions(false)),
|
||||
)
|
||||
|
||||
createOpts := createOptions{
|
||||
noBuild: true,
|
||||
}
|
||||
buildOpts := buildOptions{}
|
||||
project := sampleProject()
|
||||
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func sampleProject() *types.Project {
|
||||
return &types.Project{
|
||||
Name: "test",
|
||||
Services: types.Services{
|
||||
"svc": {
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCreateOptions(includeBuild bool) api.CreateOptions {
|
||||
var build *api.BuildOptions
|
||||
if includeBuild {
|
||||
bo := defaultBuildOptions()
|
||||
build = &bo
|
||||
}
|
||||
return api.CreateOptions{
|
||||
Build: build,
|
||||
Services: nil,
|
||||
RemoveOrphans: false,
|
||||
IgnoreOrphans: false,
|
||||
Recreate: "diverged",
|
||||
RecreateDependencies: "diverged",
|
||||
Inherit: true,
|
||||
Timeout: nil,
|
||||
QuietPull: false,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultBuildOptions() api.BuildOptions {
|
||||
return api.BuildOptions{
|
||||
Args: make(types.MappingWithEquals),
|
||||
Progress: "auto",
|
||||
}
|
||||
}
|
||||
|
||||
// deepEqual returns a nice diff on failure vs gomock.Eq when used
|
||||
// on structs.
|
||||
func deepEqual(x interface{}) gomock.Matcher {
|
||||
return gomock.GotFormatterAdapter(
|
||||
gomock.GotFormatterFunc(func(got interface{}) string {
|
||||
return cmp.Diff(x, got)
|
||||
}),
|
||||
gomock.Eq(x),
|
||||
)
|
||||
}
|
||||
|
||||
func spewAdapter(m gomock.Matcher) gomock.Matcher {
|
||||
return gomock.GotFormatterAdapter(
|
||||
gomock.GotFormatterFunc(func(got interface{}) string {
|
||||
return spew.Sdump(got)
|
||||
}),
|
||||
m,
|
||||
)
|
||||
}
|
||||
|
||||
type withPullPolicy struct {
|
||||
policy string
|
||||
}
|
||||
|
||||
func pullPolicy(policy string) gomock.Matcher {
|
||||
return spewAdapter(withPullPolicy{policy: policy})
|
||||
}
|
||||
|
||||
func (w withPullPolicy) Matches(x interface{}) bool {
|
||||
proj, ok := x.(*types.Project)
|
||||
if !ok || proj == nil || len(proj.Services) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, svc := range proj.Services {
|
||||
if svc.PullPolicy != w.policy {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (w withPullPolicy) String() string {
|
||||
return fmt.Sprintf("has pull policy %q for all services", w.policy)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
type downOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
removeOrphans bool
|
||||
timeChanged bool
|
||||
timeout int
|
||||
@@ -40,12 +40,12 @@ type downOptions struct {
|
||||
images string
|
||||
}
|
||||
|
||||
func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := downOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down [OPTIONS] [SERVICES]",
|
||||
Use: "down",
|
||||
Short: "Stop and remove containers, networks",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
@@ -57,18 +57,20 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runDown(ctx, dockerCli, backend, opts, args)
|
||||
return runDown(ctx, backend, opts)
|
||||
}),
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
|
||||
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", 0, "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.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if name == "volume" {
|
||||
switch name {
|
||||
case "volume":
|
||||
name = "volumes"
|
||||
logrus.Warn("--volume is deprecated, please use --volumes")
|
||||
}
|
||||
@@ -77,10 +79,16 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return downCmd
|
||||
}
|
||||
|
||||
func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
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
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
@@ -94,6 +102,5 @@ func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
Timeout: timeout,
|
||||
Images: opts.images,
|
||||
Volumes: opts.volumes,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,32 +31,32 @@ type eventsOpts struct {
|
||||
json bool
|
||||
}
|
||||
|
||||
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := eventsOpts{
|
||||
composeOptions: &composeOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
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, dockerCli, backend, opts, args)
|
||||
return runEvents(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error {
|
||||
name, err := opts.toProjectName(dockerCli)
|
||||
func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Events(ctx, name, api.EventsOptions{
|
||||
return backend.Events(ctx, project, api.EventsOptions{
|
||||
Services: services,
|
||||
Consumer: func(event api.Event) error {
|
||||
if opts.json {
|
||||
@@ -72,9 +71,9 @@ func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), string(marshal))
|
||||
fmt.Println(string(marshal))
|
||||
} else {
|
||||
fmt.Fprintln(dockerCli.Out(), event)
|
||||
fmt.Println(event)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -43,14 +43,14 @@ type execOpts struct {
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := execOpts{
|
||||
composeOptions: &composeOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
|
||||
Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]",
|
||||
Short: "Execute a command in a running container.",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -59,14 +59,14 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExec(ctx, dockerCli, backend, opts)
|
||||
return runExec(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
|
||||
runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
@@ -81,8 +81,8 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return runCmd
|
||||
}
|
||||
|
||||
func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,30 +34,28 @@ import (
|
||||
)
|
||||
|
||||
type imageOptions struct {
|
||||
*ProjectOptions
|
||||
Quiet bool
|
||||
Format string
|
||||
*projectOptions
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := imageOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
imgCmd := &cobra.Command{
|
||||
Use: "images [OPTIONS] [SERVICE...]",
|
||||
Use: "images [SERVICE...]",
|
||||
Short: "List images used by the created containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runImages(ctx, dockerCli, backend, opts, args)
|
||||
return runImages(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
return imgCmd
|
||||
}
|
||||
|
||||
func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
func runImages(ctx context.Context, backend api.Service, opts imageOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +79,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
}
|
||||
}
|
||||
for _, img := range ids {
|
||||
fmt.Fprintln(dockerCli.Out(), img)
|
||||
fmt.Println(img)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -90,7 +88,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
return images[i].ContainerName < images[j].ContainerName
|
||||
})
|
||||
|
||||
return formatter.Print(images, opts.Format, dockerCli.Out(),
|
||||
return formatter.Print(images, formatter.PRETTY, os.Stdout,
|
||||
func(w io.Writer) {
|
||||
for _, img := range images {
|
||||
id := stringid.TruncateID(img.ID)
|
||||
@@ -106,5 +104,5 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
|
||||
}
|
||||
},
|
||||
"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
|
||||
"Container", "Repository", "Tag", "Image Id", "Size")
|
||||
}
|
||||
|
||||
@@ -18,52 +18,45 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type killOptions struct {
|
||||
*ProjectOptions
|
||||
removeOrphans bool
|
||||
signal string
|
||||
*projectOptions
|
||||
signal string
|
||||
}
|
||||
|
||||
func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := killOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [OPTIONS] [SERVICE...]",
|
||||
Use: "kill [options] [SERVICE...]",
|
||||
Short: "Force stop service containers.",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, dockerCli, backend, opts, args)
|
||||
return runKill(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
|
||||
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, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Kill(ctx, name, api.KillOptions{
|
||||
RemoveOrphans: opts.removeOrphans,
|
||||
Project: project,
|
||||
Services: services,
|
||||
Signal: opts.signal,
|
||||
return backend.Kill(ctx, projectName, api.KillOptions{
|
||||
Services: services,
|
||||
Signal: opts.signal,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/docker/cli/opts"
|
||||
@@ -38,21 +38,20 @@ type lsOptions struct {
|
||||
Filter opts.FilterOpt
|
||||
}
|
||||
|
||||
func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
|
||||
func listCommand(backend api.Service) *cobra.Command {
|
||||
opts := lsOptions{Filter: opts.NewFilterOpt()}
|
||||
lsCmd := &cobra.Command{
|
||||
Use: "ls [OPTIONS]",
|
||||
Use: "ls",
|
||||
Short: "List running compose projects",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runList(ctx, dockerCli, backend, lsOpts)
|
||||
return runList(ctx, backend, opts)
|
||||
}),
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.")
|
||||
lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects")
|
||||
lsCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
|
||||
lsCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs.")
|
||||
lsCmd.Flags().Var(&opts.Filter, "filter", "Filter output based on conditions provided.")
|
||||
lsCmd.Flags().BoolVarP(&opts.All, "all", "a", false, "Show all stopped Compose projects")
|
||||
|
||||
return lsCmd
|
||||
}
|
||||
@@ -61,17 +60,23 @@ var acceptedListFilters = map[string]bool{
|
||||
"name": true,
|
||||
}
|
||||
|
||||
func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, lsOpts lsOptions) error {
|
||||
filters := lsOpts.Filter.Value()
|
||||
func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
|
||||
filters := opts.Filter.Value()
|
||||
err := filters.Validate(acceptedListFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stackList, err := backend.List(ctx, api.ListOptions{All: lsOpts.All})
|
||||
stackList, err := backend.List(ctx, api.ListOptions{All: opts.All})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Quiet {
|
||||
for _, s := range stackList {
|
||||
fmt.Println(s.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if filters.Len() > 0 {
|
||||
var filtered []api.Stack
|
||||
@@ -84,15 +89,8 @@ func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, ls
|
||||
stackList = filtered
|
||||
}
|
||||
|
||||
if lsOpts.Quiet {
|
||||
for _, s := range stackList {
|
||||
fmt.Fprintln(dockerCli.Out(), s.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
view := viewFromStackList(stackList)
|
||||
return formatter.Print(view, lsOpts.Format, dockerCli.Out(), func(w io.Writer) {
|
||||
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
|
||||
for _, stack := range view {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
|
||||
}
|
||||
|
||||
@@ -18,20 +18,19 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type logsOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
composeOptions
|
||||
follow bool
|
||||
index int
|
||||
tail string
|
||||
since string
|
||||
until string
|
||||
@@ -40,47 +39,38 @@ type logsOptions struct {
|
||||
timestamps bool
|
||||
}
|
||||
|
||||
func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := logsOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
logsCmd := &cobra.Command{
|
||||
Use: "logs [OPTIONS] [SERVICE...]",
|
||||
Use: "logs [SERVICE...]",
|
||||
Short: "View output from containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runLogs(ctx, dockerCli, backend, opts, args)
|
||||
return runLogs(ctx, backend, opts, args)
|
||||
}),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if opts.index > 0 && len(args) != 1 {
|
||||
return errors.New("--index requires one service to be selected")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := logsCmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
|
||||
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.")
|
||||
flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
|
||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps.")
|
||||
flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container.")
|
||||
flags.StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs for each container.")
|
||||
return logsCmd
|
||||
}
|
||||
|
||||
func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runLogs(ctx context.Context, backend api.Service, opts logsOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
|
||||
return backend.Logs(ctx, name, consumer, api.LogOptions{
|
||||
Project: project,
|
||||
consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix)
|
||||
return backend.Logs(ctx, projectName, consumer, api.LogOptions{
|
||||
Services: services,
|
||||
Follow: opts.follow,
|
||||
Index: opts.index,
|
||||
Tail: opts.tail,
|
||||
Since: opts.since,
|
||||
Until: opts.until,
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
defaultPlatform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
|
||||
for name, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// default platform only applies if the service doesn't specify
|
||||
if defaultPlatform != "" && service.Platform == "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, defaultPlatform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
|
||||
}
|
||||
service.Platform = defaultPlatform
|
||||
}
|
||||
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
if !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
|
||||
if buildForSinglePlatform || len(service.Build.Platforms) == 0 {
|
||||
// if we're building for a single platform, we want to build for the platform we'll use to run the image
|
||||
// similarly, if no build platforms were explicitly specified, it makes sense to build for the platform
|
||||
// the image is designed for rather than allowing the builder to infer the platform
|
||||
service.Build.Platforms = []string{service.Platform}
|
||||
}
|
||||
}
|
||||
|
||||
// services can specify that they should be built for multiple platforms, which can be used
|
||||
// with `docker compose build` to produce a multi-arch image
|
||||
// other cases, such as `up` and `run`, need a single architecture to actually run
|
||||
// if there is only a single platform present (which might have been inferred
|
||||
// from service.Platform above), it will be used, even if it requires emulation.
|
||||
// if there's more than one platform, then the list is cleared so that the builder
|
||||
// can decide.
|
||||
// TODO(milas): there's no validation that the platform the builder will pick is actually one
|
||||
// of the supported platforms from the build definition
|
||||
// e.g. `build.platforms: [linux/arm64, linux/amd64]` on a `linux/ppc64` machine would build
|
||||
// for `linux/ppc64` instead of returning an error that it's not a valid platform for the service.
|
||||
if buildForSinglePlatform && len(service.Build.Platforms) > 1 {
|
||||
// empty indicates that the builder gets to decide
|
||||
service.Build.Platforms = nil
|
||||
}
|
||||
project.Services[name] = service
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
||||
makeProject := func() *types.Project {
|
||||
return &types.Project{
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"alice/32",
|
||||
},
|
||||
},
|
||||
Platform: "alice/32",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("SinglePlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, true))
|
||||
require.EqualValues(t, []string{"alice/32"}, project.Services["test"].Build.Platforms)
|
||||
})
|
||||
|
||||
t.Run("MultiPlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, false))
|
||||
require.EqualValues(t, []string{"linux/amd64", "linux/arm64", "alice/32"},
|
||||
project.Services["test"].Build.Platforms)
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
|
||||
makeProject := func() *types.Project {
|
||||
return &types.Project{
|
||||
Environment: map[string]string{
|
||||
"DOCKER_DEFAULT_PLATFORM": "linux/amd64",
|
||||
},
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("SinglePlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, true))
|
||||
require.EqualValues(t, []string{"linux/amd64"}, project.Services["test"].Build.Platforms)
|
||||
})
|
||||
|
||||
t.Run("MultiPlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, false))
|
||||
require.EqualValues(t, []string{"linux/amd64", "linux/arm64"},
|
||||
project.Services["test"].Build.Platforms)
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
|
||||
makeProject := func() *types.Project {
|
||||
return &types.Project{
|
||||
Environment: map[string]string{
|
||||
"DOCKER_DEFAULT_PLATFORM": "commodore/64",
|
||||
},
|
||||
Services: types.Services{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
Platforms: []string{
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("SinglePlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.EqualError(t, applyPlatforms(project, true),
|
||||
`service "test" build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: commodore/64`)
|
||||
})
|
||||
|
||||
t.Run("MultiPlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.EqualError(t, applyPlatforms(project, false),
|
||||
`service "test" build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: commodore/64`)
|
||||
})
|
||||
}
|
||||
@@ -19,70 +19,67 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type pauseOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
}
|
||||
|
||||
func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pauseOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pause [SERVICE...]",
|
||||
Short: "Pause services",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPause(ctx, dockerCli, backend, opts, args)
|
||||
return runPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Pause(ctx, name, api.PauseOptions{
|
||||
return backend.Pause(ctx, project, api.PauseOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
type unpauseOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
}
|
||||
|
||||
func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := unpauseOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "unpause [SERVICE...]",
|
||||
Short: "Unpause services",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runUnPause(ctx, dockerCli, backend, opts, args)
|
||||
return runUnPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.UnPause(ctx, name, api.PauseOptions{
|
||||
return backend.UnPause(ctx, project, api.PauseOptions{
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,50 +20,47 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type portOptions struct {
|
||||
*ProjectOptions
|
||||
port uint16
|
||||
*projectOptions
|
||||
port int
|
||||
protocol string
|
||||
index int
|
||||
}
|
||||
|
||||
func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := portOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
|
||||
Use: "port [options] [--] SERVICE PRIVATE_PORT",
|
||||
Short: "Print the public port for a port binding.",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
port, err := strconv.ParseUint(args[1], 10, 16)
|
||||
port, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.port = uint16(port)
|
||||
opts.protocol = strings.ToLower(opts.protocol)
|
||||
opts.port = port
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPort(ctx, dockerCli, backend, opts, args[0])
|
||||
return runPort(ctx, backend, opts, args[0])
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
func runPort(ctx context.Context, backend api.Service, opts portOptions, service string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,6 +72,6 @@ func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
|
||||
fmt.Printf("%s:%d\n", ip, port)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,31 +18,31 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliformatter "github.com/docker/cli/cli/command/formatter"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type psOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
Format string
|
||||
All bool
|
||||
Quiet bool
|
||||
Services bool
|
||||
Filter string
|
||||
Status []string
|
||||
noTrunc bool
|
||||
Orphans bool
|
||||
}
|
||||
|
||||
func (p *psOptions) parseFilter() error {
|
||||
@@ -64,55 +64,37 @@ func (p *psOptions) parseFilter() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := psOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
psCmd := &cobra.Command{
|
||||
Use: "ps [OPTIONS] [SERVICE...]",
|
||||
Use: "ps [SERVICE...]",
|
||||
Short: "List containers",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.parseFilter()
|
||||
},
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPs(ctx, dockerCli, backend, args, opts)
|
||||
return runPs(ctx, backend, args, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
|
||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
|
||||
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.BoolVar(&opts.Services, "services", false, "Display services")
|
||||
flags.BoolVar(&opts.Orphans, "orphans", true, "Include orphaned services (not declared by project)")
|
||||
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||
return psCmd
|
||||
}
|
||||
|
||||
func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if project != nil {
|
||||
names := project.ServiceNames()
|
||||
if len(services) > 0 {
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
} else if !opts.Orphans {
|
||||
// until user asks to list orphaned services, we only include those declared in project
|
||||
services = names
|
||||
}
|
||||
}
|
||||
|
||||
containers, err := backend.Ps(ctx, name, api.PsOptions{
|
||||
Project: project,
|
||||
containers, err := backend.Ps(ctx, projectName, api.PsOptions{
|
||||
All: opts.All,
|
||||
Services: services,
|
||||
})
|
||||
@@ -120,6 +102,16 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
return err
|
||||
}
|
||||
|
||||
SERVICES:
|
||||
for _, s := range services {
|
||||
for _, c := range containers {
|
||||
if c.Service == s {
|
||||
continue SERVICES
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("no such service: %s", s)
|
||||
}
|
||||
|
||||
if len(opts.Status) != 0 {
|
||||
containers = filterByStatus(containers, opts.Status)
|
||||
}
|
||||
@@ -130,33 +122,41 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
|
||||
if opts.Quiet {
|
||||
for _, c := range containers {
|
||||
fmt.Fprintln(dockerCli.Out(), c.ID)
|
||||
fmt.Println(c.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.Services {
|
||||
services := []string{}
|
||||
for _, c := range containers {
|
||||
s := c.Service
|
||||
if !utils.StringContains(services, s) {
|
||||
services = append(services, s)
|
||||
for _, s := range containers {
|
||||
if !utils.StringContains(services, s.Service) {
|
||||
services = append(services, s.Service)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||
fmt.Println(strings.Join(services, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.Format == "" {
|
||||
opts.Format = dockerCli.ConfigFile().PsFormat
|
||||
}
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
writter(containers),
|
||||
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
||||
}
|
||||
|
||||
containerCtx := cliformatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewContainerFormat(opts.Format, opts.Quiet, false),
|
||||
Trunc: !opts.noTrunc,
|
||||
func writter(containers []api.ContainerSummary) func(w io.Writer) {
|
||||
return func(w io.Writer) {
|
||||
for _, container := range containers {
|
||||
ports := DisplayablePorts(container)
|
||||
status := container.State
|
||||
if status == "running" && container.Health != "" {
|
||||
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
|
||||
} else if status == "exited" || status == "dead" {
|
||||
status = fmt.Sprintf("%s (%d)", container.State, container.ExitCode)
|
||||
}
|
||||
command := formatter2.Ellipsis(container.Command, 20)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", container.Name, strconv.Quote(command), container.Service, status, ports)
|
||||
}
|
||||
}
|
||||
return formatter.ContainerWrite(containerCtx, containers)
|
||||
}
|
||||
|
||||
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
|
||||
@@ -177,3 +177,73 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type portRange struct {
|
||||
pStart int
|
||||
pEnd int
|
||||
tStart int
|
||||
tEnd int
|
||||
IP string
|
||||
protocol string
|
||||
}
|
||||
|
||||
func (pr portRange) String() string {
|
||||
var (
|
||||
pub string
|
||||
tgt string
|
||||
)
|
||||
|
||||
if pr.pEnd > pr.pStart {
|
||||
pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
|
||||
} else if pr.pStart > 0 {
|
||||
pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
|
||||
}
|
||||
if pr.tEnd > pr.tStart {
|
||||
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
|
||||
} else {
|
||||
tgt = fmt.Sprintf("%d", pr.tStart)
|
||||
}
|
||||
return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
|
||||
}
|
||||
|
||||
// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
|
||||
func DisplayablePorts(c api.ContainerSummary) string {
|
||||
if c.Publishers == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
sort.Sort(c.Publishers)
|
||||
|
||||
pr := portRange{}
|
||||
ports := []string{}
|
||||
for _, p := range c.Publishers {
|
||||
prIsRange := pr.tEnd != pr.tStart
|
||||
tOverlaps := p.TargetPort <= pr.tEnd
|
||||
|
||||
// Start a new port-range if:
|
||||
// - the protocol is different from the current port-range
|
||||
// - published or target port are not consecutive to the current port-range
|
||||
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
|
||||
if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
|
||||
// start a new port-range, and print the previous port-range (if any)
|
||||
if pr.pStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
pr = portRange{
|
||||
pStart: p.PublishedPort,
|
||||
pEnd: p.PublishedPort,
|
||||
tStart: p.TargetPort,
|
||||
tEnd: p.TargetPort,
|
||||
protocol: p.Protocol,
|
||||
IP: p.URL,
|
||||
}
|
||||
continue
|
||||
}
|
||||
pr.pEnd = p.PublishedPort
|
||||
pr.tEnd = p.TargetPort
|
||||
}
|
||||
if pr.tStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestPsTable(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dir := t.TempDir()
|
||||
out := filepath.Join(dir, "output.txt")
|
||||
f, err := os.Create(out)
|
||||
if err != nil {
|
||||
t.Fatal("could not create output file")
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
backend := mocks.NewMockService(ctrl)
|
||||
backend.EXPECT().
|
||||
Ps(gomock.Eq(ctx), gomock.Any(), gomock.Any()).
|
||||
DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
return []api.ContainerSummary{
|
||||
{
|
||||
ID: "abc123",
|
||||
Name: "ABC",
|
||||
Image: "foo/bar",
|
||||
Publishers: api.PortPublishers{
|
||||
{
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 8080,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
TargetPort: 8443,
|
||||
PublishedPort: 8443,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}).AnyTimes()
|
||||
|
||||
opts := psOptions{ProjectOptions: &ProjectOptions{ProjectName: "test"}}
|
||||
stdout := streams.NewOut(f)
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
cli.EXPECT().Out().Return(stdout).AnyTimes()
|
||||
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
|
||||
err = runPs(ctx, cli, backend, nil, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
output, err := os.ReadFile(out)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type publishOptions struct {
|
||||
*ProjectOptions
|
||||
resolveImageDigests bool
|
||||
ociVersion string
|
||||
}
|
||||
|
||||
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := publishOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "publish [OPTIONS] [REPOSITORY]",
|
||||
Short: "Publish compose application",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPublish(ctx, dockerCli, backend, opts, args[0])
|
||||
}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
|
||||
project, err := opts.ToProject(dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Publish(ctx, project, repository, api.PublishOptions{
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
OCIVersion: api.OCIVersion(opts.ociVersion),
|
||||
})
|
||||
}
|
||||
@@ -21,32 +21,29 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type pullOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
composeOptions
|
||||
quiet bool
|
||||
parallel bool
|
||||
noParallel bool
|
||||
includeDeps bool
|
||||
ignorePullFailures bool
|
||||
noBuildable bool
|
||||
policy string
|
||||
}
|
||||
|
||||
func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pullOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull [OPTIONS] [SERVICE...]",
|
||||
Use: "pull [SERVICE...]",
|
||||
Short: "Pull service images",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.noParallel {
|
||||
@@ -55,58 +52,42 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPull(ctx, dockerCli, backend, opts, args)
|
||||
return runPull(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information.")
|
||||
cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
|
||||
cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies")
|
||||
cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel.")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.")
|
||||
flags.MarkHidden("no-parallel") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures.")
|
||||
cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built.")
|
||||
cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always").`)
|
||||
cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts pullOptions) apply(project *types.Project, services []string) (*types.Project, error) {
|
||||
func runPull(ctx context.Context, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.includeDeps {
|
||||
var err error
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
enabled, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.policy != "" {
|
||||
for i, service := range project.Services {
|
||||
if service.Image == "" {
|
||||
continue
|
||||
for _, s := range project.Services {
|
||||
if !utils.StringContains(services, s.Name) {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
service.PullPolicy = opts.policy
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err = opts.apply(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
project.Services = enabled
|
||||
}
|
||||
|
||||
return backend.Pull(ctx, project, api.PullOptions{
|
||||
Quiet: opts.quiet,
|
||||
IgnoreFailures: opts.ignorePullFailures,
|
||||
IgnoreBuildable: opts.noBuildable,
|
||||
Quiet: opts.quiet,
|
||||
IgnoreFailures: opts.ignorePullFailures,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestApplyPullOptions(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"must-build": {
|
||||
Name: "must-build",
|
||||
// No image, local build only
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
},
|
||||
"has-build": {
|
||||
Name: "has-build",
|
||||
Image: "registry.example.com/myservice",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
},
|
||||
"must-pull": {
|
||||
Name: "must-pull",
|
||||
Image: "registry.example.com/another-service",
|
||||
},
|
||||
},
|
||||
}
|
||||
project, err := pullOptions{
|
||||
policy: types.PullPolicyMissing,
|
||||
}.apply(project, nil)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, project.Services["must-build"].PullPolicy, "") // still default
|
||||
assert.Equal(t, project.Services["has-build"].PullPolicy, types.PullPolicyMissing)
|
||||
assert.Equal(t, project.Services["must-pull"].PullPolicy, types.PullPolicyMissing)
|
||||
}
|
||||
@@ -19,55 +19,42 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type pushOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
composeOptions
|
||||
IncludeDeps bool
|
||||
|
||||
Ignorefailures bool
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := pushOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
pushCmd := &cobra.Command{
|
||||
Use: "push [OPTIONS] [SERVICE...]",
|
||||
Use: "push [SERVICE...]",
|
||||
Short: "Push service images",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPush(ctx, dockerCli, backend, opts, args)
|
||||
return runPush(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
|
||||
pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies")
|
||||
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
|
||||
|
||||
return pushCmd
|
||||
}
|
||||
|
||||
func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
func runPush(ctx context.Context, backend api.Service, opts pushOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.IncludeDeps {
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return backend.Push(ctx, project, api.PushOptions{
|
||||
IgnoreFailures: opts.Ignorefailures,
|
||||
Quiet: opts.Quiet,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,24 +19,23 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
force bool
|
||||
stop bool
|
||||
volumes bool
|
||||
}
|
||||
|
||||
func removeCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func removeCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := removeOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm [OPTIONS] [SERVICE...]",
|
||||
Use: "rm [SERVICE...]",
|
||||
Short: "Removes stopped service containers",
|
||||
Long: `Removes stopped service containers
|
||||
|
||||
@@ -45,9 +44,9 @@ can override this with -v. To list all volumes, use "docker volume ls".
|
||||
|
||||
Any data which is not in a volume will be lost.`,
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRemove(ctx, dockerCli, backend, opts, args)
|
||||
return runRemove(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
|
||||
@@ -59,17 +58,24 @@ Any data which is not in a volume will be lost.`,
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Remove(ctx, name, api.RemoveOptions{
|
||||
if opts.stop {
|
||||
err := backend.Stop(ctx, project, api.StopOptions{
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return backend.Remove(ctx, project, api.RemoveOptions{
|
||||
Services: services,
|
||||
Force: opts.force,
|
||||
Volumes: opts.volumes,
|
||||
Project: project,
|
||||
Stop: opts.stop,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,64 +20,43 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type restartOptions struct {
|
||||
*ProjectOptions
|
||||
timeChanged bool
|
||||
timeout int
|
||||
noDeps bool
|
||||
*projectOptions
|
||||
timeout int
|
||||
}
|
||||
|
||||
func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := restartOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
restartCmd := &cobra.Command{
|
||||
Use: "restart [OPTIONS] [SERVICE...]",
|
||||
Short: "Restart service containers",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
},
|
||||
Use: "restart",
|
||||
Short: "Restart containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRestart(ctx, dockerCli, backend, opts, args)
|
||||
return runRestart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := restartCmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
return restartCmd
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli)
|
||||
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if project != nil && len(services) > 0 {
|
||||
project, err = project.WithServicesEnabled(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
if opts.timeChanged {
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
|
||||
return backend.Restart(ctx, name, api.RestartOptions{
|
||||
Timeout: timeout,
|
||||
timeout := time.Duration(opts.timeout) * time.Second
|
||||
return backend.Restart(ctx, projectName, api.RestartOptions{
|
||||
Timeout: &timeout,
|
||||
Services: services,
|
||||
Project: project,
|
||||
NoDeps: opts.noDeps,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,14 +21,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/format"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
cgo "github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
cgo "github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -36,7 +32,6 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type runOptions struct {
|
||||
@@ -47,14 +42,11 @@ type runOptions struct {
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
tty bool
|
||||
interactive bool
|
||||
user string
|
||||
workdir string
|
||||
entrypoint string
|
||||
entrypointCmd []string
|
||||
capAdd opts.ListOpts
|
||||
capDrop opts.ListOpts
|
||||
labels []string
|
||||
volumes []string
|
||||
publish []string
|
||||
@@ -66,133 +58,112 @@ type runOptions struct {
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func (options runOptions) apply(project *types.Project) (*types.Project, error) {
|
||||
if options.noDeps {
|
||||
var err error
|
||||
project, err = project.WithSelectedServices([]string{options.Service}, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
target, err := project.GetService(options.Service)
|
||||
func (opts runOptions) apply(project *types.Project) error {
|
||||
target, err := project.GetService(opts.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
target.Tty = !options.noTty
|
||||
target.StdinOpen = options.interactive
|
||||
|
||||
// --service-ports and --publish are incompatible
|
||||
if !options.servicePorts {
|
||||
if len(target.Ports) > 0 {
|
||||
logrus.Debug("Running service without ports exposed as --service-ports=false")
|
||||
}
|
||||
target.Tty = !opts.noTty
|
||||
target.StdinOpen = opts.interactive
|
||||
if !opts.servicePorts {
|
||||
target.Ports = []types.ServicePortConfig{}
|
||||
for _, p := range options.publish {
|
||||
}
|
||||
if len(opts.publish) > 0 {
|
||||
target.Ports = []types.ServicePortConfig{}
|
||||
for _, p := range opts.publish {
|
||||
config, err := types.ParsePortConfig(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
target.Ports = append(target.Ports, config...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range options.volumes {
|
||||
volume, err := format.ParseVolume(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(opts.volumes) > 0 {
|
||||
for _, v := range opts.volumes {
|
||||
volume, err := loader.ParseVolume(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Volumes = append(target.Volumes, volume)
|
||||
}
|
||||
target.Volumes = append(target.Volumes, volume)
|
||||
}
|
||||
|
||||
for name := range project.Services {
|
||||
if name == options.Service {
|
||||
project.Services[name] = target
|
||||
if opts.noDeps {
|
||||
for _, s := range project.Services {
|
||||
if s.Name != opts.Service {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
}
|
||||
project.Services = types.Services{target}
|
||||
}
|
||||
|
||||
for i, s := range project.Services {
|
||||
if s.Name == opts.Service {
|
||||
project.Services[i] = target
|
||||
break
|
||||
}
|
||||
}
|
||||
return project, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := runOptions{
|
||||
func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := runOptions{
|
||||
composeOptions: &composeOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
},
|
||||
capAdd: opts.NewListOpts(nil),
|
||||
capDrop: opts.NewListOpts(nil),
|
||||
}
|
||||
createOpts := createOptions{}
|
||||
buildOpts := buildOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
|
||||
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] 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 {
|
||||
options.Service = args[0]
|
||||
opts.Service = args[0]
|
||||
if len(args) > 1 {
|
||||
options.Command = args[1:]
|
||||
opts.Command = args[1:]
|
||||
}
|
||||
if len(options.publish) > 0 && options.servicePorts {
|
||||
if len(opts.publish) > 0 && opts.servicePorts {
|
||||
return fmt.Errorf("--service-ports and --publish are incompatible")
|
||||
}
|
||||
if cmd.Flags().Changed("entrypoint") {
|
||||
command, err := shellwords.Parse(options.entrypoint)
|
||||
command, err := shellwords.Parse(opts.entrypoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.entrypointCmd = command
|
||||
}
|
||||
if cmd.Flags().Changed("tty") {
|
||||
if cmd.Flags().Changed("no-TTY") {
|
||||
return fmt.Errorf("--tty and --no-TTY can't be used together")
|
||||
} else {
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
opts.entrypointCmd = command
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
project, err := p.ToProject(dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
project, err := p.toProject([]string{opts.Service}, cgo.WithResolvedPaths(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createOpts.quietPull {
|
||||
buildOpts.Progress = string(xprogress.QuietMode)
|
||||
}
|
||||
|
||||
options.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
|
||||
return runRun(ctx, backend, project, options, createOpts, buildOpts, dockerCli)
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
|
||||
flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
||||
flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities")
|
||||
flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities")
|
||||
flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.")
|
||||
flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
|
||||
flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.StringArrayVarP(&opts.volumes, "volume", "v", []string{}, "Bind mount a volume.")
|
||||
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
|
||||
cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
|
||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||
@@ -210,35 +181,21 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error {
|
||||
project, err := options.apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = createOpts.Apply(project)
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
|
||||
err := opts.apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
var buildForDeps *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// allow dependencies needing build to be implicitly selected
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForDeps = &bo
|
||||
}
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options.Service, options.ignoreOrphans)
|
||||
}, dockerCli.Err())
|
||||
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labels := types.Labels{}
|
||||
for _, s := range options.labels {
|
||||
for _, s := range opts.labels {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("label must be set as KEY=VALUE")
|
||||
@@ -246,44 +203,30 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
labels[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
var buildForRun *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// dependencies have already been started above, so only the service
|
||||
// being run might need to be built at this point
|
||||
bo, err := buildOpts.toAPIBuildOptions([]string{options.Service})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForRun = &bo
|
||||
}
|
||||
|
||||
// start container and attach to container streams
|
||||
runOpts := api.RunOptions{
|
||||
Build: buildForRun,
|
||||
Name: options.name,
|
||||
Service: options.Service,
|
||||
Command: options.Command,
|
||||
Detach: options.Detach,
|
||||
AutoRemove: options.Remove,
|
||||
Tty: !options.noTty,
|
||||
Interactive: options.interactive,
|
||||
WorkingDir: options.workdir,
|
||||
User: options.user,
|
||||
CapAdd: options.capAdd.GetAll(),
|
||||
CapDrop: options.capDrop.GetAll(),
|
||||
Environment: options.environment,
|
||||
Entrypoint: options.entrypointCmd,
|
||||
Name: opts.name,
|
||||
Service: opts.Service,
|
||||
Command: opts.Command,
|
||||
Detach: opts.Detach,
|
||||
AutoRemove: opts.Remove,
|
||||
Tty: !opts.noTty,
|
||||
Interactive: opts.interactive,
|
||||
WorkingDir: opts.workdir,
|
||||
User: opts.user,
|
||||
Environment: opts.environment,
|
||||
Entrypoint: opts.entrypointCmd,
|
||||
Labels: labels,
|
||||
UseNetworkAliases: options.useAliases,
|
||||
NoDeps: options.noDeps,
|
||||
UseNetworkAliases: opts.useAliases,
|
||||
NoDeps: opts.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: options.quietPull,
|
||||
QuietPull: opts.quietPull,
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
if name == options.Service {
|
||||
service.StdinOpen = options.interactive
|
||||
project.Services[name] = service
|
||||
for i, service := range project.Services {
|
||||
if service.Name == opts.Service {
|
||||
service.StdinOpen = opts.interactive
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,21 +241,20 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error {
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for name, service := range project.Services {
|
||||
if name != requestedServiceName {
|
||||
dependencies[name] = service
|
||||
for _, service := range project.Services {
|
||||
if service.Name != requestedServiceName {
|
||||
dependencies = append(dependencies, service)
|
||||
} else {
|
||||
requestedService = service
|
||||
}
|
||||
}
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices[requestedServiceName] = requestedService
|
||||
project.DisabledServices = append(project.DisabledServices, requestedService)
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: ignoreOrphans,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -320,9 +262,7 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: &project,
|
||||
})
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type scaleOptions struct {
|
||||
*ProjectOptions
|
||||
noDeps bool
|
||||
}
|
||||
|
||||
func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := scaleOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
scaleCmd := &cobra.Command{
|
||||
Use: "scale [SERVICE=REPLICAS...]",
|
||||
Short: "Scale services ",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
serviceTuples, err := parseServicesReplicasArgs(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := scaleCmd.Flags()
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
|
||||
return scaleCmd
|
||||
}
|
||||
|
||||
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
|
||||
services := maps.Keys(serviceReplicaTuples)
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.noDeps {
|
||||
if project, err = project.WithSelectedServices(services, types.IgnoreDependencies); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range serviceReplicaTuples {
|
||||
service, err := project.GetService(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.SetScale(value)
|
||||
project.Services[key] = service
|
||||
}
|
||||
|
||||
return backend.Scale(ctx, project, api.ScaleOptions{Services: services})
|
||||
}
|
||||
|
||||
func parseServicesReplicasArgs(args []string) (map[string]int, error) {
|
||||
serviceReplicaTuples := map[string]int{}
|
||||
for _, arg := range args {
|
||||
key, val, ok := strings.Cut(arg, "=")
|
||||
if !ok || key == "" || val == "" {
|
||||
return nil, fmt.Errorf("invalid scale specifier: %s", arg)
|
||||
}
|
||||
intValue, err := strconv.Atoi(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
|
||||
}
|
||||
serviceReplicaTuples[key] = intValue
|
||||
}
|
||||
return serviceReplicaTuples, nil
|
||||
}
|
||||
@@ -19,39 +19,36 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type startOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
}
|
||||
|
||||
func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := startOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
startCmd := &cobra.Command{
|
||||
Use: "start [SERVICE...]",
|
||||
Short: "Start services",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStart(ctx, dockerCli, backend, opts, args)
|
||||
return runStart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
return startCmd
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Start(ctx, name, api.StartOptions{
|
||||
return backend.Start(ctx, projectName, api.StartOptions{
|
||||
AttachTo: services,
|
||||
Project: project,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type statsOptions struct {
|
||||
ProjectOptions *ProjectOptions
|
||||
all bool
|
||||
format string
|
||||
noStream bool
|
||||
noTrunc bool
|
||||
}
|
||||
|
||||
func statsCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
opts := statsOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "stats [OPTIONS] [SERVICE]",
|
||||
Short: "Display a live stream of container(s) resource usage statistics",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStats(ctx, dockerCli, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||
flags.StringVar(&opts.format, "format", "", `Format output using a custom template:
|
||||
'table': Print output in table format with column headers (default)
|
||||
'table TEMPLATE': Print output in table format using the given Go template
|
||||
'json': Print in JSON format
|
||||
'TEMPLATE': Print output using the given Go template.
|
||||
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates`)
|
||||
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStats(ctx context.Context, dockerCli command.Cli, opts statsOptions, service []string) error {
|
||||
name, err := opts.ProjectOptions.toProjectName(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := []filters.KeyValuePair{
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, name)),
|
||||
}
|
||||
if len(service) > 0 {
|
||||
filter = append(filter, filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, service[0])))
|
||||
}
|
||||
args := filters.NewArgs(filter...)
|
||||
return container.RunStats(ctx, dockerCli, &container.StatsOptions{
|
||||
All: opts.all,
|
||||
NoStream: opts.noStream,
|
||||
NoTrunc: opts.noTrunc,
|
||||
Format: opts.format,
|
||||
Filters: &args,
|
||||
})
|
||||
}
|
||||
@@ -20,41 +20,40 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type stopOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
timeChanged bool
|
||||
timeout int
|
||||
}
|
||||
|
||||
func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := stopOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "stop [OPTIONS] [SERVICE...]",
|
||||
Use: "stop [SERVICE...]",
|
||||
Short: "Stop services",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
opts.timeChanged = cmd.Flags().Changed("timeout")
|
||||
},
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStop(ctx, dockerCli, backend, opts, args)
|
||||
return runStop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,9 +63,8 @@ func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return backend.Stop(ctx, name, api.StopOptions{
|
||||
return backend.Stop(ctx, projectName, api.StopOptions{
|
||||
Timeout: timeout,
|
||||
Services: services,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,37 +20,37 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type topOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
}
|
||||
|
||||
func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func topCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := topOptions{
|
||||
ProjectOptions: p,
|
||||
projectOptions: p,
|
||||
}
|
||||
topCmd := &cobra.Command{
|
||||
Use: "top [SERVICES...]",
|
||||
Short: "Display the running processes",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runTop(ctx, dockerCli, backend, opts, args)
|
||||
return runTop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
return topCmd
|
||||
}
|
||||
|
||||
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
func runTop(ctx context.Context, backend api.Service, opts topOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,8 +64,8 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
|
||||
})
|
||||
|
||||
for _, container := range containers {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
|
||||
err := psPrinter(dockerCli.Out(), func(w io.Writer) {
|
||||
fmt.Printf("%s\n", container.Name)
|
||||
err := psPrinter(os.Stdout, func(w io.Writer) {
|
||||
for _, proc := range container.Processes {
|
||||
info := []interface{}{}
|
||||
for _, p := range proc {
|
||||
|
||||
@@ -18,16 +18,14 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -36,7 +34,7 @@ import (
|
||||
|
||||
// composeOptions hold options common to `up` and `run` to run compose project
|
||||
type composeOptions struct {
|
||||
*ProjectOptions
|
||||
*projectOptions
|
||||
}
|
||||
|
||||
type upOptions struct {
|
||||
@@ -46,66 +44,79 @@ type upOptions struct {
|
||||
noDeps bool
|
||||
cascadeStop bool
|
||||
exitCodeFrom string
|
||||
scale []string
|
||||
noColor bool
|
||||
noPrefix bool
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
noAttach []string
|
||||
timestamp bool
|
||||
wait bool
|
||||
waitTimeout int
|
||||
}
|
||||
|
||||
func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
|
||||
func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||
if opts.noDeps {
|
||||
var err error
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
enabled, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if !utils.StringContains(services, s.Name) {
|
||||
project.DisabledServices = append(project.DisabledServices, s)
|
||||
}
|
||||
}
|
||||
project.Services = enabled
|
||||
}
|
||||
|
||||
if opts.exitCodeFrom != "" {
|
||||
_, err := project.GetService(opts.exitCodeFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return project, nil
|
||||
for _, scale := range opts.scale {
|
||||
split := strings.Split(scale, "=")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
|
||||
}
|
||||
name := split[0]
|
||||
replicas, err := strconv.Atoi(split[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setServiceScale(project, name, uint64(replicas))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
build := buildOptions{ProjectOptions: p}
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up [OPTIONS] [SERVICE...]",
|
||||
Use: "up [SERVICE...]",
|
||||
Short: "Create and start containers",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
create.pullChanged = cmd.Flags().Changed("pull")
|
||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||
return validateFlags(&up, &create)
|
||||
}),
|
||||
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
|
||||
create.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
|
||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||
if create.ignoreOrphans && create.removeOrphans {
|
||||
return fmt.Errorf("cannot combine %s and --remove-orphans", ComposeIgnoreOrphans)
|
||||
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
|
||||
}
|
||||
if len(up.attach) != 0 && up.attachDependencies {
|
||||
return errors.New("cannot combine --attach and --attach-dependencies")
|
||||
}
|
||||
return runUp(ctx, dockerCli, backend, create, up, build, project, services)
|
||||
return runUp(ctx, backend, create, up, project, services)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
flags := upCmd.Flags()
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
|
||||
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
|
||||
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
|
||||
flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
|
||||
flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
||||
@@ -113,17 +124,14 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
|
||||
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
|
||||
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
|
||||
flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps.")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
|
||||
flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
|
||||
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
|
||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
|
||||
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services.")
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services.")
|
||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
|
||||
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
|
||||
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy.")
|
||||
|
||||
return upCmd
|
||||
}
|
||||
@@ -153,48 +161,35 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runUp(
|
||||
ctx context.Context,
|
||||
dockerCli command.Cli,
|
||||
backend api.Service,
|
||||
createOptions createOptions,
|
||||
upOptions upOptions,
|
||||
buildOptions buildOptions,
|
||||
project *types.Project,
|
||||
services []string,
|
||||
) error {
|
||||
func runUp(ctx context.Context, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
|
||||
if len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
}
|
||||
|
||||
err := createOptions.Apply(project)
|
||||
createOptions.Apply(project)
|
||||
|
||||
err := upOptions.apply(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err = upOptions.apply(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
var consumer api.LogConsumer
|
||||
if !upOptions.Detach {
|
||||
consumer = formatter.NewLogConsumer(ctx, os.Stdout, !upOptions.noColor, !upOptions.noPrefix)
|
||||
}
|
||||
|
||||
var build *api.BuildOptions
|
||||
if !createOptions.noBuild {
|
||||
if createOptions.quietPull {
|
||||
buildOptions.Progress = string(xprogress.QuietMode)
|
||||
}
|
||||
// BuildOptions here is nested inside CreateOptions, so
|
||||
// no service list is passed, it will implicitly pick all
|
||||
// services being created, which includes any explicitly
|
||||
// specified via "services" arg here as well as deps
|
||||
bo, err := buildOptions.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
build = &bo
|
||||
attachTo := services
|
||||
if len(upOptions.attach) > 0 {
|
||||
attachTo = upOptions.attach
|
||||
}
|
||||
if upOptions.attachDependencies {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
if len(attachTo) == 0 {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
|
||||
create := api.CreateOptions{
|
||||
Build: build,
|
||||
Services: services,
|
||||
RemoveOrphans: createOptions.removeOrphans,
|
||||
IgnoreOrphans: createOptions.ignoreOrphans,
|
||||
@@ -209,63 +204,33 @@ func runUp(
|
||||
return backend.Create(ctx, project, create)
|
||||
}
|
||||
|
||||
var consumer api.LogConsumer
|
||||
var attach []string
|
||||
if !upOptions.Detach {
|
||||
consumer = formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
|
||||
|
||||
var attachSet utils.Set[string]
|
||||
if len(upOptions.attach) != 0 {
|
||||
// services are passed explicitly with --attach, verify they're valid and then use them as-is
|
||||
attachSet = utils.NewSet(upOptions.attach...)
|
||||
unexpectedSvcs := attachSet.Diff(utils.NewSet(project.ServiceNames()...))
|
||||
if len(unexpectedSvcs) != 0 {
|
||||
return fmt.Errorf("cannot attach to services not included in up: %s", strings.Join(unexpectedSvcs.Elements(), ", "))
|
||||
}
|
||||
} else {
|
||||
// mark services being launched (and potentially their deps) for attach
|
||||
// if they didn't opt-out via Compose YAML
|
||||
attachSet = utils.NewSet[string]()
|
||||
var dependencyOpt types.DependencyOption = types.IgnoreDependencies
|
||||
if upOptions.attachDependencies {
|
||||
dependencyOpt = types.IncludeDependencies
|
||||
}
|
||||
if err := project.ForEachService(services, func(serviceName string, s *types.ServiceConfig) error {
|
||||
if s.Attach == nil || *s.Attach {
|
||||
attachSet.Add(serviceName)
|
||||
}
|
||||
return nil
|
||||
}, dependencyOpt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// filter out any services that have been explicitly marked for ignore with `--no-attach`
|
||||
attachSet.RemoveAll(upOptions.noAttach...)
|
||||
attach = attachSet.Elements()
|
||||
}
|
||||
|
||||
timeout := time.Duration(upOptions.waitTimeout) * time.Second
|
||||
return backend.Up(ctx, project, api.UpOptions{
|
||||
Create: create,
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attach,
|
||||
AttachTo: attachTo,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
WaitTimeout: timeout,
|
||||
Services: services,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func setServiceScale(project *types.Project, name string, replicas int) error {
|
||||
service, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
func setServiceScale(project *types.Project, name string, replicas uint64) error {
|
||||
for i, s := range project.Services {
|
||||
if s.Name == name {
|
||||
service, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if service.Deploy == nil {
|
||||
service.Deploy = &types.DeployConfig{}
|
||||
}
|
||||
service.Deploy.Replicas = &replicas
|
||||
project.Services[i] = service
|
||||
return nil
|
||||
}
|
||||
}
|
||||
service.SetScale(replicas)
|
||||
project.Services[name] = service
|
||||
return nil
|
||||
return fmt.Errorf("unknown service %q", name)
|
||||
}
|
||||
|
||||
@@ -19,33 +19,25 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestApplyScaleOpt(t *testing.T) {
|
||||
p := types.Project{
|
||||
Services: types.Services{
|
||||
"foo": {
|
||||
Services: []types.ServiceConfig{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
"bar": {
|
||||
{
|
||||
Name: "bar",
|
||||
Deploy: &types.DeployConfig{
|
||||
Mode: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := applyScaleOpts(&p, []string{"foo=2", "bar=3"})
|
||||
opt := upOptions{scale: []string{"foo=2"}}
|
||||
err := opt.apply(&p, nil)
|
||||
assert.NilError(t, err)
|
||||
foo, err := p.GetService("foo")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *foo.Scale, 2)
|
||||
|
||||
bar, err := p.GetService("bar")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *bar.Scale, 3)
|
||||
assert.Equal(t, *bar.Deploy.Replicas, 3)
|
||||
|
||||
assert.Equal(t, *foo.Deploy.Replicas, uint64(2))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -33,19 +32,14 @@ type versionOptions struct {
|
||||
short bool
|
||||
}
|
||||
|
||||
func versionCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func versionCommand() *cobra.Command {
|
||||
opts := versionOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "version [OPTIONS]",
|
||||
Use: "version",
|
||||
Short: "Show the Docker Compose version information",
|
||||
Args: cobra.NoArgs,
|
||||
Args: cobra.MaximumNArgs(0),
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
runVersion(opts, dockerCli)
|
||||
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
|
||||
runVersion(opts)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -57,14 +51,14 @@ func versionCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runVersion(opts versionOptions, dockerCli command.Cli) {
|
||||
func runVersion(opts versionOptions) {
|
||||
if opts.short {
|
||||
fmt.Fprintln(dockerCli.Out(), strings.TrimPrefix(internal.Version, "v"))
|
||||
fmt.Println(strings.TrimPrefix(internal.Version, "v"))
|
||||
return
|
||||
}
|
||||
if opts.format == formatter.JSON {
|
||||
fmt.Fprintf(dockerCli.Out(), "{\"version\":%q}\n", internal.Version)
|
||||
fmt.Printf("{\"version\":%q}\n", internal.Version)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), "Docker Compose version", internal.Version)
|
||||
fmt.Println("Docker Compose version", internal.Version)
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type vizOptions struct {
|
||||
*ProjectOptions
|
||||
includeNetworks bool
|
||||
includePorts bool
|
||||
includeImageName bool
|
||||
indentationStr string
|
||||
}
|
||||
|
||||
func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := vizOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
var indentationSize int
|
||||
var useSpaces bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "viz [OPTIONS]",
|
||||
Short: "EXPERIMENTAL - Generate a graphviz graph from your compose file",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
var err error
|
||||
opts.indentationStr, err = preferredIndentationStr(indentationSize, useSpaces)
|
||||
return err
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runViz(ctx, dockerCli, backend, &opts)
|
||||
}),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.includePorts, "ports", false, "Include service's exposed ports in output graph")
|
||||
cmd.Flags().BoolVar(&opts.includeNetworks, "networks", false, "Include service's attached networks in output graph")
|
||||
cmd.Flags().BoolVar(&opts.includeImageName, "image", false, "Include service's image name in output graph")
|
||||
cmd.Flags().IntVar(&indentationSize, "indentation-size", 1, "Number of tabs or spaces to use for indentation")
|
||||
cmd.Flags().BoolVar(&useSpaces, "spaces", false, "If given, space character ' ' will be used to indent,\notherwise tab character '\\t' will be used")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
|
||||
project, err := opts.ToProject(dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build graph
|
||||
graphStr, _ := backend.Viz(ctx, project, api.VizOptions{
|
||||
IncludeNetworks: opts.includeNetworks,
|
||||
IncludePorts: opts.includePorts,
|
||||
IncludeImageName: opts.includeImageName,
|
||||
Indentation: opts.indentationStr,
|
||||
})
|
||||
|
||||
fmt.Println(graphStr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// preferredIndentationStr returns a single string given the indentation preference
|
||||
func preferredIndentationStr(size int, useSpace bool) (string, error) {
|
||||
if size < 0 {
|
||||
return "", fmt.Errorf("invalid indentation size: %d", size)
|
||||
}
|
||||
|
||||
indentationStr := "\t"
|
||||
if useSpace {
|
||||
indentationStr = " "
|
||||
}
|
||||
return strings.Repeat(indentationStr, size), nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPreferredIndentationStr(t *testing.T) {
|
||||
type args struct {
|
||||
size int
|
||||
useSpace bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should return '\\t\\t'",
|
||||
args: args{
|
||||
size: 2,
|
||||
useSpace: false,
|
||||
},
|
||||
want: "\t\t",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should return ' '",
|
||||
args: args{
|
||||
size: 4,
|
||||
useSpace: true,
|
||||
},
|
||||
want: " ",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should return ''",
|
||||
args: args{
|
||||
size: 0,
|
||||
useSpace: false,
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should return ''",
|
||||
args: args{
|
||||
size: 0,
|
||||
useSpace: true,
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should throw error because indentation size < 0",
|
||||
args: args{
|
||||
size: -1,
|
||||
useSpace: false,
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := preferredIndentationStr(tt.args.size, tt.args.useSpace)
|
||||
if tt.wantErr && assert.NotNilf(t, err, fmt.Sprintf("preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)) {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type waitOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
services []string
|
||||
|
||||
downProject bool
|
||||
}
|
||||
|
||||
func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := waitOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
var statusCode int64
|
||||
var err error
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
|
||||
Short: "Block until the first service container stops",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, services []string) error {
|
||||
opts.services = services
|
||||
statusCode, err = runWait(ctx, dockerCli, backend, &opts)
|
||||
return err
|
||||
}),
|
||||
PostRun: func(cmd *cobra.Command, args []string) {
|
||||
os.Exit(int(statusCode))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.downProject, "down-project", false, "Drops project when the first container stops")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) {
|
||||
_, name, err := opts.projectOrName(dockerCli)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return backend.Wait(ctx, name, api.WaitOptions{
|
||||
Services: opts.services,
|
||||
DownProjectOnContainerExit: opts.downProject,
|
||||
})
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/internal/locker"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type watchOptions struct {
|
||||
*ProjectOptions
|
||||
quiet bool
|
||||
noUp bool
|
||||
}
|
||||
|
||||
func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
watchOpts := watchOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
buildOpts := buildOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "watch [SERVICE...]",
|
||||
Short: "Watch build context for service and rebuild/refresh containers when files are updated",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}),
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
if cmd.Parent().Name() == "alpha" {
|
||||
logrus.Warn("watch command is now available as a top level command")
|
||||
}
|
||||
return runWatch(ctx, dockerCli, backend, watchOpts, buildOpts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&watchOpts.quiet, "quiet", false, "hide build output")
|
||||
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
|
||||
project, err := watchOpts.ToProject(dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := applyPlatforms(project, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
build, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validation done -- ensure we have the lockfile for this project before doing work
|
||||
l, err := locker.NewPidfile(project.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot take exclusive lock for project %q: %w", project.Name, err)
|
||||
}
|
||||
if err := l.Lock(); err != nil {
|
||||
return fmt.Errorf("cannot take exclusive lock for project %q: %w", project.Name, err)
|
||||
}
|
||||
|
||||
if !watchOpts.noUp {
|
||||
for index, service := range project.Services {
|
||||
if service.Build != nil && service.Develop != nil {
|
||||
service.PullPolicy = types.PullPolicyBuild
|
||||
}
|
||||
project.Services[index] = service
|
||||
}
|
||||
upOpts := api.UpOptions{
|
||||
Create: api.CreateOptions{
|
||||
Build: &build,
|
||||
Services: services,
|
||||
RemoveOrphans: false,
|
||||
Recreate: api.RecreateDiverged,
|
||||
RecreateDependencies: api.RecreateNever,
|
||||
Inherit: true,
|
||||
QuietPull: watchOpts.quiet,
|
||||
},
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: nil,
|
||||
CascadeStop: false,
|
||||
Services: services,
|
||||
},
|
||||
}
|
||||
if err := backend.Up(ctx, project, upOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return backend.Watch(ctx, project, services, api.WatchOptions{
|
||||
Build: build,
|
||||
})
|
||||
}
|
||||
@@ -18,10 +18,10 @@ package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var names = []string{
|
||||
@@ -47,20 +47,20 @@ const (
|
||||
)
|
||||
|
||||
// SetANSIMode configure formatter for colored output on ANSI-compliant console
|
||||
func SetANSIMode(streams api.Streams, ansi string) {
|
||||
if !useAnsi(streams, ansi) {
|
||||
func SetANSIMode(ansi string) {
|
||||
if !useAnsi(ansi) {
|
||||
nextColor = func() colorFunc {
|
||||
return monochrome
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func useAnsi(streams api.Streams, ansi string) bool {
|
||||
func useAnsi(ansi string) bool {
|
||||
switch ansi {
|
||||
case Always:
|
||||
return true
|
||||
case Auto:
|
||||
return streams.Out().IsTerminal()
|
||||
return isatty.IsTerminal(os.Stdout.Fd())
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -87,34 +87,38 @@ func makeColorFunc(code string) colorFunc {
|
||||
}
|
||||
|
||||
var nextColor = rainbowColor
|
||||
var rainbow []colorFunc
|
||||
var currentIndex = 0
|
||||
var mutex sync.Mutex
|
||||
|
||||
func rainbowColor() colorFunc {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
result := rainbow[currentIndex]
|
||||
currentIndex = (currentIndex + 1) % len(rainbow)
|
||||
return result
|
||||
return <-loop
|
||||
}
|
||||
|
||||
var loop = make(chan colorFunc)
|
||||
|
||||
func init() {
|
||||
colors := map[string]colorFunc{}
|
||||
for i, name := range names {
|
||||
colors[name] = makeColorFunc(strconv.Itoa(30 + i))
|
||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
|
||||
}
|
||||
rainbow = []colorFunc{
|
||||
colors["cyan"],
|
||||
colors["yellow"],
|
||||
colors["green"],
|
||||
colors["magenta"],
|
||||
colors["blue"],
|
||||
colors["intense_cyan"],
|
||||
colors["intense_yellow"],
|
||||
colors["intense_green"],
|
||||
colors["intense_magenta"],
|
||||
colors["intense_blue"],
|
||||
}
|
||||
|
||||
go func() {
|
||||
i := 0
|
||||
rainbow := []colorFunc{
|
||||
colors["cyan"],
|
||||
colors["yellow"],
|
||||
colors["green"],
|
||||
colors["magenta"],
|
||||
colors["blue"],
|
||||
colors["intense_cyan"],
|
||||
colors["intense_yellow"],
|
||||
colors["intense_green"],
|
||||
colors["intense_magenta"],
|
||||
colors["intense_blue"],
|
||||
}
|
||||
|
||||
for {
|
||||
loop <- rainbow[i]
|
||||
i = (i + 1) % len(rainbow)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -17,13 +17,10 @@
|
||||
package formatter
|
||||
|
||||
const (
|
||||
// JSON Print in JSON format
|
||||
// JSON is the constant for Json formats on list commands
|
||||
JSON = "json"
|
||||
// TemplateLegacyJSON the legacy json formatting value using go template
|
||||
TemplateLegacyJSON = "{{json.}}"
|
||||
// PRETTY is the constant for default formats on list commands
|
||||
// Deprecated: use TABLE
|
||||
PRETTY = "pretty"
|
||||
// TABLE Print output in table format with column headers (default)
|
||||
TABLE = "table"
|
||||
)
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultContainerTableFormat = "table {{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Service}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
nameHeader = "NAME"
|
||||
projectHeader = "PROJECT"
|
||||
serviceHeader = "SERVICE"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
)
|
||||
|
||||
// NewContainerFormat returns a Format for rendering using a Context
|
||||
func NewContainerFormat(source string, quiet bool, size bool) formatter.Format {
|
||||
switch source {
|
||||
case formatter.TableFormatKey, "": // table formatting is the default if none is set.
|
||||
if quiet {
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
format := defaultContainerTableFormat
|
||||
if size {
|
||||
format += `\t{{.Size}}`
|
||||
}
|
||||
return formatter.Format(format)
|
||||
case formatter.RawFormatKey:
|
||||
if quiet {
|
||||
return `container_id: {{.ID}}`
|
||||
}
|
||||
format := `container_id: {{.ID}}
|
||||
image: {{.Image}}
|
||||
command: {{.Command}}
|
||||
created_at: {{.CreatedAt}}
|
||||
state: {{- pad .State 1 0}}
|
||||
status: {{- pad .Status 1 0}}
|
||||
names: {{.Names}}
|
||||
labels: {{- pad .Labels 1 0}}
|
||||
ports: {{- pad .Ports 1 0}}
|
||||
`
|
||||
if size {
|
||||
format += `size: {{.Size}}\n`
|
||||
}
|
||||
return formatter.Format(format)
|
||||
default: // custom format
|
||||
if quiet {
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
return formatter.Format(source)
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerWrite renders the context for a list of containers
|
||||
func ContainerWrite(ctx formatter.Context, containers []api.ContainerSummary) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, container := range containers {
|
||||
err := format(&ContainerContext{trunc: ctx.Trunc, c: container})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(NewContainerContext(), render)
|
||||
}
|
||||
|
||||
// ContainerContext is a struct used for rendering a list of containers in a Go template.
|
||||
type ContainerContext struct {
|
||||
formatter.HeaderContext
|
||||
trunc bool
|
||||
c api.ContainerSummary
|
||||
|
||||
// FieldsUsed is used in the pre-processing step to detect which fields are
|
||||
// used in the template. It's currently only used to detect use of the .Size
|
||||
// field which (if used) automatically sets the '--size' option when making
|
||||
// the API call.
|
||||
FieldsUsed map[string]interface{}
|
||||
}
|
||||
|
||||
// NewContainerContext creates a new context for rendering containers
|
||||
func NewContainerContext() *ContainerContext {
|
||||
containerCtx := ContainerContext{}
|
||||
containerCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": formatter.ContainerIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Project": projectHeader,
|
||||
"Service": serviceHeader,
|
||||
"Image": formatter.ImageHeader,
|
||||
"Command": commandHeader,
|
||||
"CreatedAt": formatter.CreatedAtHeader,
|
||||
"RunningFor": runningForHeader,
|
||||
"Ports": formatter.PortsHeader,
|
||||
"State": formatter.StateHeader,
|
||||
"Status": formatter.StatusHeader,
|
||||
"Size": formatter.SizeHeader,
|
||||
"Labels": formatter.LabelsHeader,
|
||||
}
|
||||
return &containerCtx
|
||||
}
|
||||
|
||||
// MarshalJSON makes ContainerContext implement json.Marshaler
|
||||
func (c *ContainerContext) MarshalJSON() ([]byte, error) {
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
// ID returns the container's ID as a string. Depending on the `--no-trunc`
|
||||
// option being set, the full or truncated ID is returned.
|
||||
func (c *ContainerContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.c.ID)
|
||||
}
|
||||
return c.c.ID
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Name() string {
|
||||
return c.c.Name
|
||||
}
|
||||
|
||||
// Names returns a comma-separated string of the container's names, with their
|
||||
// slash (/) prefix stripped. Additional names for the container (related to the
|
||||
// legacy `--link` feature) are omitted.
|
||||
func (c *ContainerContext) Names() string {
|
||||
names := formatter.StripNamePrefix(c.c.Names)
|
||||
if c.trunc {
|
||||
for _, name := range names {
|
||||
if len(strings.Split(name, "/")) == 1 {
|
||||
names = []string{name}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(names, ",")
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Service() string {
|
||||
return c.c.Service
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Project() string {
|
||||
return c.c.Project
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Image() string {
|
||||
return c.c.Image
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Command() string {
|
||||
command := c.c.Command
|
||||
if c.trunc {
|
||||
command = formatter.Ellipsis(command, 20)
|
||||
}
|
||||
return strconv.Quote(command)
|
||||
}
|
||||
|
||||
func (c *ContainerContext) CreatedAt() string {
|
||||
return time.Unix(c.c.Created, 0).String()
|
||||
}
|
||||
|
||||
func (c *ContainerContext) RunningFor() string {
|
||||
createdAt := time.Unix(c.c.Created, 0)
|
||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
||||
}
|
||||
|
||||
func (c *ContainerContext) ExitCode() int {
|
||||
return c.c.ExitCode
|
||||
}
|
||||
|
||||
func (c *ContainerContext) State() string {
|
||||
return c.c.State
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Status() string {
|
||||
return c.c.Status
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Health() string {
|
||||
return c.c.Health
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Publishers() api.PortPublishers {
|
||||
return c.c.Publishers
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Ports() string {
|
||||
var ports []types.Port
|
||||
for _, publisher := range c.c.Publishers {
|
||||
ports = append(ports, types.Port{
|
||||
IP: publisher.URL,
|
||||
PrivatePort: uint16(publisher.TargetPort),
|
||||
PublicPort: uint16(publisher.PublishedPort),
|
||||
Type: publisher.Protocol,
|
||||
})
|
||||
}
|
||||
return formatter.DisplayablePorts(ports)
|
||||
}
|
||||
|
||||
// Labels returns a comma-separated string of labels present on the container.
|
||||
func (c *ContainerContext) Labels() string {
|
||||
if c.c.Labels == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var joinLabels []string
|
||||
for k, v := range c.c.Labels {
|
||||
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
// Label returns the value of the label with the given name or an empty string
|
||||
// if the given label does not exist.
|
||||
func (c *ContainerContext) Label(name string) string {
|
||||
if c.c.Labels == nil {
|
||||
return ""
|
||||
}
|
||||
return c.c.Labels[name]
|
||||
}
|
||||
|
||||
// Mounts returns a comma-separated string of mount names present on the container.
|
||||
// If the trunc option is set, names can be truncated (ellipsized).
|
||||
func (c *ContainerContext) Mounts() string {
|
||||
var mounts []string
|
||||
for _, name := range c.c.Mounts {
|
||||
if c.trunc {
|
||||
name = formatter.Ellipsis(name, 15)
|
||||
}
|
||||
mounts = append(mounts, name)
|
||||
}
|
||||
return strings.Join(mounts, ",")
|
||||
}
|
||||
|
||||
// LocalVolumes returns the number of volumes using the "local" volume driver.
|
||||
func (c *ContainerContext) LocalVolumes() string {
|
||||
return fmt.Sprintf("%d", c.c.LocalVolumes)
|
||||
}
|
||||
|
||||
// Networks returns a comma-separated string of networks that the container is
|
||||
// attached to.
|
||||
func (c *ContainerContext) Networks() string {
|
||||
return strings.Join(c.c.Networks, ",")
|
||||
}
|
||||
|
||||
// Size returns the container's size and virtual size (e.g. "2B (virtual 21.5MB)")
|
||||
func (c *ContainerContext) Size() string {
|
||||
if c.FieldsUsed == nil {
|
||||
c.FieldsUsed = map[string]interface{}{}
|
||||
}
|
||||
c.FieldsUsed["Size"] = struct{}{}
|
||||
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
|
||||
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
|
||||
|
||||
sf := srw
|
||||
if c.c.SizeRootFs > 0 {
|
||||
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
|
||||
}
|
||||
return sf
|
||||
}
|
||||
@@ -23,12 +23,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Print prints formatted lists in different formats
|
||||
func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error {
|
||||
switch strings.ToLower(format) {
|
||||
case TABLE, PRETTY, "":
|
||||
case PRETTY, "":
|
||||
return PrintPrettySection(outWriter, writerFn, headers...)
|
||||
case TemplateLegacyJSON:
|
||||
switch reflect.TypeOf(toJSON).Kind() {
|
||||
@@ -65,7 +67,7 @@ func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func
|
||||
_, _ = fmt.Fprintln(outWriter, outJSON)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("format value %q could not be parsed: %w", format, api.ErrParsingFailed)
|
||||
return errors.Wrapf(api.ErrParsingFailed, "format value %q could not be parsed", format)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
type testStruct struct {
|
||||
@@ -72,7 +71,3 @@ func TestPrint(t *testing.T) {
|
||||
{"Name":"myName2","Status":"myStatus2"}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestColorsGoroutinesLeak(t *testing.T) {
|
||||
goleak.VerifyNone(t)
|
||||
}
|
||||
|
||||
@@ -23,35 +23,19 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
|
||||
// LogConsumer consume logs from services and format them
|
||||
type logConsumer struct {
|
||||
ctx context.Context
|
||||
presenters sync.Map // map[string]*presenter
|
||||
width int
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
color bool
|
||||
prefix bool
|
||||
timestamp bool
|
||||
}
|
||||
|
||||
// NewLogConsumer creates a new LogConsumer
|
||||
func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix, timestamp bool) api.LogConsumer {
|
||||
func NewLogConsumer(ctx context.Context, w io.Writer, color bool, prefix bool) api.LogConsumer {
|
||||
return &logConsumer{
|
||||
ctx: ctx,
|
||||
presenters: sync.Map{},
|
||||
width: 0,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
writer: w,
|
||||
color: color,
|
||||
prefix: prefix,
|
||||
timestamp: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,34 +73,20 @@ func (l *logConsumer) getPresenter(container string) *presenter {
|
||||
}
|
||||
|
||||
// Log formats a log message as received from name/container
|
||||
func (l *logConsumer) Log(container, message string) {
|
||||
l.write(l.stdout, container, message)
|
||||
}
|
||||
|
||||
// Log formats a log message as received from name/container
|
||||
func (l *logConsumer) Err(container, message string) {
|
||||
l.write(l.stderr, container, message)
|
||||
}
|
||||
|
||||
func (l *logConsumer) write(w io.Writer, container, message string) {
|
||||
func (l *logConsumer) Log(container, service, message string) {
|
||||
if l.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
p := l.getPresenter(container)
|
||||
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
if l.timestamp {
|
||||
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
|
||||
}
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Status(container, msg string) {
|
||||
p := l.getPresenter(container)
|
||||
s := p.colors(fmt.Sprintf("%s %s\n", container, msg))
|
||||
l.stdout.Write([]byte(s)) //nolint:errcheck
|
||||
l.writer.Write([]byte(s)) // nolint:errcheck
|
||||
}
|
||||
|
||||
func (l *logConsumer) computeWidth() {
|
||||
@@ -131,6 +101,16 @@ func (l *logConsumer) computeWidth() {
|
||||
l.width = width + 1
|
||||
}
|
||||
|
||||
// LogConsumer consume logs from services and format them
|
||||
type logConsumer struct {
|
||||
ctx context.Context
|
||||
presenters sync.Map // map[string]*presenter
|
||||
width int
|
||||
writer io.Writer
|
||||
color bool
|
||||
prefix bool
|
||||
}
|
||||
|
||||
type presenter struct {
|
||||
colors colorFunc
|
||||
name string
|
||||
|
||||
33
cmd/main.go
33
cmd/main.go
@@ -23,39 +23,30 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/cmdtrace"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compatibility"
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
backend := compose.NewComposeService(dockerCli)
|
||||
cmd := commands.RootCommand(dockerCli, backend)
|
||||
lazyInit := api.NewServiceProxy()
|
||||
cmd := commands.RootCommand(dockerCli, lazyInit)
|
||||
originalPreRun := cmd.PersistentPreRunE
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
// initialize the dockerCli instance
|
||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
// compose-specific initialization
|
||||
dockerCliPostInitialize(dockerCli)
|
||||
|
||||
// TODO(milas): add an env var to enable logging from the
|
||||
// OTel components for debugging purposes
|
||||
_ = cmdtrace.Setup(cmd, dockerCli, os.Args[1:])
|
||||
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli))
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
|
||||
return dockercli.StatusError{
|
||||
StatusCode: compose.CommandSyntaxFailure.ExitCode,
|
||||
@@ -71,22 +62,6 @@ func pluginMain() {
|
||||
})
|
||||
}
|
||||
|
||||
// dockerCliPostInitialize performs Compose-specific configuration for the
|
||||
// command.Cli instance provided by the plugin.Run() initialization.
|
||||
//
|
||||
// NOTE: This must be called AFTER plugin.PersistentPreRunE.
|
||||
func dockerCliPostInitialize(dockerCli command.Cli) {
|
||||
// HACK(milas): remove once docker/cli#4574 is merged; for now,
|
||||
// set it in a rather roundabout way by grabbing the underlying
|
||||
// concrete client and manually invoking an option on it
|
||||
_ = dockerCli.Apply(func(cli *command.DockerCli) error {
|
||||
if mobyClient, ok := cli.Client().(*client.Client); ok {
|
||||
_ = client.WithUserAgent("compose/" + internal.Version)(mobyClient)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
if plugin.RunningStandalone() {
|
||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||
|
||||
21
codecov.yml
21
codecov.yml
@@ -1,21 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
target: auto
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
comment:
|
||||
require_changes: true
|
||||
|
||||
ignore:
|
||||
- "packaging"
|
||||
- "docs"
|
||||
- "bin"
|
||||
- "e2e"
|
||||
- "pkg/e2e"
|
||||
- "**/*_test.go"
|
||||
148
docker-bake.hcl
148
docker-bake.hcl
@@ -1,148 +0,0 @@
|
||||
// Copyright 2022 Docker Compose CLI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
variable "GO_VERSION" {
|
||||
# default ARG value set in Dockerfile
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "BUILD_TAGS" {
|
||||
default = "e2e"
|
||||
}
|
||||
|
||||
variable "DOCS_FORMATS" {
|
||||
default = "md,yaml"
|
||||
}
|
||||
|
||||
# Defines the output folder to override the default behavior.
|
||||
# See Makefile for details, this is generally only useful for
|
||||
# the packaging scripts and care should be taken to not break
|
||||
# them.
|
||||
variable "DESTDIR" {
|
||||
default = ""
|
||||
}
|
||||
function "outdir" {
|
||||
params = [defaultdir]
|
||||
result = DESTDIR != "" ? DESTDIR : "${defaultdir}"
|
||||
}
|
||||
|
||||
# Special target: https://github.com/docker/metadata-action#bake-definition
|
||||
target "meta-helper" {}
|
||||
|
||||
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 "vendor-update" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "test" {
|
||||
inherits = ["_common"]
|
||||
target = "test-coverage"
|
||||
output = [outdir("./bin/coverage/unit")]
|
||||
}
|
||||
|
||||
target "binary-with-coverage" {
|
||||
inherits = ["_common"]
|
||||
target = "binary"
|
||||
args = {
|
||||
BUILD_FLAGS = "-cover -covermode=atomic"
|
||||
}
|
||||
output = [outdir("./bin/build")]
|
||||
platforms = ["local"]
|
||||
}
|
||||
|
||||
target "binary" {
|
||||
inherits = ["_common"]
|
||||
target = "binary"
|
||||
output = [outdir("./bin/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 = [outdir("./bin/release")]
|
||||
}
|
||||
|
||||
target "docs-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "docs-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "docs-update" {
|
||||
inherits = ["_common"]
|
||||
target = "docs-update"
|
||||
output = ["./docs"]
|
||||
}
|
||||
|
||||
target "image-cross" {
|
||||
inherits = ["meta-helper", "binary-cross"]
|
||||
output = ["type=image"]
|
||||
}
|
||||
57
docs/docs.Dockerfile
Normal file
57
docs/docs.Dockerfile
Normal file
@@ -0,0 +1,57 @@
|
||||
# syntax=docker/dockerfile:1.3-labs
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.18.2
|
||||
ARG FORMATS=md,yaml
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
|
||||
WORKDIR /src
|
||||
RUN --mount=target=. \
|
||||
--mount=target=/root/.cache,type=cache \
|
||||
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} alpine AS gen
|
||||
RUN apk add --no-cache rsync git
|
||||
WORKDIR /src
|
||||
COPY --from=docsgen /out/docsgen /usr/bin
|
||||
ARG FORMATS
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
docsgen --formats "$FORMATS" --source "docs/reference"
|
||||
mkdir /out
|
||||
cp -r docs/reference /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS update
|
||||
COPY --from=gen /out /out
|
||||
|
||||
FROM gen AS validate
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf docs/reference/*
|
||||
cp -rf /out/* ./docs/
|
||||
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
|
||||
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
|
||||
git status --porcelain -- docs/reference
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
@@ -1,68 +1,61 @@
|
||||
# docker compose
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Define and run multi-container applications with Docker.
|
||||
Docker Compose
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:--------------------------------|:-----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container. |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service. |
|
||||
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||
| [`events`](compose_events.md) | Receive real time events from containers. |
|
||||
| [`exec`](compose_exec.md) | Execute a command in a running container. |
|
||||
| [`images`](compose_images.md) | List images used by the created containers |
|
||||
| [`kill`](compose_kill.md) | Force stop service containers. |
|
||||
| [`logs`](compose_logs.md) | View output from containers |
|
||||
| [`ls`](compose_ls.md) | List running compose projects |
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding. |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart service containers |
|
||||
| [`rm`](compose_rm.md) | Removes stopped service containers |
|
||||
| [`run`](compose_run.md) | Run a one-off command on a service. |
|
||||
| [`scale`](compose_scale.md) | Scale services |
|
||||
| [`start`](compose_start.md) | Start services |
|
||||
| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics |
|
||||
| [`stop`](compose_stop.md) | Stop services |
|
||||
| [`top`](compose_top.md) | Display the running processes |
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
| [`wait`](compose_wait.md) | Block until the first service container stops |
|
||||
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`convert`](compose_convert.md) | Converts the compose file to platform's canonical format |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service. |
|
||||
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||
| [`events`](compose_events.md) | Receive real time events from containers. |
|
||||
| [`exec`](compose_exec.md) | Execute a command in a running container. |
|
||||
| [`images`](compose_images.md) | List images used by the created containers |
|
||||
| [`kill`](compose_kill.md) | Force stop service containers. |
|
||||
| [`logs`](compose_logs.md) | View output from containers |
|
||||
| [`ls`](compose_ls.md) | List running compose projects |
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding. |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart containers |
|
||||
| [`rm`](compose_rm.md) | Removes stopped service containers |
|
||||
| [`run`](compose_run.md) | Run a one-off command on a service. |
|
||||
| [`start`](compose_start.md) | Start services |
|
||||
| [`stop`](compose_stop.md) | Stop services |
|
||||
| [`top`](compose_top.md) | Display the running processes |
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------------|:--------------|:--------|:----------------------------------------------------------------------------------------------------|
|
||||
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
|
||||
| `--compatibility` | | | Run compose in backward compatibility mode |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--env-file` | `stringArray` | | Specify an alternate environment file. |
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory<br>(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
|
||||
| `--compatibility` | | | Run compose in backward compatibility mode |
|
||||
| `--env-file` | `string` | | Specify an alternate environment file. |
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
You can use the compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
You can use compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
### Use `-f` to specify name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
@@ -121,19 +114,12 @@ $ docker compose -f ~/sandbox/rails/compose.yaml pull db
|
||||
|
||||
### Use `-p` to specify a project name
|
||||
|
||||
Each configuration has a project name. Compose sets the project name using
|
||||
the following mechanisms, in order of precedence:
|
||||
- The `-p` command line flag
|
||||
- The `COMPOSE_PROJECT_NAME` environment variable
|
||||
- The top level `name:` variable from the config file (or the last `name:`
|
||||
from a series of config files specified using `-f`)
|
||||
- The `basename` of the project directory containing the config file (or
|
||||
containing the first config file specified using `-f`)
|
||||
- The `basename` of the current directory if no config file is specified
|
||||
Project names must contain only lowercase letters, decimal digits, dashes,
|
||||
and underscores, and must begin with a lowercase letter or decimal digit. If
|
||||
the `basename` of the project directory or current directory violates this
|
||||
constraint, you must use one of the other mechanisms.
|
||||
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t
|
||||
specify the flag, Compose uses the current directory name.
|
||||
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
||||
|
||||
Most compose subcommand can be ran without a compose file, just passing
|
||||
project name to retrieve the relevant resources.
|
||||
|
||||
```console
|
||||
$ docker compose -p my_project ps -a
|
||||
@@ -148,55 +134,21 @@ demo_1 | 64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.095 ms
|
||||
### Use profiles to enable optional services
|
||||
|
||||
Use `--profile` to specify one or more active profiles
|
||||
Calling `docker compose --profile frontend up` starts the services with the profile `frontend` and services
|
||||
Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
|
||||
without any specified profiles.
|
||||
You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` is enabled.
|
||||
You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` will be enabled.
|
||||
|
||||
Profiles can also be set by `COMPOSE_PROFILES` environment variable.
|
||||
|
||||
### Configuring parallelism
|
||||
|
||||
Use `--parallel` to specify the maximum level of parallelism for concurrent engine calls.
|
||||
Calling `docker compose --parallel 1 pull` pulls the pullable images defined in the Compose file
|
||||
one at a time. This can also be used to control build concurrency.
|
||||
|
||||
Parallelism can also be set by the `COMPOSE_PARALLEL_LIMIT` environment variable.
|
||||
|
||||
### Set up environment variables
|
||||
|
||||
You can set environment variables for various docker compose options, including the `-f`, `-p` and `--profiles` flags.
|
||||
|
||||
Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f` flag,
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same as the `-p` flag,
|
||||
`COMPOSE_PROFILES` environment variable is equivalent to the `--profiles` flag
|
||||
and `COMPOSE_PARALLEL_LIMIT` does the same as the `--parallel` flag.
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same for to the `-p` flag,
|
||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||
|
||||
If flags are explicitly set on the command line, the associated environment variable is ignored.
|
||||
If flags are explicitly set on command line, associated environment variable is ignored
|
||||
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` stops docker compose from detecting orphaned
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
|
||||
### Use Dry Run mode to test your command
|
||||
|
||||
Use `--dry-run` flag to test a command without changing your application stack state.
|
||||
Dry Run mode shows you all the steps Compose applies when executing a command, for example:
|
||||
```console
|
||||
$ docker compose --dry-run up --build -d
|
||||
[+] Pulling 1/1
|
||||
✔ DRY-RUN MODE - db Pulled 0.9s
|
||||
[+] Running 10/8
|
||||
✔ DRY-RUN MODE - build service backend 0.0s
|
||||
✔ DRY-RUN MODE - ==> ==> writing image dryRun-754a08ddf8bcb1cf22f310f09206dd783d42f7dd 0.0s
|
||||
✔ DRY-RUN MODE - ==> ==> naming to nginx-golang-mysql-backend 0.0s
|
||||
✔ DRY-RUN MODE - Network nginx-golang-mysql_default Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-db-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-backend-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-proxy-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-db-1 Healthy 0.5s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-backend-1 Started 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-proxy-1 Started Started
|
||||
```
|
||||
From the example above, you can see that the first step is to pull the image defined by `db` service, then build the `backend` service.
|
||||
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
|
||||
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# docker compose alpha
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Experimental commands
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:----------------------------------|:-----------------------------------------------------------------------------------------------------|
|
||||
| [`viz`](compose_alpha_viz.md) | EXPERIMENTAL - Generate a graphviz graph from your compose file |
|
||||
| [`watch`](compose_alpha_watch.md) | EXPERIMENTAL - Watch build context for service and rebuild/refresh containers when files are updated |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# docker compose alpha dry-run
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Dry run command allows you to test a command without applying changes.
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# docker compose alpha publish
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Publish compose application
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) |
|
||||
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# docker compose alpha scale
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Scale services.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--no-deps` | | | Don't start linked services |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# docker compose alpha viz
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
EXPERIMENTAL - Generate a graphviz graph from your compose file
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--image` | | | Include service's image name in output graph |
|
||||
| `--indentation-size` | `int` | `1` | Number of tabs or spaces to use for indentation |
|
||||
| `--networks` | | | Include service's attached networks in output graph |
|
||||
| `--ports` | | | Include service's exposed ports in output graph |
|
||||
| `--spaces` | | | If given, space character ' ' will be used to indent,<br>otherwise tab character '\t' will be used |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# docker compose alpha watch
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Watch build context for service and rebuild/refresh containers when files are updated
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:----------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--no-up` | | | Do not build & start services before watching |
|
||||
| `--quiet` | | | hide build output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# docker compose attach
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Attach local standard input, output, and error streams to a service's running container.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:----------------------------------------------------------|
|
||||
| `--detach-keys` | `string` | | Override the key sequence for detaching from a container. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `--no-stdin` | | | Do not attach STDIN |
|
||||
| `--sig-proxy` | | | Proxy all received signals to the process |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,18 +5,14 @@ Build or rebuild services
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
|
||||
| `--builder` | `string` | | Set builder to use. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--pull` | | | Always attempt to pull a newer version of the image. |
|
||||
| `--push` | | | Push service images. |
|
||||
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
| `--with-dependencies` | | | Also build dependencies (transitively). |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
|
||||
| `--pull` | | | Always attempt to pull a newer version of the image. |
|
||||
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# docker compose convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Parse, resolve and render compose file in canonical format
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose config`, `docker compose convert`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | | | Print the image names, one per line. |
|
||||
| `--no-consistency` | | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-interpolate` | | | Don't interpolate environment variables. |
|
||||
| `--no-normalize` | | | Don't normalize compose model. |
|
||||
| `--no-path-resolution` | | | Don't resolve file paths. |
|
||||
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
|
||||
| `--profiles` | | | Print the profile names, one per line. |
|
||||
| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
|
||||
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||
| `--services` | | | Print the service names, one per line. |
|
||||
| `--volumes` | | | Print the volume names, one per line. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
`docker compose config` renders the actual data model to be applied on the Docker Engine.
|
||||
It merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
35
docs/reference/compose_convert.md
Normal file
35
docs/reference/compose_convert.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# docker compose convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Converts the compose file to platform's canonical format
|
||||
|
||||
### Aliases
|
||||
|
||||
`convert`, `config`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | | | Print the image names, one per line. |
|
||||
| `--no-interpolate` | | | Don't interpolate environment variables. |
|
||||
| `--no-normalize` | | | Don't normalize compose model. |
|
||||
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
|
||||
| `--profiles` | | | Print the profile names, one per line. |
|
||||
| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
|
||||
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||
| `--services` | | | Print the service names, one per line. |
|
||||
| `--volumes` | | | Print the volume names, one per line. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||
fully defined Compose model.
|
||||
|
||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||
@@ -5,12 +5,11 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:------|:--------|:--------------------------------------------------------|
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | Index of the container if there are multiple instances of a service . |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,16 +5,12 @@ Creates containers for a service.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:--------------|:---------|:----------------------------------------------------------------------------------------------|
|
||||
| `--build` | | | Build images before starting containers. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
|
||||
| `--no-build` | | | Don't build an image, even if it's policy. |
|
||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--build` | | | Build images before starting containers. |
|
||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
|
||||
| `--no-build` | | | Don't build an image, even if it's missing. |
|
||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,13 +5,12 @@ Stop and remove containers, networks
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--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` | `0` | 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. |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--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. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -22,12 +21,12 @@ Stops containers and removes containers, networks, volumes, and images created b
|
||||
|
||||
By default, the only things removed are:
|
||||
|
||||
- Containers for services defined in the Compose file.
|
||||
- Networks defined in the networks section of the Compose file.
|
||||
- The default network, if one is used.
|
||||
- Containers for services defined in the Compose file
|
||||
- Networks defined in the networks section of the Compose file
|
||||
- The default network, if one is used
|
||||
|
||||
Networks and volumes defined as external are never removed.
|
||||
|
||||
Anonymous volumes are not removed by default. However, as they don’t have a stable name, they are not automatically
|
||||
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.
|
||||
|
||||
@@ -5,10 +5,9 @@ Receive real time events from containers.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--json` | | | Output events as a stream of json objects |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--json` | | | Output events as a stream of json objects |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -33,4 +32,4 @@ With the `--json` flag, a json object is printed one per line with the format:
|
||||
}
|
||||
```
|
||||
|
||||
The events that can be received using this can be seen [here](https://docs.docker.com/engine/reference/commandline/system_events/#object-types).
|
||||
The events that can be received using this can be seen [here](https://docs.docker.com/engine/reference/commandline/events/#object-types).
|
||||
|
||||
@@ -5,16 +5,15 @@ Execute a command in a running container.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
|
||||
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | | | Give extended privileges to the process. |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | | | Give extended privileges to the process. |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -23,5 +22,5 @@ Execute a command in a running container.
|
||||
|
||||
This is the equivalent of `docker exec` targeting a Compose service.
|
||||
|
||||
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
|
||||
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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user