Compare commits

..

2 Commits

Author SHA1 Message Date
Nicolas De Loof
f06caeb844 oras doesn't prepend index.docker.io to repository ref
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2023-07-07 09:26:13 +02:00
Nicolas De Loof
49d1bc7524 introduce push --repository
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2023-07-05 09:06:41 +02:00
356 changed files with 5755 additions and 15003 deletions

44
.github/SECURITY.md vendored
View File

@@ -1,44 +0,0 @@
# Security Policy
The maintainers of Docker Compose take security seriously. If you discover
a security issue, please bring it to their attention right away!
## Reporting a Vulnerability
Please **DO NOT** file a public issue, instead send your report privately
to [security@docker.com](mailto:security@docker.com).
Reporter(s) can expect a response within 72 hours, acknowledging the issue was
received.
## Review Process
After receiving the report, an initial triage and technical analysis is
performed to confirm the report and determine its scope. We may request
additional information in this stage of the process.
Once a reviewer has confirmed the relevance of the report, a draft security
advisory will be created on GitHub. The draft advisory will be used to discuss
the issue with maintainers, the reporter(s), and where applicable, other
affected parties under embargo.
If the vulnerability is accepted, a timeline for developing a patch, public
disclosure, and patch release will be determined. If there is an embargo period
on public disclosure before the patch release, the reporter(s) are expected to
participate in the discussion of the timeline and abide by agreed upon dates
for public disclosure.
## Accreditation
Security reports are greatly appreciated and we will publicly thank you,
although we will keep your name confidential if you request it. We also like to
send gifts - if you're into swag, make sure to let us know. We do not currently
offer a paid security bounty program at this time.
## Supported Versions
This project docs not provide long-term supported versions, and only the current
release and `main` branch are actively maintained. Docker Compose v1, and the
corresponding [v1 branch](https://github.com/docker/compose/tree/v1) reached
EOL and are no longer supported.

View File

@@ -19,5 +19,6 @@ updates:
- 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" ]
# OTEL dependencies should be upgraded in sync with engine, cli, buildkit and buildx projects
- dependency-name: "go.opentelemetry.io/*"
- 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"]

View File

@@ -7,7 +7,7 @@ concurrency:
on:
push:
branches:
- 'main'
- 'v2'
tags:
- 'v*'
pull_request:
@@ -18,6 +18,9 @@ on:
required: false
default: "false"
env:
DOCKER_CLI_VERSION: "20.10.17"
permissions:
contents: read # to fetch code (actions/checkout)
@@ -29,7 +32,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Create matrix
id: platforms
@@ -53,7 +56,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
@@ -78,7 +81,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
@@ -96,10 +99,10 @@ jobs:
*.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: compose-${{ env.PLATFORM_PAIR }}
path: ./bin/release
name: compose
path: ./bin/release/*
if-no-files-found: error
test:
@@ -107,7 +110,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
@@ -121,17 +124,12 @@ jobs:
*.cache-to=type=gha,scope=test
-
name: Gather coverage data
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: coverage-data-unit
path: bin/coverage/unit/
if-no-files-found: error
-
name: Unit Test Summary
uses: test-summary/action@v2
with:
paths: bin/coverage/unit/report.xml
if: always()
e2e:
runs-on: ubuntu-latest
strategy:
@@ -140,39 +138,26 @@ jobs:
mode:
- plugin
- standalone
engine:
- 24.0.9
- 25.0.5
- 26.1.4
- 27.3.0
- cucumber
steps:
- name: Prepare
run: |
mode=${{ matrix.mode }}
engine=${{ matrix.engine }}
echo "MODE_ENGINE_PAIR=${mode}-${engine}" >> $GITHUB_ENV
-
name: Checkout
uses: actions/checkout@v4
- name: Install Docker ${{ matrix.engine }}
run: |
sudo systemctl stop docker.service
sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-ce-rootless-extras docker-buildx-plugin
sudo apt-get install curl
curl -fsSL https://test.docker.com -o get-docker.sh
sudo sh ./get-docker.sh --version ${{ matrix.engine }}
- name: Check Docker Version
run: docker --version
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Set up Go
uses: actions/setup-go@v5
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
@@ -201,9 +186,9 @@ jobs:
-
name: Gather coverage data
if: ${{ matrix.mode == 'plugin' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: coverage-data-e2e-${{ env.MODE_ENGINE_PAIR }}
name: coverage-data-e2e
path: bin/coverage/e2e/
if-no-files-found: error
-
@@ -213,43 +198,41 @@ jobs:
rm -f /usr/local/bin/docker-compose
cp bin/build/docker-compose /usr/local/bin
make e2e-compose-standalone
-
name: e2e Test Summary
uses: test-summary/action@v2
with:
paths: /tmp/report/report.xml
if: always()
-
name: Run cucumber tests
if: ${{ matrix.mode == 'cucumber'}}
run: |
make test-cucumber
coverage:
runs-on: ubuntu-latest
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@v4
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'
check-latest: true
- name: Download unit test coverage
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: coverage-data-unit
path: coverage/unit
merge-multiple: true
- name: Download E2E test coverage
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
pattern: coverage-data-e2e-*
name: coverage-data-e2e
path: coverage/e2e
merge-multiple: true
- 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@v4
uses: actions/upload-artifact@v3
with:
name: go-covdata-txt
path: ./coverage.txt
@@ -269,17 +252,16 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
pattern: compose-*
path: ./bin/release
merge-multiple: true
name: compose
path: bin/release
-
name: Create checksums
working-directory: ./bin/release
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
@@ -287,11 +269,11 @@ jobs:
cat checksums.txt | while read sum file; do echo "$sum $file" > ${file#\*}.sha256; done
-
name: License
run: cp packaging/* ./bin/release/
run: cp packaging/* bin/release/
-
name: List artifacts
run: |
tree -nh ./bin/release
tree -nh bin/release
-
name: Check artifacts
run: |
@@ -301,7 +283,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/v')
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0
with:
artifacts: ./bin/release/*
artifacts: bin/release/*
generateReleaseNotes: true
draft: true
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,51 +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
# Default to 'contents: read', which grants actions to read commits.
#
# If any permission is set, any permission not included in the list is
# implicitly set to "none".
#
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: read
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-latest
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

56
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Docs
on:
release:
types: [published]
permissions: {}
jobs:
open-pr:
permissions:
contents: write # to create branch (peter-evans/create-pull-request)
pull-requests: write # to create a PR (peter-evans/create-pull-request)
runs-on: ubuntu-latest
steps:
-
name: Checkout docs repo
uses: actions/checkout@v3
with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
repository: docker/docs
ref: main
-
name: Prepare
run: |
rm -rf ./_data/compose-cli/*
-
name: Build
uses: docker/build-push-action@v3
with:
context: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
target: docs-reference
outputs: ./_data/compose-cli
-
name: Update compose_version in _config.yml
run: |
sed -i "s|^compose_version\:.*|compose_version\: \"${{ github.event.release.name }}\"|g" _config.yml
cat _config.yml | yq .compose_version
-
name: Commit changes
run: |
git add -A .
-
name: Create PR on docs repo
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
push-to-fork: docker-tools-robot/docker.github.io
commit-message: Update Compose reference API to ${{ github.event.release.name }}
signoff: true
branch: dispatch/compose-api-reference-${{ github.event.release.name }}
delete-branch: true
title: Update Compose reference API to ${{ github.event.release.name }}
body: |
Update the Compose reference API documentation to keep in sync with the latest release `${{ github.event.release.name }}`
draft: false

View File

@@ -7,7 +7,7 @@ concurrency:
on:
push:
branches:
- 'main'
- 'v2'
tags:
- 'v*'
@@ -31,9 +31,9 @@ jobs:
env:
GO111MODULE: "on"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: actions/setup-go@v5
- uses: actions/setup-go@v3
with:
go-version-file: go.mod
cache: true
@@ -81,7 +81,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2

View File

@@ -5,7 +5,7 @@ on:
schedule:
- cron: '44 9 * * 4'
push:
branches: [ "main" ]
branches: [ "v2" ]
# Declare default permissions as read only.
permissions: read-all

View File

@@ -1,33 +0,0 @@
name: 'Close stale issues'
# Default to 'contents: read', which grants actions to read commits.
#
# If any permission is set, any permission not included in the list is
# implicitly set to "none".
#
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: read
on:
schedule:
- cron: '0 0 * * 0,3' # at midnight UTC every Sunday and Wednesday
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
days-before-issue-stale: 150 # marks stale after 5 months
days-before-issue-close: 30 # closes 1 month after being marked with no action
stale-issue-label: "stale"
exempt-issue-labels: "kind/feature,kind/enhancement"

View File

@@ -7,7 +7,6 @@ linters:
enable:
- depguard
- errcheck
- errorlint
- gocritic
- gocyclo
- gofmt
@@ -37,18 +36,9 @@ linters-settings:
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"
@@ -69,5 +59,7 @@ linters-settings:
line-length: 200
issues:
# golangci hides some golint warnings (the warning about exported things
# without documentation for example), this will make it show them anyway.
# withtout documentation for example), this will make it show them anyway.
exclude-use-default: false
exclude:
- should not use dot imports

View File

@@ -49,7 +49,7 @@ To execute both CLI and standalone e2e tests, run :
make e2e
```
Or if you need to build the CLI, run:
Or if you need to build the CLI, run:
```console
make build-and-e2e
```
@@ -85,7 +85,7 @@ make build-and-e2e-compose-standalone
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.
* Run the release Github Actions workflow with a tag of form vx.y.z following existing tags.
This will automatically create a new tag, release and make binaries for
Windows, macOS, and Linux available for download on the

View File

@@ -66,11 +66,6 @@ When sending lengthy log files, consider posting them as a gist
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.
@@ -85,7 +80,8 @@ before anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged!
don't get discouraged! Our contributor's guide explains
[the review process we use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
### Talking to other Docker users and contributors

View File

@@ -15,9 +15,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.22.8
ARG GO_VERSION=1.20.5
ARG XX_VERSION=1.2.1
ARG GOLANGCI_LINT_VERSION=v1.60.2
ARG GOLANGCI_LINT_VERSION=v1.53.2
ARG ADDLICENSE_VERSION=v1.0.0
ARG BUILD_TAGS="e2e"
@@ -89,13 +89,9 @@ RUN --mount=type=bind,target=. \
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
@@ -106,14 +102,11 @@ RUN --mount=type=bind,target=. \
--mount=type=cache,target=/go/pkg/mod \
rm -rf /tmp/coverage && \
mkdir -p /tmp/coverage && \
rm -rf /tmp/report && \
mkdir -p /tmp/report && \
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/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 /
COPY --from=test --link /tmp/report /
FROM base AS license-set
ARG LICENSE_FILES
@@ -136,7 +129,6 @@ FROM base AS docsgen
WORKDIR /src
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
FROM --platform=${BUILDPLATFORM} alpine AS docs-build
@@ -195,3 +187,9 @@ RUN --mount=from=binary \
FROM scratch AS release
COPY --from=releaser /out/ /
# docs-reference is a target used as remote context to update docs on release
# with latest changes on docs.docker.com.
# see open-pr job in .github/workflows/docs.yml for more details
FROM scratch AS docs-reference
COPY docs/reference/*.yaml .

View File

@@ -23,8 +23,8 @@
people = [
"glours",
"jhrotko",
"milas",
"laurazard",
"ndeloof",
"nicksieger",
"StefanScherer",
@@ -39,7 +39,6 @@
"aiordache",
"chris-crone",
"gtardif",
"laurazard",
"maxcleme",
"rumpl",
"thaJeztah"
@@ -73,11 +72,6 @@
Email = "guillaume.tardif@docker.com"
GitHub = "gtardif"
[people.jhrotko]
Name = "Joana Hrotko"
Email = "joana.hrotko@docker.com"
Github = "jhrotko"
[people.laurazard]
Name = "Laura Brehm"
Email = "laura.brehm@docker.com"

View File

@@ -32,13 +32,9 @@ endif
BUILD_FLAGS?=
TEST_FLAGS?=
E2E_TEST?=
ifneq ($(E2E_TEST),)
TEST_FLAGS:=$(TEST_FLAGS) -run '$(E2E_TEST)'
endif
EXCLUDE_E2E_TESTS?=
ifneq ($(EXCLUDE_E2E_TESTS),)
TEST_FLAGS:=$(TEST_FLAGS) -skip '$(EXCLUDE_E2E_TESTS)'
ifeq ($(E2E_TEST),)
else
TEST_FLAGS=-run $(E2E_TEST)
endif
BUILDX_CMD ?= docker buildx
@@ -75,11 +71,15 @@ install: binary
.PHONY: e2e-compose
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
.PHONY: e2e-compose-standalone
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
.PHONY: test-cucumber
test-cucumber:
go test $(TEST_FLAGS) -v -count=1 -parallel=1 ./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
@@ -89,7 +89,7 @@ build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the co
.PHONY: mocks
mocks:
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0
mockgen --version >/dev/null 2>&1 || go install github.com/golang/mock/mockgen@v1.6.0
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service

View File

@@ -1,18 +1,18 @@
# Table of Contents
- [Docker Compose v2](#docker-compose-v2)
- [About update and backward compatibility](#about-update-and-backward-compatibility)
- [Where to get Docker Compose](#where-to-get-docker-compose)
+ [Windows and macOS](#windows-and-macos)
+ [Linux](#linux)
- [Quick Start](#quick-start)
- [Contributing](#contributing)
- [Legacy](#legacy)
# Docker Compose v2
[![GitHub release](https://img.shields.io/github/release/docker/compose.svg?style=flat-square)](https://github.com/docker/compose/releases/latest)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/compose/v2)
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/compose/ci.yml?label=ci&logo=github&style=flat-square)](https://github.com/docker/compose/actions?query=workflow%3Aci)
[![Build Status](https://img.shields.io/github/workflow/status/docker/compose/ci?label=ci&logo=github&style=flat-square)](https://github.com/docker/compose/actions?query=workflow%3Aci)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/compose/v2?style=flat-square)](https://goreportcard.com/report/github.com/docker/compose/v2)
[![Codecov](https://codecov.io/gh/docker/compose/branch/main/graph/badge.svg?token=HP3K4Y4ctu)](https://codecov.io/gh/docker/compose)
[![Codecov](https://codecov.io/gh/docker/compose/branch/master/graph/badge.svg?token=HP3K4Y4ctu)](https://codecov.io/gh/docker/compose)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/docker/compose/badge)](https://api.securityscorecards.dev/projects/github.com/docker/compose)
![Docker Compose](logo.png?raw=true "Docker Compose Logo")
@@ -23,6 +23,12 @@ 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
@@ -52,7 +58,7 @@ Quick Start
Using Docker Compose is a three-step process:
1. Define your app's environment with a `Dockerfile` so it can be
reproduced anywhere.
2. Define the services that make up your app in `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 +85,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).

View File

@@ -29,8 +29,6 @@ import (
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"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
@@ -44,20 +42,17 @@ import (
// 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 {
func Setup(cmd *cobra.Command, dockerCli command.Cli) error {
tracingShutdown, err := tracing.InitTracing(dockerCli)
if err != nil {
return fmt.Errorf("initializing tracing: %w", err)
}
ctx := cmd.Context()
ctx, cmdSpan := otel.Tracer("").Start(
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
@@ -105,7 +100,7 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
if tracingShutdown != nil {
// use background for root context because the cmd's context might have
// been canceled already
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
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
@@ -134,11 +129,3 @@ func commandName(cmd *cobra.Command) []string {
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
}

View File

@@ -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)
}
})
}
}

View File

@@ -19,7 +19,6 @@ package compatibility
import (
"fmt"
"os"
"strings"
"github.com/docker/compose/v2/cmd/compose"
)
@@ -56,14 +55,13 @@ func Convert(args []string) []string {
var rootFlags []string
command := []string{compose.PluginName}
l := len(args)
ARGS:
for i := 0; i < l; i++ {
arg := args[i]
if contains(getCompletionCommands(), arg) {
command = append([]string{arg}, command...)
continue
}
if arg != "" && arg[0] != '-' {
if len(arg) > 0 && arg[0] != '-' {
command = append(command, args[i:]...)
break
}
@@ -83,23 +81,14 @@ ARGS:
rootFlags = append(rootFlags, arg)
continue
}
for _, flag := range getStringFlags() {
if arg == flag {
i++
if i >= l {
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
os.Exit(1)
}
rootFlags = append(rootFlags, arg, args[i])
continue ARGS
}
if strings.HasPrefix(arg, flag) {
_, val, found := strings.Cut(arg, "=")
if found {
rootFlags = append(rootFlags, flag, val)
continue ARGS
}
if contains(getStringFlags(), arg) {
i++
if i >= l {
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
os.Exit(1)
}
rootFlags = append(rootFlags, arg, args[i])
continue
}
command = append(command, arg)
}

View File

@@ -38,11 +38,6 @@ func Test_convert(t *testing.T) {
args: []string{"--context", "foo", "-f", "compose.yaml", "up"},
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
},
{
name: "with context arg",
args: []string{"--context=foo", "-f", "compose.yaml", "up"},
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
},
{
name: "with host",
args: []string{"--host", "tcp://1.2.3.4", "up"},

View File

@@ -15,13 +15,12 @@
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 {
func alphaCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
cmd := &cobra.Command{
Short: "Experimental commands",
Use: "alpha [COMMAND]",
@@ -31,9 +30,8 @@ func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
},
}
cmd.AddCommand(
vizCommand(p, dockerCli, backend),
publishCommand(p, dockerCli, backend),
generateCommand(p, backend),
watchCommand(p, backend),
vizCommand(p, backend),
)
return cmd
}

View File

@@ -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(ctx, 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)
}

View File

@@ -22,12 +22,12 @@ 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"
"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"
cliopts "github.com/docker/cli/opts"
ui "github.com/docker/compose/v2/pkg/progress"
buildkit "github.com/moby/buildkit/util/progress/progressui"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -35,6 +35,7 @@ import (
type buildOptions struct {
*ProjectOptions
composeOptions
quiet bool
pull bool
push bool
@@ -43,45 +44,36 @@ type buildOptions struct {
memory cliopts.MemBytes
ssh string
builder string
deps bool
}
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, err = loader.ParseShortSSHSyntax(opts.ssh)
if err != nil {
return api.BuildOptions{}, err
}
SSHKeys = append(SSHKeys, types.SSHKey{
ID: id,
Path: path,
})
}
builderName := opts.builder
if builderName == "" {
builderName = os.Getenv("BUILDX_BUILDER")
}
uiMode := ui.Mode
if uiMode == ui.ModeJSON {
uiMode = "rawjson"
}
return api.BuildOptions{
Pull: opts.pull,
Push: opts.push,
Progress: uiMode,
Progress: ui.Mode,
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 {
func buildCommand(p *ProjectOptions, progress *string, backend api.Service) *cobra.Command {
opts := buildOptions{
ProjectOptions: p,
}
@@ -104,47 +96,40 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
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")
fmt.Fprint(os.Stderr, "--progress is a global compose flag, better use `docker compose --progress xx build ...")
}
return runBuild(ctx, dockerCli, backend, opts, args)
return runBuild(ctx, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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().BoolVar(&opts.push, "push", false, "Push service images.")
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().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().StringVar(&opts.builder, "builder", "", "Set builder to use.")
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().VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
cmd.Flags().StringVar(progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
cmd.Flags().MarkHidden("progress") //nolint:errcheck
return cmd
}
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
project, _, err := opts.ToProject(ctx, 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

View File

@@ -17,10 +17,8 @@
package compose
import (
"sort"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -34,21 +32,19 @@ func noCompletion() validArgsFn {
}
}
func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
func completeServiceNames(p *ProjectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
p.Offline = true
project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
project, err := p.ToProject(nil)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
var values []string
serviceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
for _, s := range serviceNames {
var serviceNames []string
for _, s := range project.ServiceNames() {
if toComplete == "" || strings.HasPrefix(s, toComplete) {
values = append(values, s)
serviceNames = append(serviceNames, s)
}
}
return values, cobra.ShellCompDirectiveNoFileComp
return serviceNames, cobra.ShellCompDirectiveNoFileComp
}
}
@@ -69,24 +65,3 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s
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(cmd.Context(), 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
}
}

View File

@@ -18,10 +18,7 @@ package compose
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
@@ -29,30 +26,26 @@ import (
"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/loader"
"github.com/compose-spec/compose-go/v2/types"
composegoutils "github.com/compose-spec/compose-go/v2/utils"
buildx "github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
composegoutils "github.com/compose-spec/compose-go/utils"
"github.com/docker/buildx/util/logutil"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/pkg/kvfile"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/internal/desktop"
"github.com/docker/compose/v2/internal/experimental"
"github.com/docker/compose/v2/internal/tracing"
"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/remote"
"github.com/docker/compose/v2/pkg/utils"
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"
"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/utils"
)
const (
@@ -62,44 +55,12 @@ const (
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 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"
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
ComposeMenu = "COMPOSE_MENU"
)
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
lines, err := kvfile.ParseFromReader(r, lookup)
if err != nil {
return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err)
}
vars := types.Mapping{}
for _, line := range lines {
key, value, _ := strings.Cut(line, "=")
vars[key] = value
}
return vars, nil
}
func init() {
// compose evaluates env file values for interpolation
// `raw` format allows to load env_file with the same parser used by docker run --env-file
dotenv.RegisterFormat("raw", rawEnv)
}
type Backend interface {
api.Service
SetDesktopClient(cli *desktop.Client)
SetExperiments(experiments *experimental.State)
}
// Command defines a compose CLI command as a func with args
type Command func(context.Context, []string) error
@@ -109,17 +70,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) {
@@ -134,9 +96,6 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
Status: err.Error(),
}
}
if ui.Mode == ui.ModeJSON {
err = makeJSONError(err)
}
return err
}
}
@@ -156,9 +115,6 @@ type ProjectOptions struct {
ProjectDir string
EnvFiles []string
Compatibility bool
Progress string
Offline bool
All bool
}
// ProjectFunc does stuff within a types.Project
@@ -168,88 +124,40 @@ 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,
}
project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
project, err := o.ToProject(args, cli.WithResolvedPaths(true), cli.WithDiscardEnvFile)
if err != nil {
return err
}
ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
return fn(ctx, project, args)
})
}
type jsonErrorData struct {
Error bool `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}
func errorAsJSON(message string) string {
errorMessage := &jsonErrorData{
Error: true,
Message: message,
}
marshal, err := json.Marshal(errorMessage)
if err == nil {
return string(marshal)
} else {
return message
}
}
func makeJSONError(err error) error {
if err == nil {
return nil
}
var statusErr dockercli.StatusError
if errors.As(err, &statusErr) {
return dockercli.StatusError{
StatusCode: statusErr.StatusCode,
Status: errorAsJSON(statusErr.Status),
}
}
return fmt.Errorf("%s", errorAsJSON(err.Error()))
}
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", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file")
f.StringArrayVar(&o.EnvFiles, "env-file", nil, "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.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
_ = f.MarkHidden("workdir")
}
// get default value for a command line flag that is set by a coma-separated value in environment variable
func defaultStringArrayVar(env string) []string {
return strings.FieldsFunc(os.Getenv(env), func(c rune) bool {
return c == ','
})
}
func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
func (o *ProjectOptions) projectOrName(services ...string) (*types.Project, string, error) {
name := o.ProjectName
var project *types.Project
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile)
p, err := o.ToProject(services, cli.WithDiscardEnvFile)
if err != nil {
envProjectName := os.Getenv(ComposeProjectName)
if envProjectName != "" {
@@ -263,7 +171,7 @@ func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cl
return project, name, nil
}
func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cli) (string, error) {
func (o *ProjectOptions) toProjectName() (string, error) {
if o.ProjectName != "" {
return o.ProjectName, nil
}
@@ -273,88 +181,41 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl
return envProjectName, nil
}
project, _, err := o.ToProject(ctx, dockerCli, nil)
project, err := o.ToProject(nil)
if err != nil {
return "", err
}
return project.Name, nil
}
func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
remotes := o.remoteLoaders(dockerCli)
for _, r := range remotes {
po = append(po, cli.WithResourceLoader(r))
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)
}
options, err := o.toProjectOptions(po...)
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")
}
err = project.EnableServices(services...)
if err != nil {
return nil, err
}
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
api.Separator = "_"
}
return options.LoadModel(ctx)
}
func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { //nolint:gocyclo
var metrics tracing.Metrics
remotes := o.remoteLoaders(dockerCli)
for _, r := range remotes {
po = append(po, cli.WithResourceLoader(r))
}
options, err := o.toProjectOptions(po...)
if err != nil {
return nil, metrics, compose.WrapComposeError(err)
}
options.WithListeners(func(event string, metadata map[string]any) {
switch event {
case "extends":
metrics.CountExtends++
case "include":
paths := metadata["path"].(types.StringList)
for _, path := range paths {
var isRemote bool
for _, r := range remotes {
if r.Accept(path) {
isRemote = true
break
}
}
if isRemote {
metrics.CountIncludesRemote++
} else {
metrics.CountIncludesLocal++
}
}
}
})
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
api.Separator = "_"
}
project, err := options.LoadProject(ctx)
if err != nil {
return nil, metrics, compose.WrapComposeError(err)
}
if project.Name == "" {
return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
}
project, err = project.WithServicesEnabled(services...)
if err != nil {
return nil, metrics, 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, ","),
@@ -363,48 +224,25 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s
if len(o.EnvFiles) != 0 {
s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
}
project.Services[name] = s
project.Services[i] = s
}
project, err = project.WithSelectedServices(services)
if err != nil {
return nil, tracing.Metrics{}, err
}
project.WithoutUnnecessaryResources()
if !o.All {
project = project.WithoutUnnecessaryResources()
}
return project, metrics, err
}
func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader {
if o.Offline {
return nil
}
git := remote.NewGitRemoteLoader(o.Offline)
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
return []loader.ResourceLoader{git, oci}
err = project.ForServices(services)
return project, err
}
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
return cli.NewProjectOptions(o.ConfigPaths,
append(po,
cli.WithWorkingDirectory(o.ProjectDir),
// First apply os.Environment, always win
cli.WithOsEnv,
// Load PWD/.env if present and no explicit --env-file has been set
cli.WithEnvFiles(o.EnvFiles...),
// read dot env file to populate project environment
cli.WithDotEnv,
// get compose file path set by COMPOSE_FILE
cli.WithConfigFileEnv,
// if none was selected, get default compose.yaml file from current dir or parent folder
cli.WithDefaultConfigPath,
// .. and then, a project directory != PWD maybe has been set so let's load .env file
cli.WithEnvFiles(o.EnvFiles...),
cli.WithDotEnv,
// eventually COMPOSE_PROFILES should have been set
cli.WithDefaultProfiles(o.Profiles...),
cli.WithProfiles(o.Profiles),
cli.WithName(o.ProjectName))...)
}
@@ -417,7 +255,7 @@ func RunningAsStandalone() bool {
}
// RootCommand returns the compose command with its child commands
func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //nolint:gocyclo
func RootCommand(streams 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
@@ -428,7 +266,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
"commandConn.CloseRead:",
))
experiments := experimental.NewState()
opts := ProjectOptions{}
var (
ansi string
@@ -437,10 +274,11 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
version bool
parallel int
dryRun bool
progress string
)
c := &cobra.Command{
Short: "Docker Compose",
Long: "Define and run multi-container applications with Docker",
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) !
@@ -449,7 +287,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
return cmd.Help()
}
if version {
return versionCommand(dockerCli).Execute()
return versionCommand(streams).Execute()
}
_ = cmd.Help()
return dockercli.StatusError{
@@ -458,9 +296,11 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
}
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
err := setEnvWithDotEnv(&opts)
if err != nil {
return err
}
parent := cmd.Root()
if parent != nil {
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
@@ -470,15 +310,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
}
}
}
if verbose {
logrus.SetLevel(logrus.TraceLevel)
}
err := setEnvWithDotEnv(opts)
if err != nil {
return err
}
if noAnsi {
if ansi != "auto" {
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
@@ -486,14 +317,19 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
ansi = "never"
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
}
if verbose {
logrus.SetLevel(logrus.TraceLevel)
}
if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") {
ansi = v
}
formatter.SetANSIMode(dockerCli, ansi)
formatter.SetANSIMode(streams, ansi)
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
ui.NoColor()
formatter.SetANSIMode(dockerCli, formatter.Never)
formatter.SetANSIMode(streams, formatter.Never)
}
switch ansi {
@@ -503,12 +339,9 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
ui.Mode = ui.ModeTTY
}
switch opts.Progress {
switch progress {
case ui.ModeAuto:
ui.Mode = ui.ModeAuto
if ansi == "never" {
ui.Mode = ui.ModePlain
}
case ui.ModeTTY:
if ansi == "never" {
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
@@ -521,14 +354,10 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
ui.Mode = ui.ModePlain
case ui.ModeQuiet, "none":
ui.Mode = ui.ModeQuiet
case ui.ModeJSON:
ui.Mode = ui.ModeJSON
logrus.SetFormatter(&logrus.JSONFormatter{})
default:
return fmt.Errorf("unsupported --progress value %q", opts.Progress)
return fmt.Errorf("unsupported --progress value %q", progress)
}
// (4) options validation / normalization
if opts.WorkDir != "" {
if opts.ProjectDir != "" {
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
@@ -538,7 +367,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
}
for i, file := range opts.EnvFiles {
if !filepath.IsAbs(file) {
file, err := filepath.Abs(file)
file, err = filepath.Abs(file)
if err != nil {
return err
}
@@ -565,75 +394,46 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
parallel = i
}
if parallel > 0 {
logrus.Debugf("Limiting max concurrency to %d jobs", parallel)
backend.MaxConcurrency(parallel)
}
// dry run detection
ctx, err = backend.DryRunMode(ctx, dryRun)
ctx, err := backend.DryRunMode(cmd.Context(), dryRun)
if err != nil {
return err
}
cmd.SetContext(ctx)
// (6) Desktop integration
var desktopCli *desktop.Client
if !dryRun {
if desktopCli, err = desktop.NewFromDockerClient(ctx, dockerCli); desktopCli != nil {
logrus.Debugf("Enabled Docker Desktop integration (experimental) @ %s", desktopCli.Endpoint())
backend.SetDesktopClient(desktopCli)
} else if err != nil {
// not fatal, Compose will still work but behave as though
// it's not running as part of Docker Desktop
logrus.Debugf("failed to enable Docker Desktop integration: %v", err)
} else {
logrus.Trace("Docker Desktop integration not enabled")
}
}
// (7) experimental features
if err := experiments.Load(ctx, desktopCli); err != nil {
logrus.Debugf("Failed to query feature flags from Desktop: %v", err)
}
backend.SetExperiments(experiments)
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),
killCommand(&opts, dockerCli, backend),
runCommand(&opts, dockerCli, backend),
removeCommand(&opts, dockerCli, backend),
execCommand(&opts, dockerCli, backend),
attachCommand(&opts, dockerCli, backend),
exportCommand(&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),
upCommand(&opts, streams, backend),
downCommand(&opts, backend),
startCommand(&opts, backend),
restartCommand(&opts, backend),
stopCommand(&opts, backend),
psCommand(&opts, streams, backend),
listCommand(streams, backend),
logsCommand(&opts, streams, backend),
configCommand(&opts, streams, backend),
killCommand(&opts, backend),
runCommand(&opts, streams, backend),
removeCommand(&opts, backend),
execCommand(&opts, streams, backend),
pauseCommand(&opts, backend),
unpauseCommand(&opts, backend),
topCommand(&opts, streams, backend),
eventsCommand(&opts, streams, backend),
portCommand(&opts, streams, backend),
imagesCommand(&opts, streams, backend),
versionCommand(streams),
buildCommand(&opts, &progress, backend),
publishCommand(&opts, backend),
pushCommand(&opts, backend),
pullCommand(&opts, backend),
createCommand(&opts, backend),
copyCommand(&opts, backend),
waitCommand(&opts, backend),
alphaCommand(&opts, backend),
)
c.Flags().SetInterspersed(false)
@@ -642,22 +442,14 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
"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(&progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
c.Flags().IntVar(&parallel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
@@ -671,46 +463,33 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
return c
}
func setEnvWithDotEnv(opts ProjectOptions) error {
options, err := cli.NewProjectOptions(opts.ConfigPaths,
cli.WithWorkingDirectory(opts.ProjectDir),
cli.WithOsEnv,
cli.WithEnvFiles(opts.EnvFiles...),
cli.WithDotEnv,
)
func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
options, err := prjOpts.toProjectOptions()
if err != nil {
return nil
return compose.WrapComposeError(err)
}
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
workingDir, err := options.GetWorkingDir()
if err != nil {
return nil
return err
}
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
if err != nil {
return err
}
for k, v := range envFromFile {
if _, ok := os.LookupEnv(k); !ok {
if err = os.Setenv(k, v); err != nil {
return nil
if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env
if err := os.Setenv(k, v); err != nil {
return err
}
}
}
return err
return nil
}
var printerModes = []string{
ui.ModeAuto,
ui.ModeTTY,
ui.ModePlain,
ui.ModeJSON,
ui.ModeQuiet,
}
func SetUnchangedOption(name string, experimentalFlag bool) bool {
var value bool
// If the var is defined we use that value first
if envVar, ok := os.LookupEnv(name); ok {
value = utils.StringToBool(envVar)
} else {
// if not, we try to get it from experimental feature flag
value = experimentalFlag
}
return value
}

View File

@@ -19,32 +19,32 @@ 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": {},
},
},
"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)

View File

@@ -19,20 +19,14 @@ package compose
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strings"
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/template"
"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/cli"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
@@ -53,33 +47,19 @@ type configOptions struct {
images bool
hash string
noConsistency bool
variables bool
environment bool
}
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
po = append(po, o.ToProjectOptions()...)
project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
return project, err
}
func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
po = append(po, o.ToProjectOptions()...)
return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...)
}
func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn {
return []cli.ProjectOptionsFn{
func (o *configOptions) ToProject(services []string) (*types.Project, error) {
return o.ProjectOptions.ToProject(services,
cli.WithInterpolation(!o.noInterpolate),
cli.WithResolvedPaths(!o.noResolvePath),
cli.WithNormalization(!o.noNormalize),
cli.WithConsistency(!o.noConsistency),
cli.WithDefaultProfiles(o.Profiles...),
cli.WithDiscardEnvFile,
}
cli.WithProfiles(o.Profiles),
cli.WithDiscardEnvFile)
}
func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
func configCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := configOptions{
ProjectOptions: p,
}
@@ -102,38 +82,32 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
if opts.services {
return runServices(ctx, dockerCli, opts)
return runServices(streams, opts)
}
if opts.volumes {
return runVolumes(ctx, dockerCli, opts)
return runVolumes(streams, opts)
}
if opts.hash != "" {
return runHash(ctx, dockerCli, opts)
return runHash(streams, opts)
}
if opts.profiles {
return runProfiles(ctx, dockerCli, opts, args)
return runProfiles(streams, opts, args)
}
if opts.images {
return runConfigImages(ctx, dockerCli, opts, args)
}
if opts.variables {
return runVariables(ctx, dockerCli, opts, args)
}
if opts.environment {
return runEnvironment(ctx, dockerCli, opts, args)
return runConfigImages(streams, opts, args)
}
return runConfig(ctx, dockerCli, opts, args)
return runConfig(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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.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.")
@@ -141,25 +115,25 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
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.BoolVar(&opts.variables, "variables", false, "Print model variables and default values.")
flags.BoolVar(&opts.environment, "environment", false, "Print environment used for interpolation.")
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
return cmd
}
func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) (err error) {
func runConfig(ctx context.Context, streams api.Streams, backend api.Service, opts configOptions, services []string) error {
var content []byte
if opts.noInterpolate {
content, err = runConfigNoInterpolate(ctx, dockerCli, opts, services)
if err != nil {
return err
}
} else {
content, err = runConfigInterpolate(ctx, dockerCli, opts, services)
if err != nil {
return err
}
project, err := opts.ToProject(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 {
@@ -173,176 +147,67 @@ func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, s
if opts.Output != "" && len(content) > 0 {
return os.WriteFile(opts.Output, content, 0o666)
}
_, err = fmt.Fprint(dockerCli.Out(), string(content))
_, err = fmt.Fprint(streams.Out(), string(content))
return err
}
func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
project, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return nil, err
}
if opts.resolveImageDigests {
project, err = project.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
if err != nil {
return nil, err
}
}
if !opts.noConsistency {
err := project.CheckContainerNameUnicity()
if err != nil {
return nil, err
}
}
var content []byte
switch opts.Format {
case "json":
content, err = project.MarshalJSON()
case "yaml":
content, err = project.MarshalYAML()
default:
return nil, fmt.Errorf("unsupported format %q", opts.Format)
}
if err != nil {
return nil, err
}
return content, nil
}
func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
// we can't use ToProject, so the model we render here is only partially resolved
model, err := opts.ToModel(ctx, dockerCli, services)
if err != nil {
return nil, err
}
if opts.resolveImageDigests {
err = resolveImageDigests(ctx, dockerCli, model)
if err != nil {
return nil, err
}
}
return formatModel(model, opts.Format)
}
func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[string]any) (err error) {
// create a pseudo-project so we can rely on WithImagesResolved to resolve images
p := &types.Project{
Services: types.Services{},
}
services := model["services"].(map[string]any)
for name, s := range services {
service := s.(map[string]any)
if image, ok := service["image"]; ok {
p.Services[name] = types.ServiceConfig{
Image: image.(string),
}
}
}
p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
func runServices(streams api.Streams, opts configOptions) error {
project, err := opts.ToProject(nil)
if err != nil {
return err
}
// Collect image resolved with digest and update model accordingly
for name, s := range services {
service := s.(map[string]any)
config := p.Services[name]
if config.Image != "" {
service["image"] = config.Image
}
services[name] = service
}
model["services"] = services
return nil
}
func formatModel(model map[string]any, format string) (content []byte, err error) {
switch format {
case "json":
content, err = json.MarshalIndent(model, "", " ")
case "yaml":
buf := bytes.NewBuffer([]byte{})
encoder := yaml.NewEncoder(buf)
encoder.SetIndent(2)
err = encoder.Encode(model)
content = buf.Bytes()
default:
return nil, fmt.Errorf("unsupported format %q", format)
}
return
}
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 project.WithServices(project.ServiceNames(), func(s types.ServiceConfig) error {
fmt.Fprintln(streams.Out(), s.Name)
return nil
})
return err
}
func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
func runVolumes(streams api.Streams, opts configOptions) error {
project, err := opts.ToProject(nil)
if err != nil {
return err
}
for n := range project.Volumes {
_, _ = fmt.Fprintln(dockerCli.Out(), n)
fmt.Fprintln(streams.Out(), n)
}
return nil
}
func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
func runHash(streams api.Streams, 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)
project, err := opts.ToProject(nil)
if err != nil {
return err
}
if err := applyPlatforms(project, true); err != nil {
return err
if len(services) > 0 {
err = project.ForServices(services, types.IgnoreDependencies)
if err != nil {
return err
}
}
if len(services) == 0 {
services = project.ServiceNames()
}
sorted := services
sorted := project.Services
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
return sorted[i].Name < sorted[j].Name
})
for _, name := range sorted {
s, err := project.GetService(name)
if err != nil {
return err
}
for _, s := range sorted {
hash, err := compose.ServiceHash(s)
if err != nil {
return err
}
_, _ = fmt.Fprintf(dockerCli.Out(), "%s %s\n", name, hash)
fmt.Fprintf(streams.Out(), "%s %s\n", s.Name, hash)
}
return nil
}
func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
func runProfiles(streams api.Streams, opts configOptions, services []string) error {
set := map[string]struct{}{}
project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
project, err := opts.ToProject(services)
if err != nil {
return err
}
@@ -357,47 +222,22 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions,
}
sort.Strings(profiles)
for _, p := range profiles {
_, _ = fmt.Fprintln(dockerCli.Out(), p)
fmt.Fprintln(streams.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)
func runConfigImages(streams api.Streams, opts configOptions, services []string) error {
project, err := opts.ToProject(services)
if err != nil {
return err
}
for _, s := range project.Services {
_, _ = fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
}
return nil
}
func runVariables(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
opts.noInterpolate = true
model, err := opts.ToModel(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
variables := template.ExtractVariables(model, template.DefaultPattern)
return formatter.Print(variables, "", dockerCli.Out(), func(w io.Writer) {
for name, variable := range variables {
_, _ = fmt.Fprintf(w, "%s\t%t\t%s\t%s\n", name, variable.Required, variable.DefaultValue, variable.PresenceValue)
if s.Image != "" {
fmt.Fprintln(streams.Out(), s.Image)
} else {
fmt.Fprintf(streams.Out(), "%s%s%s\n", project.Name, api.Separator, s.Name)
}
}, "NAME", "REQUIRED", "DEFAULT VALUE", "ALTERNATE VALUE")
}
func runEnvironment(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
project, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}
for _, v := range project.Environment.Values() {
fmt.Println(v)
}
return nil
}

View File

@@ -21,7 +21,6 @@ import (
"errors"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -38,7 +37,7 @@ 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,
}
@@ -59,22 +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: completeServiceNames(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, "Include containers created by the run command")
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
flags.MarkHidden("all") //nolint:errcheck
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
return copyCmd
}
func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error {
name, err := opts.toProjectName(ctx, dockerCli)
func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
name, err := opts.toProjectName()
if err != nil {
return err
}

View File

@@ -19,13 +19,11 @@ 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"
@@ -48,14 +46,11 @@ type createOptions struct {
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...]",
Short: "Creates containers for a 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")
if opts.Build && opts.noBuild {
@@ -66,50 +61,33 @@ 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 {
if err := opts.Apply(project); err != nil {
return err
}
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: completeServiceNames(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.quietPull, "quiet-pull", false, "Pull without printing progress information")
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed")
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 missing.")
flags.StringVar(&opts.Pull, "pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
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.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: createOpts.quietPull,
})
}
func (opts createOptions) recreateStrategy() string {
if opts.noRecreate {
return api.RecreateNever
@@ -117,9 +95,6 @@ func (opts createOptions) recreateStrategy() string {
if opts.forceRecreate {
return api.RecreateForce
}
if opts.noInherit {
return api.RecreateForce
}
return api.RecreateDiverged
}
@@ -143,17 +118,11 @@ func (opts createOptions) GetTimeout() *time.Duration {
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
if opts.Build {
for i, service := range project.Services {
if service.Build == nil {
@@ -163,20 +132,16 @@ func (opts createOptions) Apply(project *types.Project) error {
project.Services[i] = service
}
}
if err := applyPlatforms(project, true); err != nil {
return err
if opts.noBuild {
for i, service := range project.Services {
service.Build = nil
if service.Image == "" {
service.Image = api.GetImageNameOrDefault(service, project.Name)
}
project.Services[i] = service
}
}
err := applyScaleOpts(project, opts.scale)
if err != nil {
return err
}
return nil
}
func applyScaleOpts(project *types.Project, opts []string) error {
for _, scale := range opts {
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)
@@ -186,16 +151,10 @@ func applyScaleOpts(project *types.Project, opts []string) error {
if err != nil {
return err
}
err = setServiceScale(project, name, replicas)
err = setServiceScale(project, name, uint64(replicas))
if err != nil {
return err
}
}
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)
}

View File

@@ -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)
}

View File

@@ -22,7 +22,6 @@ import (
"os"
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -40,7 +39,7 @@ 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,
}
@@ -57,15 +56,15 @@ 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)
}),
ValidArgsFunction: noCompletion(),
}
flags := downCmd.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.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.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" {
@@ -77,8 +76,8 @@ 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(ctx, dockerCli, services...)
func runDown(ctx context.Context, backend api.Service, opts downOptions, services []string) error {
project, name, err := opts.projectOrName()
if err != nil {
return err
}

View File

@@ -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,7 +31,7 @@ type eventsOpts struct {
json bool
}
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func eventsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := eventsOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -40,19 +39,19 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
cmd := &cobra.Command{
Use: "events [OPTIONS] [SERVICE...]",
Short: "Receive real time events from containers",
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, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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(ctx, dockerCli)
func runEvents(ctx context.Context, streams api.Streams, backend api.Service, opts eventsOpts, services []string) error {
name, err := opts.toProjectName()
if err != nil {
return err
}
@@ -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.Fprintln(streams.Out(), string(marshal))
} else {
_, _ = fmt.Fprintln(dockerCli.Out(), event)
fmt.Fprintln(streams.Out(), event)
}
return nil
},

View File

@@ -19,9 +19,8 @@ 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"
"github.com/docker/compose/v2/pkg/compose"
"github.com/spf13/cobra"
@@ -43,7 +42,7 @@ type execOpts struct {
interactive bool
}
func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func execCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := execOpts{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -51,7 +50,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
runCmd := &cobra.Command{
Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
Short: "Execute a command in a running container",
Short: "Execute a command in a running container.",
Args: cobra.MinimumNArgs(2),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
opts.service = args[0]
@@ -59,30 +58,30 @@ 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: completeServiceNames(p),
}
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background")
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().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.")
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command")
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY")
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
runCmd.Flags().SetInterspersed(false)
return runCmd
}
func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}

View File

@@ -1,74 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
)
type exportOptions struct {
*ProjectOptions
service string
output string
index int
}
func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
options := exportOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "export [OPTIONS] SERVICE",
Short: "Export a service container's filesystem as a tar archive",
Args: cobra.MinimumNArgs(1),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
options.service = args[0]
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runExport(ctx, dockerCli, backend, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
flags.StringVarP(&options.output, "output", "o", "", "Write to a file, instead of STDOUT")
return cmd
}
func runExport(ctx context.Context, dockerCli command.Cli, backend api.Service, options exportOptions) error {
projectName, err := options.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
exportOptions := api.ExportOptions{
Service: options.service,
Index: options.index,
Output: options.output,
}
return backend.Export(ctx, projectName, exportOptions)
}

View File

@@ -1,82 +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"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
type generateOptions struct {
*ProjectOptions
Format string
}
func generateCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := generateOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "generate [OPTIONS] [CONTAINERS...]",
Short: "EXPERIMENTAL - Generate a Compose file from existing containers",
PreRunE: Adapt(func(ctx context.Context, args []string) error {
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runGenerate(ctx, backend, opts, args)
}),
}
cmd.Flags().StringVar(&opts.ProjectName, "name", "", "Project name to set in the Compose file")
cmd.Flags().StringVar(&opts.ProjectDir, "project-dir", "", "Directory to use for the project")
cmd.Flags().StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
return cmd
}
func runGenerate(ctx context.Context, backend api.Service, opts generateOptions, containers []string) error {
_, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL")
if len(containers) == 0 {
return fmt.Errorf("at least one container must be specified")
}
project, err := backend.Generate(ctx, api.GenerateOptions{
Containers: containers,
ProjectName: opts.ProjectName,
})
if err != nil {
return err
}
var content []byte
switch opts.Format {
case "json":
content, err = project.MarshalJSON()
case "yaml":
content, err = project.MarshalYAML()
default:
return fmt.Errorf("unsupported format %q", opts.Format)
}
if err != nil {
return err
}
fmt.Println(string(content))
return nil
}

View File

@@ -23,7 +23,6 @@ import (
"sort"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
"github.com/spf13/cobra"
@@ -39,7 +38,7 @@ type imageOptions struct {
Format string
}
func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func imagesCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := imageOptions{
ProjectOptions: p,
}
@@ -47,17 +46,17 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
Use: "images [OPTIONS] [SERVICE...]",
Short: "List images used by the created containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, dockerCli, backend, opts, args)
return runImages(ctx, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(p),
}
imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
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(ctx, dockerCli)
func runImages(ctx context.Context, streams api.Streams, backend api.Service, opts imageOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
@@ -81,7 +80,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
}
}
for _, img := range ids {
_, _ = fmt.Fprintln(dockerCli.Out(), img)
fmt.Fprintln(streams.Out(), img)
}
return nil
}
@@ -90,7 +89,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, opts.Format, streams.Out(),
func(w io.Writer) {
for _, img := range images {
id := stringid.TruncateID(img.ID)

View File

@@ -20,7 +20,6 @@ import (
"context"
"os"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -33,29 +32,29 @@ type killOptions struct {
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,
}
cmd := &cobra.Command{
Use: "kill [OPTIONS] [SERVICE...]",
Short: "Force stop service containers",
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: completeServiceNames(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")
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(ctx, dockerCli, services...)
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}

View File

@@ -22,7 +22,6 @@ import (
"io"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/cli/opts"
@@ -38,20 +37,20 @@ type lsOptions struct {
Filter opts.FilterOpt
}
func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
func listCommand(streams api.Streams, backend api.Service) *cobra.Command {
lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
lsCmd := &cobra.Command{
Use: "ls [OPTIONS]",
Short: "List running compose projects",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runList(ctx, dockerCli, backend, lsOpts)
return runList(ctx, streams, backend, lsOpts)
}),
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().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")
return lsCmd
@@ -61,7 +60,7 @@ var acceptedListFilters = map[string]bool{
"name": true,
}
func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, lsOpts lsOptions) error {
func runList(ctx context.Context, streams api.Streams, backend api.Service, lsOpts lsOptions) error {
filters := lsOpts.Filter.Value()
err := filters.Validate(acceptedListFilters)
if err != nil {
@@ -72,6 +71,12 @@ func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, ls
if err != nil {
return err
}
if lsOpts.Quiet {
for _, s := range stackList {
fmt.Fprintln(streams.Out(), 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, lsOpts.Format, streams.Out(), func(w io.Writer) {
for _, stack := range view {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
}

View File

@@ -18,9 +18,7 @@ package compose
import (
"context"
"errors"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/cmd/formatter"
@@ -31,7 +29,6 @@ type logsOptions struct {
*ProjectOptions
composeOptions
follow bool
index int
tail string
since string
until string
@@ -40,7 +37,7 @@ type logsOptions struct {
timestamps bool
}
func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func logsCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := logsOptions{
ProjectOptions: p,
}
@@ -48,49 +45,31 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
Use: "logs [OPTIONS] [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, streams, 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: completeServiceNames(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.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
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.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.")
return logsCmd
}
func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
func runLogs(ctx context.Context, streams api.Streams, backend api.Service, opts logsOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}
// exclude services configured to ignore output (attach: false), until explicitly selected
if project != nil && len(services) == 0 {
for n, service := range project.Services {
if service.Attach == nil || *service.Attach {
services = append(services, n)
}
}
}
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
consumer := formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !opts.noColor, !opts.noPrefix, false)
return backend.Logs(ctx, name, consumer, api.LogOptions{
Project: project,
Services: services,
Follow: opts.follow,
Index: opts.index,
Tail: opts.tail,
Since: opts.since,
Until: opts.until,

View File

@@ -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
}

View File

@@ -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`)
})
}

View File

@@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -29,7 +28,7 @@ type pauseOptions struct {
*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,
}
@@ -37,15 +36,15 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
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: completeServiceNames(p),
}
return cmd
}
func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}
@@ -60,7 +59,7 @@ type unpauseOptions struct {
*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,
}
@@ -68,15 +67,15 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
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: completeServiceNames(p),
}
return cmd
}
func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}

View File

@@ -22,7 +22,6 @@ import (
"strconv"
"strings"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -35,13 +34,13 @@ type portOptions struct {
index int
}
func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func portCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := portOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
Short: "Print the public port for a port binding",
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)
@@ -53,17 +52,17 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPort(ctx, dockerCli, backend, opts, args[0])
return runPort(ctx, streams, backend, opts, args[0])
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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(ctx, dockerCli)
func runPort(ctx context.Context, streams api.Streams, backend api.Service, opts portOptions, service string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
@@ -75,6 +74,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.Fprintf(streams.Out(), "%s:%d\n", ip, port)
return nil
}

View File

@@ -18,19 +18,23 @@ package compose
import (
"context"
"errors"
"fmt"
"io"
"sort"
"strconv"
"strings"
"time"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
"github.com/docker/docker/api/types"
"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/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
)
type psOptions struct {
@@ -41,8 +45,6 @@ type psOptions struct {
Services bool
Filter string
Status []string
noTrunc bool
Orphans bool
}
func (p *psOptions) parseFilter() error {
@@ -64,7 +66,7 @@ func (p *psOptions) parseFilter() error {
return nil
}
func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func psCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := psOptions{
ProjectOptions: p,
}
@@ -75,45 +77,38 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
return opts.parseFilter()
},
RunE: Adapt(func(ctx context.Context, args []string) error {
return runPs(ctx, dockerCli, backend, args, opts)
return runPs(ctx, streams, backend, args, opts)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(p),
}
flags := psCmd.Flags()
flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status)")
flags.StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
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(ctx, dockerCli, services...)
func runPs(ctx context.Context, streams api.Streams, backend api.Service, services []string, opts psOptions) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}
if project != nil {
if project != nil && len(services) > 0 {
names := project.ServiceNames()
if len(services) > 0 {
for _, service := range services {
if !utils.StringContains(names, service) {
return fmt.Errorf("no such service: %s", service)
}
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,
All: opts.All || len(opts.Status) != 0,
All: opts.All,
Services: services,
})
if err != nil {
@@ -130,33 +125,38 @@ 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.Fprintln(streams.Out(), 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.Fprintln(streams.Out(), strings.Join(services, "\n"))
return nil
}
if opts.Format == "" {
opts.Format = dockerCli.ConfigFile().PsFormat
}
return formatter.Print(containers, opts.Format, streams.Out(),
writer(containers),
"NAME", "IMAGE", "COMMAND", "SERVICE", "CREATED", "STATUS", "PORTS")
}
containerCtx := cliformatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewContainerFormat(opts.Format, opts.Quiet, false),
Trunc: !opts.noTrunc,
func writer(containers []api.ContainerSummary) func(w io.Writer) {
return func(w io.Writer) {
for _, container := range containers {
ports := displayablePorts(container)
createdAt := time.Unix(container.Created, 0)
created := units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
status := container.Status
command := formatter2.Ellipsis(container.Command, 20)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", container.Name, container.Image, strconv.Quote(command), container.Service, created, status, ports)
}
}
return formatter.ContainerWrite(containerCtx, containers)
}
func filterByStatus(containers []api.ContainerSummary, statuses []string) []api.ContainerSummary {
@@ -177,3 +177,21 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
}
return false
}
func displayablePorts(c api.ContainerSummary) string {
if c.Publishers == nil {
return ""
}
ports := make([]types.Port, len(c.Publishers))
for i, pub := range c.Publishers {
ports[i] = types.Port{
IP: pub.URL,
PrivatePort: uint16(pub.TargetPort),
PublicPort: uint16(pub.PublishedPort),
Type: pub.Protocol,
}
}
return formatter2.DisplayablePorts(ports)
}

View File

@@ -18,16 +18,16 @@ package compose
import (
"context"
"io"
"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/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestPsTable(t *testing.T) {
@@ -69,11 +69,7 @@ func TestPsTable(t *testing.T) {
}).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)
err = runPs(ctx, stream{out: streams.NewOut(f)}, backend, nil, opts)
assert.NoError(t, err)
_, err = f.Seek(0, 0)
@@ -84,3 +80,21 @@ func TestPsTable(t *testing.T) {
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
}
type stream struct {
out *streams.Out
err io.Writer
in *streams.In
}
func (s stream) Out() *streams.Out {
return s.out
}
func (s stream) Err() io.Writer {
return s.err
}
func (s stream) In() *streams.In {
return s.in
}

View File

@@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -27,36 +26,30 @@ import (
type publishOptions struct {
*ProjectOptions
resolveImageDigests bool
ociVersion string
composeOptions
Repository string
}
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
opts := publishOptions{
func publishCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := pushOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "publish [OPTIONS] REPOSITORY[:TAG]",
publishCmd := &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])
return runPublish(ctx, 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
return publishCmd
}
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
project, _, err := opts.ToProject(ctx, dockerCli, nil)
func runPublish(ctx context.Context, backend api.Service, opts pushOptions, repository string) error {
project, err := opts.ToProject(nil)
if err != nil {
return err
}
return backend.Publish(ctx, project, repository, api.PublishOptions{
ResolveImageDigests: opts.resolveImageDigests,
OCIVersion: api.OCIVersion(opts.ociVersion),
})
return backend.Publish(ctx, project, repository)
}

View File

@@ -21,8 +21,7 @@ import (
"fmt"
"os"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/compose-spec/compose-go/types"
"github.com/morikuni/aec"
"github.com/spf13/cobra"
@@ -38,10 +37,9 @@ type pullOptions struct {
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,
}
@@ -55,55 +53,35 @@ 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: completeServiceNames(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")
cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel")
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")
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.")
cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built.")
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)
err := project.ForServices(services, types.IgnoreDependencies)
if err != nil {
return nil, err
return err
}
}
if opts.policy != "" {
for i, service := range project.Services {
if service.Image == "" {
continue
}
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(ctx, dockerCli, services)
if err != nil {
return err
}
project, err = opts.apply(project, services)
if err != nil {
return err
}
return backend.Pull(ctx, project, api.PullOptions{
Quiet: opts.quiet,
IgnoreFailures: opts.ignorePullFailures,

View File

@@ -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)
}

View File

@@ -19,8 +19,7 @@ package compose
import (
"context"
"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"
@@ -32,9 +31,10 @@ type pushOptions struct {
IncludeDeps bool
Ignorefailures bool
Quiet bool
Repository string
}
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,
}
@@ -42,25 +42,26 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
Use: "push [OPTIONS] [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: completeServiceNames(p),
}
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
pushCmd.Flags().BoolVar(&opts.IncludeDeps, "include-deps", false, "Also push images of services declared as dependencies")
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
pushCmd.Flags().StringVarP(&opts.Repository, "repository", "r", "", "Also publish the compose application in repository")
return pushCmd
}
func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error {
project, _, err := opts.ToProject(ctx, 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)
err := project.ForServices(services, types.IgnoreDependencies)
if err != nil {
return err
}
@@ -69,5 +70,6 @@ func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, op
return backend.Push(ctx, project, api.PushOptions{
IgnoreFailures: opts.Ignorefailures,
Quiet: opts.Quiet,
Repository: opts.Repository,
})
}

View File

@@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -31,7 +30,7 @@ type removeOptions struct {
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,
}
@@ -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: completeServiceNames(p),
}
f := cmd.Flags()
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
@@ -59,8 +58,8 @@ 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(ctx, dockerCli, services...)
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}

View File

@@ -20,7 +20,7 @@ import (
"context"
"time"
"github.com/docker/cli/cli/command"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -33,7 +33,7 @@ type restartOptions struct {
noDeps bool
}
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,
}
@@ -44,40 +44,39 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
opts.timeChanged = cmd.Flags().Changed("timeout")
},
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: completeServiceNames(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.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services.")
return restartCmd
}
func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli)
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
project, name, err := opts.projectOrName()
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
}
if opts.noDeps {
err := project.ForServices(services, types.IgnoreDependencies)
if err != nil {
return err
}
}
return backend.Restart(ctx, name, api.RestartOptions{
Timeout: timeout,
Services: services,
Project: project,
NoDeps: opts.noDeps,
})
}

View File

@@ -21,13 +21,9 @@ 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"
"github.com/docker/cli/cli/command"
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/opts"
"github.com/mattn/go-shellwords"
"github.com/spf13/cobra"
@@ -66,56 +62,54 @@ 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
}
}
func (options runOptions) apply(project *types.Project) error {
target, err := project.GetService(options.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.Ports = []types.ServicePortConfig{}
}
if len(options.publish) > 0 {
target.Ports = []types.ServicePortConfig{}
for _, p := range options.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(options.volumes) > 0 {
for _, v := range options.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 options.noDeps {
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
if err != nil {
return err
}
}
for i, s := range project.Services {
if s.Name == options.Service {
project.Services[i] = target
break
}
}
return project, nil
return nil
}
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
options := runOptions{
composeOptions: &composeOptions{
ProjectOptions: p,
@@ -124,12 +118,9 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
capDrop: opts.NewListOpts(nil),
}
createOpts := createOptions{}
buildOpts := buildOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
Short: "Run a one-off command on a service",
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]
@@ -156,43 +147,39 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
project, err := p.ToProject([]string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
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)
return runRun(ctx, backend, project, options, createOpts, streams)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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.BoolVarP(&options.noTty, "no-TTY", "T", !streams.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.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.BoolVar(&options.servicePorts, "service-ports", false, "Run command with the 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.")
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(&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().MarkHidden("tty") //nolint:errcheck
flags.SetNormalizeFunc(normalizeRunFlags)
@@ -210,8 +197,8 @@ 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)
func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, streams api.Streams) error {
err := options.apply(project)
if err != nil {
return err
}
@@ -222,17 +209,8 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
}
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)
}, dockerCli.Err())
return startDependencies(ctx, backend, *project, options.Service, options.ignoreOrphans)
}, streams.Err())
if err != nil {
return err
}
@@ -246,20 +224,8 @@ 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,
@@ -280,10 +246,10 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
QuietPull: options.quietPull,
}
for name, service := range project.Services {
if name == options.Service {
for i, service := range project.Services {
if service.Name == options.Service {
service.StdinOpen = options.interactive
project.Services[name] = service
project.Services[i] = service
}
}
@@ -298,23 +264,21 @@ 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, options runOptions) 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 != options.Service {
dependencies[name] = service
for _, service := range project.Services {
if service.Name != requestedServiceName {
dependencies = append(dependencies, service)
} else {
requestedService = service
}
}
project.Services = dependencies
project.DisabledServices[options.Service] = requestedService
project.DisabledServices = append(project.DisabledServices, requestedService)
err := backend.Create(ctx, &project, api.CreateOptions{
Build: buildOpts,
IgnoreOrphans: options.ignoreOrphans,
QuietPull: options.quietPull,
IgnoreOrphans: ignoreOrphans,
})
if err != nil {
return err

View File

@@ -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(ctx, 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
}

View File

@@ -19,7 +19,6 @@ package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -28,7 +27,7 @@ type startOptions struct {
*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,
}
@@ -36,15 +35,15 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
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: completeServiceNames(p),
}
return startCmd
}
func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}

View File

@@ -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(ctx, 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,
})
}

View File

@@ -20,7 +20,6 @@ import (
"context"
"time"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -32,7 +31,7 @@ type stopOptions struct {
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,
}
@@ -43,9 +42,9 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
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: completeServiceNames(p),
}
flags := cmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
@@ -53,8 +52,8 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
return cmd
}
func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
project, name, err := opts.projectOrName(services...)
if err != nil {
return err
}

View File

@@ -24,7 +24,6 @@ import (
"strings"
"text/tabwriter"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -34,7 +33,7 @@ type topOptions struct {
*ProjectOptions
}
func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func topCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
opts := topOptions{
ProjectOptions: p,
}
@@ -42,15 +41,15 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
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, streams, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(p),
}
return topCmd
}
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
projectName, err := opts.toProjectName(ctx, dockerCli)
func runTop(ctx context.Context, streams api.Streams, backend api.Service, opts topOptions, services []string) error {
projectName, err := opts.toProjectName()
if err != nil {
return err
}
@@ -64,8 +63,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.Fprintf(streams.Out(), "%s\n", container.Name)
err := psPrinter(streams.Out(), func(w io.Writer) {
for _, proc := range container.Processes {
info := []interface{}{}
for _, p := range proc {
@@ -74,7 +73,7 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
_, _ = fmt.Fprintf(w, strings.Repeat("%s\t", len(info))+"\n", info...)
}
_, _ = fmt.Fprintln(w)
fmt.Fprintln(w)
},
container.Titles...)
if err != nil {

View File

@@ -18,20 +18,15 @@ package compose
import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
xprogress "github.com/moby/buildkit/util/progress/progressui"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
)
@@ -42,146 +37,92 @@ type composeOptions struct {
type upOptions struct {
*composeOptions
Detach bool
noStart bool
noDeps bool
cascadeStop bool
cascadeFail bool
exitCodeFrom string
noColor bool
noPrefix bool
attachDependencies bool
attach []string
noAttach []string
timestamp bool
wait bool
waitTimeout int
watch bool
navigationMenu bool
navigationMenuChanged bool
Detach bool
noStart bool
noDeps bool
cascadeStop bool
exitCodeFrom 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)
err := project.ForServices(services, types.IgnoreDependencies)
if err != nil {
return nil, err
return err
}
}
if opts.exitCodeFrom != "" {
_, err := project.GetService(opts.exitCodeFrom)
if err != nil {
return nil, err
return err
}
}
return project, nil
return nil
}
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli) {
if !dockerCli.Out().IsTerminal() {
opts.navigationMenu = false
return
}
// If --menu flag was not set
if !opts.navigationMenuChanged {
if envVar, ok := os.LookupEnv(ComposeMenu); ok {
opts.navigationMenu = utils.StringToBool(envVar)
return
}
// ...and COMPOSE_MENU env var is not defined we want the default value to be true
opts.navigationMenu = true
}
}
func (opts upOptions) OnExit() api.Cascade {
switch {
case opts.cascadeStop:
return api.CascadeStop
case opts.cascadeFail:
return api.CascadeFail
default:
return api.CascadeIgnore
}
}
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
up := upOptions{}
create := createOptions{}
build := buildOptions{ProjectOptions: p}
upCmd := &cobra.Command{
Use: "up [OPTIONS] [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")
up.navigationMenuChanged = cmd.Flags().Changed("menu")
if !cmd.Flags().Changed("remove-orphans") {
create.removeOrphans = utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
}
return validateFlags(&up, &create)
}),
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
create.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
if create.ignoreOrphans && create.removeOrphans {
return fmt.Errorf("cannot combine %s and --remove-orphans", ComposeIgnoreOrphans)
return fmt.Errorf("%s and --remove-orphans cannot be combined", ComposeIgnoreOrphans)
}
if len(up.attach) != 0 && up.attachDependencies {
return errors.New("cannot combine --attach and --attach-dependencies")
}
up.validateNavigationMenu(dockerCli)
if !p.All && len(project.Services) == 0 {
return fmt.Errorf("no service selected")
}
return runUp(ctx, dockerCli, backend, create, up, build, project, services)
return runUp(ctx, streams, backend, create, up, project, services)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(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.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
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 missing.")
flags.StringVar(&create.Pull, "pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
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.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")
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.")
flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them")
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.BoolVar(&up.cascadeFail, "abort-on-container-failure", false, "Stops all containers if any container exited with failure. 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.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services")
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.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(&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.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{}, "Attach to service output.")
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Don't attach to specified service.")
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")
flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "timeout waiting for application to be running|healthy.")
return upCmd
}
//nolint:gocyclo
func validateFlags(up *upOptions, create *createOptions) error {
if up.exitCodeFrom != "" && !up.cascadeFail {
if up.exitCodeFrom != "" {
up.cascadeStop = true
}
if up.cascadeStop && up.cascadeFail {
return fmt.Errorf("--abort-on-container-failure cannot be combined with --abort-on-container-exit")
}
if up.wait {
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
@@ -191,11 +132,8 @@ func validateFlags(up *upOptions, create *createOptions) error {
if create.Build && create.noBuild {
return fmt.Errorf("--build and --no-build are incompatible")
}
if up.Detach && (up.attachDependencies || up.cascadeStop || up.cascadeFail || len(up.attach) > 0 || up.watch) {
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch")
}
if create.noInherit && create.noRecreate {
return fmt.Errorf("--no-recreate and --renew-anon-volumes are incompatible")
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
}
if create.forceRecreate && create.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
@@ -203,50 +141,42 @@ func validateFlags(up *upOptions, create *createOptions) error {
if create.recreateDeps && create.noRecreate {
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
}
if create.noBuild && up.watch {
return fmt.Errorf("--no-build and --watch are incompatible")
}
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, streams api.Streams, 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)
if err != nil {
return err
}
project, err = upOptions.apply(project, services)
err = upOptions.apply(project, services)
if err != nil {
return err
}
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
var consumer api.LogConsumer
if !upOptions.Detach {
consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
}
attachTo := services
if len(upOptions.attach) > 0 {
attachTo = upOptions.attach
}
if upOptions.attachDependencies {
attachTo = project.ServiceNames()
}
if len(attachTo) == 0 {
attachTo = project.ServiceNames()
}
attachTo = utils.Remove(attachTo, upOptions.noAttach...)
create := api.CreateOptions{
Build: build,
Services: services,
RemoveOrphans: createOptions.removeOrphans,
IgnoreOrphans: createOptions.ignoreOrphans,
@@ -261,65 +191,39 @@ 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,
ExitCodeFrom: upOptions.exitCodeFrom,
OnExit: upOptions.OnExit(),
Wait: upOptions.wait,
WaitTimeout: timeout,
Watch: upOptions.watch,
Services: services,
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
Project: project,
Attach: consumer,
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 {
continue
}
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)
}

View File

@@ -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 := createOptions{scale: []string{"foo=2"}}
err := opt.Apply(&p)
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))
}

View File

@@ -33,14 +33,14 @@ type versionOptions struct {
short bool
}
func versionCommand(dockerCli command.Cli) *cobra.Command {
func versionCommand(streams command.Cli) *cobra.Command {
opts := versionOptions{}
cmd := &cobra.Command{
Use: "version [OPTIONS]",
Short: "Show the Docker Compose version information",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
runVersion(opts, dockerCli)
runVersion(opts, streams)
return nil
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
@@ -52,19 +52,19 @@ func versionCommand(dockerCli command.Cli) *cobra.Command {
// define flags for backward compatibility with com.docker.cli
flags := cmd.Flags()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number")
flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number.")
return cmd
}
func runVersion(opts versionOptions, dockerCli command.Cli) {
func runVersion(opts versionOptions, streams command.Cli) {
if opts.short {
_, _ = fmt.Fprintln(dockerCli.Out(), strings.TrimPrefix(internal.Version, "v"))
fmt.Fprintln(streams.Out(), strings.TrimPrefix(internal.Version, "v"))
return
}
if opts.format == formatter.JSON {
_, _ = fmt.Fprintf(dockerCli.Out(), "{\"version\":%q}\n", internal.Version)
fmt.Fprintf(streams.Out(), "{\"version\":%q}\n", internal.Version)
return
}
_, _ = fmt.Fprintln(dockerCli.Out(), "Docker Compose version", internal.Version)
fmt.Fprintln(streams.Out(), "Docker Compose version", internal.Version)
}

View File

@@ -22,7 +22,6 @@ import (
"os"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -35,7 +34,7 @@ type vizOptions struct {
indentationStr string
}
func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func vizCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := vizOptions{
ProjectOptions: p,
}
@@ -51,7 +50,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
return err
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runViz(ctx, dockerCli, backend, &opts)
return runViz(ctx, backend, &opts)
}),
}
@@ -63,9 +62,9 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
return cmd
}
func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error {
func runViz(ctx context.Context, backend api.Service, opts *vizOptions) error {
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
project, _, err := opts.ToProject(ctx, dockerCli, nil)
project, err := opts.ToProject(nil)
if err != nil {
return err
}

View File

@@ -21,7 +21,6 @@ import (
"os"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
@@ -34,7 +33,7 @@ type waitOptions struct {
downProject bool
}
func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
func waitCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := waitOptions{
ProjectOptions: p,
}
@@ -43,11 +42,11 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
var err error
cmd := &cobra.Command{
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
Short: "Block until containers of all (or specified) services stop.",
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)
statusCode, err = runWait(ctx, backend, &opts)
return err
}),
PostRun: func(cmd *cobra.Command, args []string) {
@@ -60,8 +59,8 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
return cmd
}
func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) {
_, name, err := opts.projectOrName(ctx, dockerCli)
func runWait(ctx context.Context, backend api.Service, opts *waitOptions) (int64, error) {
_, name, err := opts.projectOrName()
if err != nil {
return 0, err
}

View File

@@ -19,107 +19,43 @@ package compose
import (
"context"
"fmt"
"os"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v2/cmd/formatter"
"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
prune bool
noUp bool
quiet bool
}
func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
watchOpts := watchOptions{
ProjectOptions: p,
}
buildOpts := buildOptions{
func watchCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
opts := watchOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "watch [SERVICE...]",
Short: "Watch build context for service and rebuild/refresh containers when files are updated",
Short: "EXPERIMENTAL - 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)
RunE: Adapt(func(ctx context.Context, args []string) error {
return runWatch(ctx, backend, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeServiceNames(p),
}
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
cmd.Flags().BoolVar(&watchOpts.prune, "prune", false, "Prune dangling images on rebuild")
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
cmd.Flags().BoolVar(&opts.quiet, "quiet", false, "hide build output")
return cmd
}
func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
project, _, err := watchOpts.ToProject(ctx, dockerCli, nil)
func runWatch(ctx context.Context, backend api.Service, opts watchOptions, services []string) error {
fmt.Fprintln(os.Stderr, "watch command is EXPERIMENTAL")
project, err := opts.ToProject(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: buildOpts.quiet,
},
Start: api.StartOptions{
Project: project,
Attach: nil,
Services: services,
},
}
if err := backend.Up(ctx, project, upOpts); err != nil {
return err
}
}
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
return backend.Watch(ctx, project, services, api.WatchOptions{
Build: &build,
LogTo: consumer,
Prune: watchOpts.prune,
})
return backend.Watch(ctx, project, services, api.WatchOptions{})
}

View File

@@ -1,96 +0,0 @@
/*
Copyright 2024 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"
"github.com/acarl005/stripansi"
)
var disableAnsi bool
func ansi(code string) string {
return fmt.Sprintf("\033%s", code)
}
func SaveCursor() {
if disableAnsi {
return
}
fmt.Print(ansi("7"))
}
func RestoreCursor() {
if disableAnsi {
return
}
fmt.Print(ansi("8"))
}
func HideCursor() {
if disableAnsi {
return
}
fmt.Print(ansi("[?25l"))
}
func ShowCursor() {
if disableAnsi {
return
}
fmt.Print(ansi("[?25h"))
}
func MoveCursor(y, x int) {
if disableAnsi {
return
}
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
}
func MoveCursorX(pos int) {
if disableAnsi {
return
}
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
}
func ClearLine() {
if disableAnsi {
return
}
// Does not move cursor from its current position
fmt.Print(ansi("[2K"))
}
func MoveCursorUp(lines int) {
if disableAnsi {
return
}
// Does not add new lines
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
}
func MoveCursorDown(lines int) {
if disableAnsi {
return
}
// Does not add new lines
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
}
func NewLine() {
// Like \n
fmt.Print("\012")
}
func lenAnsi(s string) int {
// len has into consideration ansi codes, if we want
// the len of the actual len(string) we need to strip
// all ansi codes
return len(stripansi.Strip(s))
}

View File

@@ -21,7 +21,7 @@ import (
"strconv"
"sync"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
)
var names = []string{
@@ -35,18 +35,6 @@ var names = []string{
"white",
}
const (
BOLD = "1"
FAINT = "2"
ITALIC = "3"
UNDERLINE = "4"
)
const (
RESET = "0"
CYAN = "36"
)
const (
// Never use ANSI codes
Never = "never"
@@ -59,16 +47,15 @@ const (
)
// SetANSIMode configure formatter for colored output on ANSI-compliant console
func SetANSIMode(streams command.Streams, ansi string) {
func SetANSIMode(streams api.Streams, ansi string) {
if !useAnsi(streams, ansi) {
nextColor = func() colorFunc {
return monochrome
}
disableAnsi = true
}
}
func useAnsi(streams command.Streams, ansi string) bool {
func useAnsi(streams api.Streams, ansi string) bool {
switch ansi {
case Always:
return true
@@ -85,17 +72,12 @@ var monochrome = func(s string) string {
return s
}
func ansiColor(code, s string, formatOpts ...string) string {
return fmt.Sprintf("%s%s%s", ansiColorCode(code, formatOpts...), s, ansiColorCode("0"))
func ansiColor(code, s string) string {
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
}
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
func ansiColorCode(code string, formatOpts ...string) string {
res := "\033["
for _, c := range formatOpts {
res = fmt.Sprintf("%s%s;", res, c)
}
return fmt.Sprintf("%s%sm", res, code)
func ansi(code string) string {
return fmt.Sprintf("\033[%sm", code)
}
func makeColorFunc(code string) colorFunc {

View File

@@ -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
}

View File

@@ -23,6 +23,8 @@ import (
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/pkg/errors"
)
// Print prints formatted lists in different formats
@@ -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
}

View File

@@ -25,7 +25,6 @@ import (
"sync"
"time"
"github.com/buger/goterm"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/pkg/jsonmessage"
)
@@ -61,32 +60,17 @@ func (l *logConsumer) Register(name string) {
}
func (l *logConsumer) register(name string) *presenter {
var p *presenter
root, _, found := strings.Cut(name, " ")
if found {
parent := l.getPresenter(root)
p = &presenter{
colors: parent.colors,
name: name,
prefix: parent.prefix,
}
} else {
cf := monochrome
if l.color {
if name == api.WatchLogger {
cf = makeColorFunc("92")
} else {
cf = nextColor()
}
}
p = &presenter{
colors: cf,
name: name,
}
cf := monochrome
if l.color {
cf = nextColor()
}
p := &presenter{
colors: cf,
name: name,
}
l.presenters.Store(name, p)
l.computeWidth()
if l.prefix {
l.computeWidth()
l.presenters.Range(func(key, value interface{}) bool {
p := value.(*presenter)
p.setPrefix(l.width)
@@ -109,7 +93,7 @@ func (l *logConsumer) Log(container, message string) {
l.write(l.stdout, container, message)
}
// Err formats a log message as received from name/container
// Log formats a log message as received from name/container
func (l *logConsumer) Err(container, message string) {
l.write(l.stderr, container, message)
}
@@ -118,28 +102,20 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
if l.ctx.Err() != nil {
return
}
if KeyboardManager != nil {
KeyboardManager.ClearKeyboardInfo()
}
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)
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
} else {
_, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line)
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
}
}
if KeyboardManager != nil {
KeyboardManager.PrintKeyboardInfo()
}
}
func (l *logConsumer) Status(container, msg string) {
p := l.getPresenter(container)
s := p.colors(fmt.Sprintf("%s%s %s\n", goterm.RESET_LINE, container, msg))
s := p.colors(fmt.Sprintf("%s %s\n", container, msg))
l.stdout.Write([]byte(s)) //nolint:errcheck
}
@@ -162,9 +138,5 @@ type presenter struct {
}
func (p *presenter) setPrefix(width int) {
if p.name == api.WatchLogger {
p.prefix = p.colors(strings.Repeat(" ", width) + " ⦿ ")
return
}
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
}

View File

@@ -1,379 +0,0 @@
/*
Copyright 2024 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 (
"context"
"errors"
"fmt"
"math"
"os"
"syscall"
"time"
"github.com/buger/goterm"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/watch"
"github.com/eiannone/keyboard"
"github.com/hashicorp/go-multierror"
"github.com/skratchdot/open-golang/open"
)
const DISPLAY_ERROR_TIME = 10
type KeyboardError struct {
err error
timeStart time.Time
}
func (ke *KeyboardError) shouldDisplay() bool {
return ke.err != nil && int(time.Since(ke.timeStart).Seconds()) < DISPLAY_ERROR_TIME
}
func (ke *KeyboardError) printError(height int, info string) {
if ke.shouldDisplay() {
errMessage := ke.err.Error()
MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
ClearLine()
fmt.Print(errMessage)
}
}
func (ke *KeyboardError) addError(prefix string, err error) {
ke.timeStart = time.Now()
prefix = ansiColor(CYAN, fmt.Sprintf("%s →", prefix), BOLD)
errorString := fmt.Sprintf("%s %s", prefix, err.Error())
ke.err = errors.New(errorString)
}
func (ke *KeyboardError) error() string {
return ke.err.Error()
}
type KeyboardWatch struct {
Watcher watch.Notify
Watching bool
WatchFn func(ctx context.Context, doneCh chan bool, project *types.Project, services []string, options api.WatchOptions) error
Ctx context.Context
Cancel context.CancelFunc
}
func (kw *KeyboardWatch) isWatching() bool {
return kw.Watching
}
func (kw *KeyboardWatch) switchWatching() {
kw.Watching = !kw.Watching
}
func (kw *KeyboardWatch) newContext(ctx context.Context) context.CancelFunc {
ctx, cancel := context.WithCancel(ctx)
kw.Ctx = ctx
kw.Cancel = cancel
return cancel
}
type KEYBOARD_LOG_LEVEL int
const (
NONE KEYBOARD_LOG_LEVEL = 0
INFO KEYBOARD_LOG_LEVEL = 1
DEBUG KEYBOARD_LOG_LEVEL = 2
)
type LogKeyboard struct {
kError KeyboardError
Watch KeyboardWatch
IsDockerDesktopActive bool
IsWatchConfigured bool
logLevel KEYBOARD_LOG_LEVEL
signalChannel chan<- os.Signal
}
var KeyboardManager *LogKeyboard
var eg multierror.Group
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
sc chan<- os.Signal,
watchFn func(ctx context.Context,
doneCh chan bool,
project *types.Project,
services []string,
options api.WatchOptions,
) error,
) {
km := LogKeyboard{}
km.IsDockerDesktopActive = isDockerDesktopActive
km.IsWatchConfigured = isWatchConfigured
km.logLevel = INFO
km.Watch.Watching = false
km.Watch.WatchFn = watchFn
km.signalChannel = sc
KeyboardManager = &km
}
func (lk *LogKeyboard) ClearKeyboardInfo() {
lk.clearNavigationMenu()
}
func (lk *LogKeyboard) PrintKeyboardInfo() {
if lk.logLevel == INFO {
lk.printNavigationMenu()
}
}
// Creates space to print error and menu string
func (lk *LogKeyboard) createBuffer(lines int) {
if lk.kError.shouldDisplay() {
extraLines := extraLines(lk.kError.error()) + 1
lines += extraLines
}
// get the string
infoMessage := lk.navigationMenu()
// calculate how many lines we need to display the menu info
// might be needed a line break
extraLines := extraLines(infoMessage) + 1
lines += extraLines
if lines > 0 {
allocateSpace(lines)
MoveCursorUp(lines)
}
}
func (lk *LogKeyboard) printNavigationMenu() {
offset := 1
lk.clearNavigationMenu()
lk.createBuffer(offset)
if lk.logLevel == INFO {
height := goterm.Height()
menu := lk.navigationMenu()
MoveCursorX(0)
SaveCursor()
lk.kError.printError(height, menu)
MoveCursor(height-extraLines(menu), 0)
ClearLine()
fmt.Print(menu)
MoveCursorX(0)
RestoreCursor()
}
}
func (lk *LogKeyboard) navigationMenu() string {
var openDDInfo string
if lk.IsDockerDesktopActive {
openDDInfo = shortcutKeyColor("v") + navColor(" View in Docker Desktop")
}
var openDDUI string
if openDDInfo != "" {
openDDUI = navColor(" ")
}
if lk.IsDockerDesktopActive {
openDDUI = openDDUI + shortcutKeyColor("o") + navColor(" View Config")
}
var watchInfo string
if openDDInfo != "" || openDDUI != "" {
watchInfo = navColor(" ")
}
var isEnabled = " Enable"
if lk.Watch.Watching {
isEnabled = " Disable"
}
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
return openDDInfo + openDDUI + watchInfo
}
func (lk *LogKeyboard) clearNavigationMenu() {
height := goterm.Height()
MoveCursorX(0)
SaveCursor()
// ClearLine()
for i := 0; i < height; i++ {
MoveCursorDown(1)
ClearLine()
}
RestoreCursor()
}
func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
if !lk.IsDockerDesktopActive {
return
}
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
func(ctx context.Context) error {
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop")
lk.keyboardError("View", err)
}
return err
}),
)
}
func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Project) {
if !lk.IsDockerDesktopActive {
return
}
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
func(ctx context.Context) error {
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
lk.keyboardError("View Config", err)
}
return err
}),
)
}
func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Project) {
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
func(ctx context.Context) error {
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
lk.keyboardError("Watch Docs", err)
}
return err
}),
)
}
func (lk *LogKeyboard) keyboardError(prefix string, err error) {
lk.kError.addError(prefix, err)
lk.printNavigationMenu()
timer1 := time.NewTimer((DISPLAY_ERROR_TIME + 1) * time.Second)
go func() {
<-timer1.C
lk.printNavigationMenu()
}()
}
func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
if !lk.IsWatchConfigured {
return
}
lk.Watch.switchWatching()
if !lk.Watch.isWatching() {
lk.Watch.Cancel()
} else {
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
func(ctx context.Context) error {
if options.Create.Build == nil {
err := fmt.Errorf("Cannot run watch mode with flag --no-build")
lk.keyboardError("Watch", err)
return err
}
lk.Watch.newContext(ctx)
buildOpts := *options.Create.Build
buildOpts.Quiet = true
return lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
Build: &buildOpts,
LogTo: options.Start.Attach,
})
}))
}
}
func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
switch kRune := event.Rune; kRune {
case 'v':
lk.openDockerDesktop(ctx, project)
case 'w':
if !lk.IsWatchConfigured {
// we try to open watch docs if DD is installed
if lk.IsDockerDesktopActive {
lk.openDDWatchDocs(ctx, project)
}
// either way we mark menu/watch as an error
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
func(ctx context.Context) error {
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
lk.keyboardError("Watch", err)
return err
}))
return
}
lk.StartWatch(ctx, doneCh, project, options)
case 'o':
lk.openDDComposeUI(ctx, project)
}
switch key := event.Key; key {
case keyboard.KeyCtrlC:
_ = keyboard.Close()
lk.clearNavigationMenu()
ShowCursor()
lk.logLevel = NONE
if lk.Watch.Watching && lk.Watch.Cancel != nil {
lk.Watch.Cancel()
_ = eg.Wait().ErrorOrNil() // Need to print this ?
}
// will notify main thread to kill and will handle gracefully
lk.signalChannel <- syscall.SIGINT
case keyboard.KeyEnter:
NewLine()
lk.printNavigationMenu()
}
}
func allocateSpace(lines int) {
for i := 0; i < lines; i++ {
ClearLine()
NewLine()
MoveCursorX(0)
}
}
func extraLines(s string) int {
return int(math.Floor(float64(lenAnsi(s)) / float64(goterm.Width())))
}
func shortcutKeyColor(key string) string {
foreground := "38;2"
black := "0;0;0"
background := "48;2"
white := "255;255;255"
return ansiColor(foreground+";"+black+";"+background+";"+white, key, BOLD)
}
func navColor(key string) string {
return ansiColor(FAINT, key)
}

View File

@@ -24,38 +24,31 @@ import (
"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/sirupsen/logrus"
"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 {
// TODO(milas): this cast is safe but we should not need to do this,
// we should expose the concrete service type so that we do not need
// to rely on the `api.Service` interface internally
backend := compose.NewComposeService(dockerCli).(commands.Backend)
cmd := commands.RootCommand(dockerCli, backend)
originalPreRunE := cmd.PersistentPreRunE
serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli))
cmd := commands.RootCommand(dockerCli, serviceProxy)
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)
if err := cmdtrace.Setup(cmd, dockerCli, os.Args[1:]); err != nil {
logrus.Debugf("failed to enable tracing: %v", err)
}
if originalPreRunE != nil {
return originalPreRunE(cmd, args)
if originalPreRun != nil {
return originalPreRun(cmd, args)
}
return nil
}
@@ -75,22 +68,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:])...)

View File

@@ -1,69 +1,63 @@
# docker compose
```text
docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]
```
<!---MARKER_GEN_START-->
Define and run multi-container applications with Docker
Define and run multi-container applications with Docker.
### 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 |
| [`export`](compose_export.md) | Export a service container's filesystem as a tar archive |
| [`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 containers of all (or specified) services stop. |
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
| Name | Description |
|:--------------------------------|:------------------------------------------------------------------------|
| [`alpha`](compose_alpha.md) | Experimental commands |
| [`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. |
| [`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 |
| [`wait`](compose_wait.md) | Block until the first service container stops |
### Options
| Name | Type | Default | Description |
|:-----------------------|:--------------|:--------|:----------------------------------------------------------------------------------------------------|
| `--all-resources` | `bool` | | Include all resources, even those not used by services |
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
| `--compatibility` | `bool` | | Run compose in backward compatibility mode |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--env-file` | `stringArray` | | Specify an alternate environment file |
| `--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, json, quiet) |
| `--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 |
<!---MARKER_GEN_END-->
## Examples
## Description
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 the `-f` flag to specify the location of a Compose configuration file.
@@ -151,16 +145,16 @@ 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
Calling `docker compose --parallel 1 pull` will pull 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.
@@ -176,12 +170,9 @@ and `COMPOSE_PARALLEL_LIMIT` does the same as the `--parallel` flag.
If flags are explicitly set on the command line, the 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.
Setting the `COMPOSE_MENU` environment variable to `false` disables the helper menu when running `docker compose up`
in attached mode. Alternatively, you can also run `docker compose up --menu=false` to disable the helper menu.
### Use Dry Run mode to test your command
Use `--dry-run` flag to test a command without changing your application stack state.

View File

@@ -1,7 +1,7 @@
# docker compose alpha dry-run
<!---MARKER_GEN_START-->
Dry run command allows you to test a command without applying changes
EXPERIMENTAL - Dry run command allow you to test a command without applying changes
<!---MARKER_GEN_END-->

View File

@@ -1,17 +0,0 @@
# docker compose alpha generate
<!---MARKER_GEN_START-->
EXPERIMENTAL - Generate a Compose file from existing containers
### Options
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
| `--name` | `string` | | Project name to set in the Compose file |
| `--project-dir` | `string` | | Directory to use for the project |
<!---MARKER_GEN_END-->

View File

@@ -1,16 +0,0 @@
# docker compose alpha publish
<!---MARKER_GEN_START-->
Publish compose application
### Options
| Name | Type | Default | Description |
|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) |
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
<!---MARKER_GEN_END-->

View File

@@ -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-->

View File

@@ -5,14 +5,14 @@ EXPERIMENTAL - Generate a graphviz graph from your compose file
### Options
| Name | Type | Default | Description |
|:---------------------|:-------|:--------|:---------------------------------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--image` | `bool` | | Include service's image name in output graph |
| `--indentation-size` | `int` | `1` | Number of tabs or spaces to use for indentation |
| `--networks` | `bool` | | Include service's attached networks in output graph |
| `--ports` | `bool` | | Include service's exposed ports in output graph |
| `--spaces` | `bool` | | If given, space character ' ' will be used to indent,<br>otherwise tab character '\t' will be used |
| 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-->

View File

@@ -1,15 +1,14 @@
# docker compose alpha watch
<!---MARKER_GEN_START-->
Watch build context for service and rebuild/refresh containers when files are updated
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 |
| `--no-up` | | | Do not build & start services before watching |
| `--quiet` | | | hide build output |
| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--quiet` | | | hide build output |
<!---MARKER_GEN_END-->

View File

@@ -1,17 +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` | `bool` | | Execute command in dry run mode |
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
| `--no-stdin` | `bool` | | Do not attach STDIN |
| `--sig-proxy` | `bool` | `true` | Proxy all received signals to the process |
<!---MARKER_GEN_END-->

View File

@@ -1,37 +1,28 @@
# docker compose build
<!---MARKER_GEN_START-->
Services are built once and then tagged, by default as `project-service`.
If the Compose file specifies an
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
the image is tagged with that name, substituting any variables beforehand. See
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
If you change a service's `Dockerfile` or the contents of its build directory,
run `docker compose build` to rebuild it.
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` | `bool` | | Execute command in dry run mode |
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
| `--no-cache` | `bool` | | Do not use cache when building the image |
| `--pull` | `bool` | | Always attempt to pull a newer version of the image |
| `--push` | `bool` | | Push service images |
| `-q`, `--quiet` | `bool` | | 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` | `bool` | | Also build dependencies (transitively) |
| 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) |
<!---MARKER_GEN_END-->
## Description
Services are built once and then tagged, by default as `project-service`.
Services are built once and then tagged, by default as `project_service`.
If the Compose file specifies an
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,

View File

@@ -1,9 +1,7 @@
# docker compose convert
<!---MARKER_GEN_START-->
`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.
Parse, resolve and render compose file in canonical format
### Aliases
@@ -13,28 +11,26 @@ the canonical format.
| Name | Type | Default | Description |
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--environment` | `bool` | | Print environment used for interpolation. |
| `--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` | `bool` | | Print the image names, one per line. |
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
| `--no-normalize` | `bool` | | Don't normalize compose model |
| `--no-path-resolution` | `bool` | | Don't resolve file paths |
| `--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` | `bool` | | Print the profile names, one per line. |
| `-q`, `--quiet` | `bool` | | Only validate the configuration, don't print anything |
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
| `--services` | `bool` | | Print the service names, one per line. |
| `--variables` | `bool` | | Print model variables and default values. |
| `--volumes` | `bool` | | Print the volume names, one per line. |
| `--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
`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.

View File

@@ -5,13 +5,12 @@ Copy files/folders between a service container and the local filesystem
### Options
| Name | Type | Default | Description |
|:----------------------|:-------|:--------|:--------------------------------------------------------|
| `--all` | `bool` | | Include containers created by the run command |
| `-a`, `--archive` | `bool` | | Archive mode (copy all uid/gid information) |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `-L`, `--follow-link` | `bool` | | 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) |
| `--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 there are multiple instances of a service . |
<!---MARKER_GEN_END-->

View File

@@ -1,21 +1,20 @@
# docker compose create
<!---MARKER_GEN_START-->
Creates containers for a service
Creates containers for a service.
### Options
| Name | Type | Default | Description |
|:-------------------|:--------------|:---------|:----------------------------------------------------------------------------------------------|
| `--build` | `bool` | | Build images before starting containers |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--force-recreate` | `bool` | | Recreate containers even if their configuration and image haven't changed |
| `--no-build` | `bool` | | Don't build an image, even if it's policy |
| `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") |
| `--quiet-pull` | `bool` | | Pull without printing progress information |
| `--remove-orphans` | `bool` | | 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. |
| `--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 missing. |
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--pull` | `string` | `missing` | Pull image before running ("always"\|"missing"\|"never") |
| `--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. |
<!---MARKER_GEN_END-->

View File

@@ -1,29 +1,17 @@
# docker compose down
<!---MARKER_GEN_START-->
Stops containers and removes containers, networks, volumes, and images created by `up`.
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.
Networks and volumes defined as external are never removed.
Anonymous volumes are not removed by default. However, as they dont have a stable name, they are not automatically
mounted by a subsequent `up`. For data that needs to persist between updates, use explicit paths as bind mounts or
named volumes.
Stop and remove containers, networks
### Options
| Name | Type | Default | Description |
|:-------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--remove-orphans` | `bool` | | 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` | `bool` | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers |
| 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. |
<!---MARKER_GEN_END-->
@@ -34,12 +22,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 dont have a stable name, they are not automatically
Anonymous volumes are not removed by default. However, as they dont 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.

View File

@@ -1,32 +1,14 @@
# docker compose events
<!---MARKER_GEN_START-->
Stream container events for every container in the project.
With the `--json` flag, a json object is printed one per line with the format:
```json
{
"time": "2015-11-20T18:01:03.615550",
"type": "container",
"action": "create",
"id": "213cf7...5fc39a",
"service": "web",
"attributes": {
"name": "application_web_1",
"image": "alpine:edge"
}
}
```
The events that can be received using this can be seen [here](/reference/cli/docker/system/events/#object-types).
Receive real time events from containers.
### Options
| Name | Type | Default | Description |
|:------------|:-------|:--------|:------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--json` | `bool` | | Output events as a stream of json objects |
| Name | Type | Default | Description |
|:------------|:-----|:--------|:------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--json` | | | Output events as a stream of json objects |
<!---MARKER_GEN_END-->
@@ -51,4 +33,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/reference/cli/docker/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).

View File

@@ -1,23 +1,20 @@
# docker compose exec
<!---MARKER_GEN_START-->
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
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
Execute a command in a running container.
### Options
| Name | Type | Default | Description |
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
| `-d`, `--detach` | `bool` | | Detached mode: Run command in the background |
| `--dry-run` | `bool` | | 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` | `bool` | `true` | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
| `--privileged` | `bool` | | 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. |
| `--dry-run` | | | Execute command in dry run mode |
| `-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-->

View File

@@ -1,16 +0,0 @@
# docker compose export
<!---MARKER_GEN_START-->
Export a service container's filesystem as a tar archive
### Options
| Name | Type | Default | Description |
|:-----------------|:---------|:--------|:---------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
<!---MARKER_GEN_END-->

View File

@@ -5,11 +5,11 @@ List images used by the created containers
### Options
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:-------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
| `-q`, `--quiet` | `bool` | | Only display IDs |
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:--------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
| `-q`, `--quiet` | | | Only display IDs |
<!---MARKER_GEN_END-->

View File

@@ -1,19 +1,15 @@
# docker compose kill
<!---MARKER_GEN_START-->
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example:
```console
$ docker compose kill -s SIGINT
```
Force stop service containers.
### Options
| Name | Type | Default | Description |
|:-------------------|:---------|:----------|:---------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container |
| Name | Type | Default | Description |
|:-------------------|:---------|:----------|:----------------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
<!---MARKER_GEN_END-->
@@ -23,5 +19,5 @@ $ docker compose kill -s SIGINT
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example:
```console
$ docker compose kill -s SIGINT
$ docker-compose kill -s SIGINT
```

View File

@@ -1,20 +1,19 @@
# docker compose logs
<!---MARKER_GEN_START-->
Displays log output from services
View output from containers
### Options
| Name | Type | Default | Description |
|:---------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `-f`, `--follow` | `bool` | | Follow log output |
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
| `--no-color` | `bool` | | Produce monochrome output |
| `--no-log-prefix` | `bool` | | Don't print prefix in logs |
| `--dry-run` | | | Execute command in dry run mode |
| `-f`, `--follow` | | | Follow log output. |
| `--no-color` | | | Produce monochrome output. |
| `--no-log-prefix` | | | Don't print prefix in logs. |
| `--since` | `string` | | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container |
| `-t`, `--timestamps` | `bool` | | Show timestamps |
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. |
| `-t`, `--timestamps` | | | Show timestamps. |
| `--until` | `string` | | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
@@ -22,4 +21,4 @@ Displays log output from services
## Description
Displays log output from services
Displays log output from services.

View File

@@ -1,21 +1,21 @@
# docker compose ls
<!---MARKER_GEN_START-->
Lists running Compose projects
List running compose projects
### Options
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:-------------------------------------------|
| `-a`, `--all` | `bool` | | Show all stopped Compose projects |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--filter` | `filter` | | Filter output based on conditions provided |
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
| `-q`, `--quiet` | `bool` | | Only display IDs |
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:--------------------------------------------|
| `-a`, `--all` | | | Show all stopped Compose projects |
| `--dry-run` | | | Execute command in dry run mode |
| `--filter` | `filter` | | Filter output based on conditions provided. |
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
| `-q`, `--quiet` | | | Only display IDs. |
<!---MARKER_GEN_END-->
## Description
Lists running Compose projects
Lists running Compose projects.

View File

@@ -1,13 +1,13 @@
# docker compose pause
<!---MARKER_GEN_START-->
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
Pause services
### Options
| Name | Type | Default | Description |
|:------------|:-------|:--------|:--------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
<!---MARKER_GEN_END-->

View File

@@ -1,14 +1,14 @@
# docker compose port
<!---MARKER_GEN_START-->
Prints the public port for a port binding
Print the public port for a port binding.
### Options
| Name | Type | Default | Description |
|:-------------|:---------|:--------|:--------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
| `--dry-run` | | | Execute command in dry run mode |
| `--index` | `int` | `1` | index of the container if service has multiple replicas |
| `--protocol` | `string` | `tcp` | tcp or udp |
@@ -16,4 +16,4 @@ Prints the public port for a port binding
## Description
Prints the public port for a port binding
Prints the public port for a port binding.

View File

@@ -1,36 +1,19 @@
# docker compose ps
<!---MARKER_GEN_START-->
Lists containers for a Compose project, with current status and exposed ports.
```console
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
example-foo-1 alpine "/entrypoint.…" foo 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp
```
By default, only running containers are shown. `--all` flag can be used to include stopped containers.
```console
$ docker compose ps --all
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
example-foo-1 alpine "/entrypoint.…" foo 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp
example-bar-1 alpine "/entrypoint.…" bar 4 seconds ago exited (0)
```
List containers
### Options
| Name | Type | Default | Description |
|:----------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-a`, `--all` | `bool` | | Show all stopped containers (including those created by the run command) |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status) |
| [`--format`](#format) | `string` | `table` | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
| `--no-trunc` | `bool` | | Don't truncate output |
| `--orphans` | `bool` | `true` | Include orphaned services (not declared by project) |
| `-q`, `--quiet` | `bool` | | Only display IDs |
| `--services` | `bool` | | Display services |
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
| Name | Type | Default | Description |
|:----------------------|:--------------|:--------|:--------------------------------------------------------------------------------------------------------------|
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
| `--dry-run` | | | Execute command in dry run mode |
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
| [`--format`](#format) | `string` | `table` | Format the output. Values: [table \| json] |
| `-q`, `--quiet` | | | Only display IDs |
| `--services` | | | Display services |
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
<!---MARKER_GEN_END-->
@@ -45,7 +28,7 @@ NAME IMAGE COMMAND SERVICE CREATED STATUS
example-foo-1 alpine "/entrypoint.…" foo 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp
```
By default, only running containers are shown. `--all` flag can be used to include stopped containers.
By default, only running containers are shown. `--all` flag can be used to include stopped containers
```console
$ docker compose ps --all
@@ -69,7 +52,7 @@ $ docker compose ps --format json
```
The JSON output allows you to use the information in other tools for further
processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/)
processing, for example, using the [`jq` utility](https://stedolan.github.io/jq/){:target="_blank" rel="noopener" class="_"}
to pretty-print the JSON:
```console

View File

@@ -1,30 +1,30 @@
# docker compose pull
<!---MARKER_GEN_START-->
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
Pull service images
### Options
| Name | Type | Default | Description |
|:-------------------------|:---------|:--------|:-------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--ignore-buildable` | `bool` | | Ignore images that can be built |
| `--ignore-pull-failures` | `bool` | | Pull what it can and ignores images with pull failures |
| `--include-deps` | `bool` | | Also pull services declared as dependencies |
| `--policy` | `string` | | Apply pull policy ("missing"\|"always") |
| `-q`, `--quiet` | `bool` | | Pull without printing progress information |
| Name | Type | Default | Description |
|:-------------------------|:-----|:--------|:--------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--ignore-buildable` | | | Ignore images that can be built. |
| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures. |
| `--include-deps` | | | Also pull services declared as dependencies. |
| `-q`, `--quiet` | | | Pull without printing progress information. |
<!---MARKER_GEN_END-->
## Description
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
those images.
## Examples
Consider the following `compose.yaml`:
suppose you have this `compose.yaml`:
```yaml
services:
@@ -65,4 +65,5 @@ $ docker compose pull db
⠹ c8752d5b785c Waiting 9.3s
```
`docker compose pull` tries to pull image for services with a build section. If pull fails, it lets you know this service image must be built. You can skip this by setting `--ignore-buildable` flag.
`docker compose pull` will try to pull image for services with a build section. If pull fails, it will let
user know this service image MUST be built. You can skip this by setting `--ignore-buildable` flag

View File

@@ -1,33 +1,16 @@
# docker compose push
<!---MARKER_GEN_START-->
Pushes images for services to their respective registry/repository.
The following assumptions are made:
- You are pushing an image you have built locally
- You have access to the build key
Examples
```yaml
services:
service1:
build: .
image: localhost:5000/yourimage ## goes to local registry
service2:
build: .
image: your-dockerid/yourimage ## goes to your repository on Docker Hub
```
Push service images
### Options
| Name | Type | Default | Description |
|:-------------------------|:-------|:--------|:-------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--ignore-push-failures` | `bool` | | Push what it can and ignores images with push failures |
| `--include-deps` | `bool` | | Also push images of services declared as dependencies |
| `-q`, `--quiet` | `bool` | | Push without printing progress information |
| Name | Type | Default | Description |
|:-------------------------|:-----|:--------|:-------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
| `--include-deps` | | | Also push images of services declared as dependencies |
| `-q`, `--quiet` | | | Push without printing progress information |
<!---MARKER_GEN_END-->

View File

@@ -1,24 +1,15 @@
# docker compose restart
<!---MARKER_GEN_START-->
Restarts all stopped and running services, or the specified services only.
If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added
after a container is built, but before the container's command is executed) are not updated
after restarting.
If you are looking to configure a service's restart policy, refer to
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
Restart service containers
### Options
| Name | Type | Default | Description |
|:------------------|:-------|:--------|:--------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--no-deps` | `bool` | | Don't restart dependent services |
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
| Name | Type | Default | Description |
|:------------------|:------|:--------|:--------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--no-deps` | | | Don't restart dependent services. |
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
<!---MARKER_GEN_END-->
@@ -32,6 +23,6 @@ after running this command. For example, changes to environment variables (which
after a container is built, but before the container's command is executed) are not updated
after restarting.
If you are looking to configure a service's restart policy, refer to
If you are looking to configure a service's restart policy, please refer to
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).

View File

@@ -1,30 +1,21 @@
# docker compose rm
<!---MARKER_GEN_START-->
Removes stopped service containers.
Removes stopped service containers
By default, anonymous volumes attached to containers are not removed. You can override this with `-v`. To list all
volumes, use `docker volume ls`.
By default, anonymous volumes attached to containers will not be removed. You
can override this with -v. To list all volumes, use "docker volume ls".
Any data which is not in a volume is lost.
Running the command with no options also removes one-off containers created by `docker compose run`:
```console
$ docker compose rm
Going to remove djangoquickstart_web_run_1
Are you sure? [yN] y
Removing djangoquickstart_web_run_1 ... done
```
Any data which is not in a volume will be lost.
### Options
| Name | Type | Default | Description |
|:------------------|:-------|:--------|:----------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `-f`, `--force` | `bool` | | Don't ask to confirm removal |
| `-s`, `--stop` | `bool` | | Stop the containers, if required, before removing |
| `-v`, `--volumes` | `bool` | | Remove any anonymous volumes attached to containers |
| Name | Type | Default | Description |
|:------------------|:-----|:--------|:----------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `-f`, `--force` | | | Don't ask to confirm removal |
| `-s`, `--stop` | | | Stop the containers, if required, before removing |
| `-v`, `--volumes` | | | Remove any anonymous volumes attached to containers |
<!---MARKER_GEN_END-->

View File

@@ -1,85 +1,33 @@
# docker compose run
<!---MARKER_GEN_START-->
Runs a one-time command against a service.
The following command starts the `web` service and runs `bash` as its command:
```console
$ docker compose run web bash
```
Commands you use with run start in new containers with configuration defined by that of the service,
including volumes, links, and other details. However, there are two important differences:
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
`python app.py`.
The second difference is that the `docker compose run` command does not create any of the ports specified in the
service configuration. This prevents port collisions with already-open ports. If you do want the services ports
to be created and mapped to the host, specify the `--service-ports`
```console
$ docker compose run --service-ports web python manage.py shell
```
Alternatively, manual port mapping can be specified with the `--publish` or `-p` options, just as when using docker run:
```console
$ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
```
If you start a service configured with links, the run command first checks to see if the linked service is running
and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
passed it. For example, you could run:
```console
$ docker compose run db psql -h db -U docker
```
This opens an interactive PostgreSQL shell for the linked `db` container.
If you do not want the run command to start linked containers, use the `--no-deps` flag:
```console
$ docker compose run --no-deps web python manage.py shell
```
If you want to remove the container after running while overriding the containers restart policy, use the `--rm` flag:
```console
$ docker compose run --rm web python manage.py db upgrade
```
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
specified in the service configuration.
Run a one-off command on a service.
### Options
| Name | Type | Default | Description |
|:------------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
| `--build` | `bool` | | Build image before starting container |
| `--cap-add` | `list` | | Add Linux capabilities |
| `--cap-drop` | `list` | | Drop Linux capabilities |
| `-d`, `--detach` | `bool` | | Run container in background and print container ID |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--entrypoint` | `string` | | Override the entrypoint of the image |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `-i`, `--interactive` | `bool` | `true` | Keep STDIN open even if not attached |
| `-l`, `--label` | `stringArray` | | Add or override a label |
| `--name` | `string` | | Assign a name to the container |
| `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation (default: auto-detected) |
| `--no-deps` | `bool` | | Don't start linked services |
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
| `--quiet-pull` | `bool` | | Pull without printing progress information |
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
| `--rm` | `bool` | | Automatically remove the container when it exits |
| `-P`, `--service-ports` | `bool` | | Run command with all service's ports enabled and mapped to the host |
| `--use-aliases` | `bool` | | Use the service's network useAliases in the network(s) the container connects to |
| `-u`, `--user` | `string` | | Run as specified username or uid |
| `-v`, `--volume` | `stringArray` | | Bind mount a volume |
| `-w`, `--workdir` | `string` | | Working directory inside the container |
| Name | Type | Default | Description |
|:----------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
| `--build` | | | Build image before starting container. |
| `--cap-add` | `list` | | Add Linux capabilities |
| `--cap-drop` | `list` | | Drop Linux capabilities |
| `-d`, `--detach` | | | Run container in background and print container ID |
| `--dry-run` | | | Execute command in dry run mode |
| `--entrypoint` | `string` | | Override the entrypoint of the image |
| `-e`, `--env` | `stringArray` | | Set environment variables |
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
| `-l`, `--label` | `stringArray` | | Add or override a label |
| `--name` | `string` | | Assign a name to the container |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
| `--no-deps` | | | Don't start linked services. |
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
| `--quiet-pull` | | | Pull without printing progress information. |
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
| `--rm` | | | Automatically remove the container when it exits |
| `--service-ports` | | | Run command with the service's ports enabled and mapped to the host. |
| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to. |
| `-u`, `--user` | `string` | | Run as specified username or uid |
| `-v`, `--volume` | `stringArray` | | Bind mount a volume. |
| `-w`, `--workdir` | `string` | | Working directory inside the container |
<!---MARKER_GEN_END-->

View File

@@ -1,15 +0,0 @@
# docker compose scale
<!---MARKER_GEN_START-->
Scale services
### Options
| Name | Type | Default | Description |
|:------------|:-------|:--------|:--------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--no-deps` | `bool` | | Don't start linked services |
<!---MARKER_GEN_END-->

View File

@@ -1,17 +1,17 @@
# docker compose start
<!---MARKER_GEN_START-->
Starts existing containers for a service
Start services
### Options
| Name | Type | Default | Description |
|:------------|:-------|:--------|:--------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
<!---MARKER_GEN_END-->
## Description
Starts existing containers for a service
Starts existing containers for a service.

View File

@@ -1,18 +0,0 @@
# docker compose stats
<!---MARKER_GEN_START-->
Display a live stream of container(s) resource usage statistics
### Options
| Name | Type | Default | Description |
|:--------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-a`, `--all` | `bool` | | Show all containers (default shows just running) |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--format` | `string` | | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
| `--no-stream` | `bool` | | Disable streaming stats and only pull the first result |
| `--no-trunc` | `bool` | | Do not truncate output |
<!---MARKER_GEN_END-->

Some files were not shown because too many files have changed in this diff Show More