Merge branch 'v2' into 8768-avoid-pulling-same-image-multiple-times
This commit is contained in:
commit
960453fa22
|
|
@ -1,3 +1,2 @@
|
||||||
.git/
|
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ jobs:
|
||||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Checkout code into the Go module directory
|
- name: Checkout code into the Go module directory
|
||||||
|
|
@ -42,6 +42,12 @@ jobs:
|
||||||
name: docker-compose-linux-amd64
|
name: docker-compose-linux-amd64
|
||||||
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
||||||
|
|
||||||
|
- name: Upload linux-ppc64le binary
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: docker-compose-linux-ppc64le
|
||||||
|
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
|
||||||
|
|
||||||
- name: Upload windows-amd64 binary
|
- name: Upload windows-amd64 binary
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- v2
|
- v2
|
||||||
pull_request:
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
debug_enabled:
|
||||||
|
description: 'To run with tmate enter "debug_enabled"'
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|
@ -13,16 +19,16 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Checkout code into the Go module directory
|
- name: Checkout code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Validate go-mod is up-to-date and license headers
|
- name: Validate go-mod, license headers and docs are up-to-date
|
||||||
run: make validate
|
run: make validate
|
||||||
|
|
||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
|
|
@ -40,10 +46,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Checkout code into the Go module directory
|
- name: Checkout code into the Go module directory
|
||||||
|
|
@ -65,10 +71,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Setup docker CLI
|
- name: Setup docker CLI
|
||||||
|
|
@ -90,7 +96,7 @@ jobs:
|
||||||
- name: Build for local E2E
|
- name: Build for local E2E
|
||||||
env:
|
env:
|
||||||
BUILD_TAGS: e2e
|
BUILD_TAGS: e2e
|
||||||
run: make -f builder.Makefile compose-plugin
|
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||||
|
|
||||||
- name: E2E Test in plugin mode
|
- name: E2E Test in plugin mode
|
||||||
run: make e2e-compose
|
run: make e2e-compose
|
||||||
|
|
@ -101,10 +107,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GO111MODULE: "on"
|
GO111MODULE: "on"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Setup docker CLI
|
- name: Setup docker CLI
|
||||||
|
|
@ -123,7 +129,17 @@ jobs:
|
||||||
- name: Build for local E2E
|
- name: Build for local E2E
|
||||||
env:
|
env:
|
||||||
BUILD_TAGS: e2e
|
BUILD_TAGS: e2e
|
||||||
run: make -f builder.Makefile compose-plugin
|
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||||
|
|
||||||
|
- name: Setup tmate session
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||||
|
|
||||||
- name: E2E Test in standalone mode
|
- name: E2E Test in standalone mode
|
||||||
run: make e2e-compose-standalone
|
run: |
|
||||||
|
rm -f /usr/local/bin/docker-compose
|
||||||
|
cp bin/docker-compose /usr/local/bin
|
||||||
|
make e2e-compose-standalone
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
open-pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout docs repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
|
repository: docker/docker.github.io
|
||||||
|
ref: master
|
||||||
|
-
|
||||||
|
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 }}
|
||||||
|
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 }}`
|
||||||
|
labels: area/Compose
|
||||||
|
draft: false
|
||||||
|
|
@ -11,10 +11,10 @@ jobs:
|
||||||
upload-release:
|
upload-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.17
|
- name: Set up Go 1.18
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17
|
go-version: 1.18.3
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Setup docker CLI
|
- name: Setup docker CLI
|
||||||
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
||||||
|
|
||||||
- name: Compute checksums
|
- name: Compute checksums
|
||||||
run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
|
run: cd bin; for f in *; do shasum --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
|
||||||
|
|
||||||
- name: License
|
- name: License
|
||||||
run: cp packaging/* bin/
|
run: cp packaging/* bin/
|
||||||
|
|
@ -44,7 +44,8 @@ jobs:
|
||||||
- uses: ncipollo/release-action@v1
|
- uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
artifacts: "bin/*"
|
artifacts: "bin/*"
|
||||||
prerelease: true
|
generateReleaseNotes: true
|
||||||
|
draft: true
|
||||||
commit: "v2"
|
commit: "v2"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.event.inputs.tag }}
|
tag: ${{ github.event.inputs.tag }}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
|
run:
|
||||||
|
concurrency: 2
|
||||||
linters:
|
linters:
|
||||||
run:
|
|
||||||
concurrency: 2
|
|
||||||
skip-dirs:
|
|
||||||
- tests/composefiles
|
|
||||||
enable-all: false
|
enable-all: false
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- deadcode
|
- deadcode
|
||||||
|
- depguard
|
||||||
- errcheck
|
- errcheck
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gofmt
|
- gofmt
|
||||||
|
|
@ -26,6 +25,13 @@ linters:
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
depguard:
|
||||||
|
list-type: blacklist
|
||||||
|
include-go-root: true
|
||||||
|
packages:
|
||||||
|
# The io/ioutil package has been deprecated.
|
||||||
|
# https://go.dev/doc/go1.16#ioutil
|
||||||
|
- io/ioutil
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 16
|
min-complexity: 16
|
||||||
lll:
|
lll:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
|
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
|
||||||
* make
|
* make
|
||||||
* Linux:
|
* Linux:
|
||||||
* [Docker 19.03 or later](https://docs.docker.com/engine/install/)
|
* [Docker 20.10 or later](https://docs.docker.com/engine/install/)
|
||||||
* make
|
* make
|
||||||
|
|
||||||
### Building the CLI
|
### Building the CLI
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ don't get discouraged! Our contributor's guide explains
|
||||||
<tr>
|
<tr>
|
||||||
<td>Community Slack</td>
|
<td>Community Slack</td>
|
||||||
<td>
|
<td>
|
||||||
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://dockercommunity.slack.com/ssb/redirect" target="_blank">with this link</a>.
|
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://www.docker.com/docker-community" target="_blank">with this link</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -15,7 +15,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
ARG GO_VERSION=1.17-alpine
|
ARG GO_VERSION=1.18.3-alpine
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
|
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
|
||||||
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ RUN --mount=target=. \
|
||||||
make -f builder.Makefile test
|
make -f builder.Makefile test
|
||||||
|
|
||||||
FROM base AS check-license-headers
|
FROM base AS check-license-headers
|
||||||
RUN go get -u github.com/kunalkushwaha/ltag
|
RUN go install github.com/kunalkushwaha/ltag@latest
|
||||||
RUN --mount=target=. \
|
RUN --mount=target=. \
|
||||||
make -f builder.Makefile check-license-headers
|
make -f builder.Makefile check-license-headers
|
||||||
|
|
||||||
|
|
@ -105,3 +105,9 @@ COPY --from=make-go-mod-tidy /compose-cli/go.sum .
|
||||||
FROM base AS check-go-mod
|
FROM base AS check-go-mod
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make -f builder.Makefile check-go-mod
|
RUN make -f builder.Makefile check-go-mod
|
||||||
|
|
||||||
|
# docs-reference is a target used as remote context to update docs on release
|
||||||
|
# with latest changes on docker.github.io.
|
||||||
|
# see open-pr job in .github/workflows/docs.yml for more details
|
||||||
|
FROM scratch AS docs-reference
|
||||||
|
COPY docs/reference/*.yaml .
|
||||||
|
|
|
||||||
28
Makefile
28
Makefile
|
|
@ -43,12 +43,19 @@ compose-plugin: ## Compile the compose cli-plugin
|
||||||
|
|
||||||
.PHONY: e2e-compose
|
.PHONY: e2e-compose
|
||||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||||
|
docker compose version
|
||||||
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||||
|
|
||||||
.PHONY: e2e-compose-standalone
|
.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
|
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||||
go test $(TEST_FLAGS) -count=1 --tags=standalone ./pkg/e2e
|
docker-compose version
|
||||||
|
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||||
|
|
||||||
|
.PHONY: mocks
|
||||||
|
mocks:
|
||||||
|
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
|
||||||
|
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
|
||||||
|
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
|
||||||
|
|
||||||
.PHONY: e2e
|
.PHONY: e2e
|
||||||
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||||
|
|
@ -78,6 +85,23 @@ lint: ## run linter(s)
|
||||||
--build-arg GIT_TAG=$(GIT_TAG) \
|
--build-arg GIT_TAG=$(GIT_TAG) \
|
||||||
--target lint
|
--target lint
|
||||||
|
|
||||||
|
.PHONY: docs
|
||||||
|
docs: ## generate documentation
|
||||||
|
$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
|
||||||
|
docker build . \
|
||||||
|
--output type=local,dest=$($@_TMP_OUT) \
|
||||||
|
-f ./docs/docs.Dockerfile \
|
||||||
|
--target update
|
||||||
|
rm -rf ./docs/internal
|
||||||
|
cp -R "$($@_TMP_OUT)"/out/* ./docs/
|
||||||
|
rm -rf "$($@_TMP_OUT)"/*
|
||||||
|
|
||||||
|
.PHONY: validate-docs
|
||||||
|
validate-docs: ## validate the doc does not change
|
||||||
|
@docker build . \
|
||||||
|
-f ./docs/docs.Dockerfile \
|
||||||
|
--target validate
|
||||||
|
|
||||||
.PHONY: check-dependencies
|
.PHONY: check-dependencies
|
||||||
check-dependencies: ## check dependency updates
|
check-dependencies: ## check dependency updates
|
||||||
go list -u -m -f '{{if not .Indirect}}{{if .Update}}{{.}}{{end}}{{end}}' all
|
go list -u -m -f '{{if not .Indirect}}{{if .Update}}{{.}}{{end}}{{end}}' all
|
||||||
|
|
@ -94,7 +118,7 @@ go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and g
|
||||||
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
||||||
@docker build . --target check-go-mod
|
@docker build . --target check-go-mod
|
||||||
|
|
||||||
validate: validate-go-mod validate-headers ## Validate sources
|
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||||
|
|
||||||
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
|
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ compose-plugin:
|
||||||
.PHONY: cross
|
.PHONY: cross
|
||||||
cross:
|
cross:
|
||||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
||||||
|
GOOS=linux GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./cmd
|
||||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
||||||
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
||||||
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
||||||
|
|
@ -70,7 +71,3 @@ check-license-headers:
|
||||||
.PHONY: check-go-mod
|
.PHONY: check-go-mod
|
||||||
check-go-mod:
|
check-go-mod:
|
||||||
./scripts/validate/check-go-mod
|
./scripts/validate/check-go-mod
|
||||||
|
|
||||||
.PHONY: yamldocs
|
|
||||||
yamldocs:
|
|
||||||
go run docs/yaml/main/generate.go
|
|
||||||
|
|
@ -50,7 +50,7 @@ func Convert(args []string) []string {
|
||||||
l := len(args)
|
l := len(args)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
arg := args[i]
|
arg := args[i]
|
||||||
if arg[0] != '-' {
|
if len(arg) > 0 && arg[0] != '-' {
|
||||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||||
if arg == compose.PluginName {
|
if arg == compose.PluginName {
|
||||||
i++
|
i++
|
||||||
|
|
@ -58,17 +58,18 @@ func Convert(args []string) []string {
|
||||||
command = append(command, args[i:]...)
|
command = append(command, args[i:]...)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if arg == "--verbose" {
|
|
||||||
|
switch arg {
|
||||||
|
case "--verbose":
|
||||||
arg = "--debug"
|
arg = "--debug"
|
||||||
}
|
case "-h":
|
||||||
if arg == "-h" {
|
|
||||||
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
||||||
arg = "--help"
|
arg = "--help"
|
||||||
}
|
case "--version", "-v":
|
||||||
if arg == "--version" || arg == "-v" {
|
|
||||||
// redirect --version pseudo-command to actual command
|
// redirect --version pseudo-command to actual command
|
||||||
arg = "version"
|
arg = "version"
|
||||||
}
|
}
|
||||||
|
|
||||||
if contains(getBoolFlags(), arg) {
|
if contains(getBoolFlags(), arg) {
|
||||||
rootFlags = append(rootFlags, arg)
|
rootFlags = append(rootFlags, arg)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,21 @@ func Test_convert(t *testing.T) {
|
||||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||||
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "compose --verbose",
|
||||||
|
args: []string{"--verbose"},
|
||||||
|
want: []string{"--debug", "compose"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "compose --version",
|
name: "compose --version",
|
||||||
args: []string{"--version"},
|
args: []string{"--version"},
|
||||||
want: []string{"compose", "version"},
|
want: []string{"compose", "version"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "compose -v",
|
||||||
|
args: []string{"-v"},
|
||||||
|
want: []string{"compose", "version"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "help",
|
name: "help",
|
||||||
args: []string{"-h"},
|
args: []string{"-h"},
|
||||||
|
|
@ -68,6 +78,11 @@ func Test_convert(t *testing.T) {
|
||||||
args: []string{"--log-level", "INFO", "up"},
|
args: []string{"--log-level", "INFO", "up"},
|
||||||
want: []string{"--log-level", "INFO", "compose", "up"},
|
want: []string{"--log-level", "INFO", "compose", "up"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "empty string argument",
|
||||||
|
args: []string{"--project-directory", "", "ps"},
|
||||||
|
want: []string{"compose", "--project-directory", "", "ps"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/cli"
|
"github.com/compose-spec/compose-go/cli"
|
||||||
|
"github.com/compose-spec/compose-go/loader"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
buildx "github.com/docker/buildx/util/progress"
|
buildx "github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
|
|
@ -40,6 +41,28 @@ type buildOptions struct {
|
||||||
args []string
|
args []string
|
||||||
noCache bool
|
noCache bool
|
||||||
memory string
|
memory string
|
||||||
|
ssh string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||||
|
var SSHKeys []types.SSHKey
|
||||||
|
var err error
|
||||||
|
if opts.ssh != "" {
|
||||||
|
SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
|
||||||
|
if err != nil {
|
||||||
|
return api.BuildOptions{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.BuildOptions{
|
||||||
|
Pull: opts.pull,
|
||||||
|
Progress: opts.progress,
|
||||||
|
Args: types.NewMappingWithEquals(opts.args),
|
||||||
|
NoCache: opts.noCache,
|
||||||
|
Quiet: opts.quiet,
|
||||||
|
Services: services,
|
||||||
|
SSHs: SSHKeys,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var printerModes = []string{
|
var printerModes = []string{
|
||||||
|
|
@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||||
|
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
|
||||||
|
opts.ssh = "default"
|
||||||
|
}
|
||||||
return runBuild(ctx, backend, opts, args)
|
return runBuild(ctx, backend, opts, args)
|
||||||
}),
|
}),
|
||||||
ValidArgsFunction: serviceCompletion(p),
|
ValidArgsFunction: serviceCompletion(p),
|
||||||
|
|
@ -82,6 +108,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||||
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||||
|
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||||
|
|
@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return backend.Build(ctx, project, api.BuildOptions{
|
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||||
Pull: opts.pull,
|
if err != nil {
|
||||||
Progress: opts.progress,
|
return err
|
||||||
Args: types.NewMappingWithEquals(opts.args),
|
}
|
||||||
NoCache: opts.noCache,
|
return backend.Build(ctx, project, apiBuildOptions)
|
||||||
Quiet: opts.quiet,
|
|
||||||
Services: services,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,21 @@ import (
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/cli"
|
"github.com/compose-spec/compose-go/cli"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||||
dockercli "github.com/docker/cli/cli"
|
dockercli "github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli-plugins/manager"
|
"github.com/docker/cli/cli-plugins/manager"
|
||||||
"github.com/docker/compose/v2/cmd/formatter"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/docker/compose/v2/cmd/formatter"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/compose"
|
"github.com/docker/compose/v2/pkg/compose"
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command defines a compose CLI command as a func with args
|
// Command defines a compose CLI command as a func with args
|
||||||
|
|
@ -86,9 +90,6 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning is a global warning to be displayed to user on command failure
|
|
||||||
var Warning string
|
|
||||||
|
|
||||||
type projectOptions struct {
|
type projectOptions struct {
|
||||||
ProjectName string
|
ProjectName string
|
||||||
Profiles []string
|
Profiles []string
|
||||||
|
|
@ -129,8 +130,8 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||||
f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
|
f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
|
||||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose 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 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.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||||
_ = f.MarkHidden("workdir")
|
_ = f.MarkHidden("workdir")
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +141,11 @@ func (o *projectOptions) toProjectName() (string, error) {
|
||||||
return o.ProjectName, nil
|
return o.ProjectName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||||
|
if envProjectName != "" {
|
||||||
|
return envProjectName, nil
|
||||||
|
}
|
||||||
|
|
||||||
project, err := o.toProject(nil)
|
project, err := o.toProject(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -158,13 +164,16 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||||
return nil, compose.WrapComposeError(err)
|
return nil, compose.WrapComposeError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
|
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
|
||||||
compose.Separator = "_"
|
compose.Separator = "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
ef := o.EnvFile
|
ef := o.EnvFile
|
||||||
if ef != "" && !filepath.IsAbs(ef) {
|
if ef != "" && !filepath.IsAbs(ef) {
|
||||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
ef, err = filepath.Abs(ef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for i, s := range project.Services {
|
for i, s := range project.Services {
|
||||||
s.CustomLabels = map[string]string{
|
s.CustomLabels = map[string]string{
|
||||||
|
|
@ -205,9 +214,9 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
|
||||||
return cli.NewProjectOptions(o.ConfigPaths,
|
return cli.NewProjectOptions(o.ConfigPaths,
|
||||||
append(po,
|
append(po,
|
||||||
cli.WithWorkingDirectory(o.ProjectDir),
|
cli.WithWorkingDirectory(o.ProjectDir),
|
||||||
|
cli.WithOsEnv,
|
||||||
cli.WithEnvFile(o.EnvFile),
|
cli.WithEnvFile(o.EnvFile),
|
||||||
cli.WithDotEnv,
|
cli.WithDotEnv,
|
||||||
cli.WithOsEnv,
|
|
||||||
cli.WithConfigFileEnv,
|
cli.WithConfigFileEnv,
|
||||||
cli.WithDefaultConfigPath,
|
cli.WithDefaultConfigPath,
|
||||||
cli.WithName(o.ProjectName))...)
|
cli.WithName(o.ProjectName))...)
|
||||||
|
|
@ -222,7 +231,7 @@ func RunningAsStandalone() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootCommand returns the compose command with its child commands
|
// RootCommand returns the compose command with its child commands
|
||||||
func RootCommand(backend api.Service) *cobra.Command {
|
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||||
opts := projectOptions{}
|
opts := projectOptions{}
|
||||||
var (
|
var (
|
||||||
ansi string
|
ansi string
|
||||||
|
|
@ -249,6 +258,10 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := setEnvWithDotEnv(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
parent := cmd.Root()
|
parent := cmd.Root()
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
parentPrerun := parent.PersistentPreRunE
|
parentPrerun := parent.PersistentPreRunE
|
||||||
|
|
@ -264,12 +277,18 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||||
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
|
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
|
||||||
}
|
}
|
||||||
ansi = "never"
|
ansi = "never"
|
||||||
fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
|
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
logrus.SetLevel(logrus.TraceLevel)
|
logrus.SetLevel(logrus.TraceLevel)
|
||||||
}
|
}
|
||||||
formatter.SetANSIMode(ansi)
|
formatter.SetANSIMode(ansi)
|
||||||
|
switch ansi {
|
||||||
|
case "never":
|
||||||
|
progress.Mode = progress.ModePlain
|
||||||
|
case "tty":
|
||||||
|
progress.Mode = progress.ModeTTY
|
||||||
|
}
|
||||||
if opts.WorkDir != "" {
|
if opts.WorkDir != "" {
|
||||||
if opts.ProjectDir != "" {
|
if opts.ProjectDir != "" {
|
||||||
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
|
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
|
||||||
|
|
@ -292,9 +311,9 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||||
logsCommand(&opts, backend),
|
logsCommand(&opts, backend),
|
||||||
convertCommand(&opts, backend),
|
convertCommand(&opts, backend),
|
||||||
killCommand(&opts, backend),
|
killCommand(&opts, backend),
|
||||||
runCommand(&opts, backend),
|
runCommand(&opts, dockerCli, backend),
|
||||||
removeCommand(&opts, backend),
|
removeCommand(&opts, backend),
|
||||||
execCommand(&opts, backend),
|
execCommand(&opts, dockerCli, backend),
|
||||||
pauseCommand(&opts, backend),
|
pauseCommand(&opts, backend),
|
||||||
unpauseCommand(&opts, backend),
|
unpauseCommand(&opts, backend),
|
||||||
topCommand(&opts, backend),
|
topCommand(&opts, backend),
|
||||||
|
|
@ -319,3 +338,27 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||||
command.Flags().MarkHidden("verbose") //nolint:errcheck
|
command.Flags().MarkHidden("verbose") //nolint:errcheck
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setEnvWithDotEnv(prjOpts *projectOptions) error {
|
||||||
|
options, err := prjOpts.toProjectOptions()
|
||||||
|
if err != nil {
|
||||||
|
return compose.WrapComposeError(err)
|
||||||
|
}
|
||||||
|
workingDir, err := options.GetWorkingDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFile)
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||||
opts.source = args[0]
|
opts.source = args[0]
|
||||||
opts.destination = args[1]
|
opts.destination = args[1]
|
||||||
return runCopy(ctx, backend, opts)
|
return runCopy(ctx, backend, opts)
|
||||||
|
|
@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := copyCmd.Flags()
|
flags := copyCmd.Flags()
|
||||||
flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
|
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.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.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)")
|
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
@ -59,10 +59,11 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||||
return runDown(ctx, backend, opts)
|
return runDown(ctx, backend, opts)
|
||||||
}),
|
}),
|
||||||
|
Args: cobra.NoArgs,
|
||||||
ValidArgsFunction: noCompletion(),
|
ValidArgsFunction: noCompletion(),
|
||||||
}
|
}
|
||||||
flags := downCmd.Flags()
|
flags := downCmd.Flags()
|
||||||
removeOrphans := strings.ToLower(os.Getenv("COMPOSE_REMOVE_ORPHANS ")) == "true"
|
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS"))
|
||||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,10 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
"github.com/containerd/console"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/compose"
|
"github.com/docker/compose/v2/pkg/compose"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -37,14 +35,15 @@ type execOpts struct {
|
||||||
environment []string
|
environment []string
|
||||||
workingDir string
|
workingDir string
|
||||||
|
|
||||||
noTty bool
|
noTty bool
|
||||||
user string
|
user string
|
||||||
detach bool
|
detach bool
|
||||||
index int
|
index int
|
||||||
privileged bool
|
privileged bool
|
||||||
|
interactive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||||
opts := execOpts{
|
opts := execOpts{
|
||||||
composeOptions: &composeOptions{
|
composeOptions: &composeOptions{
|
||||||
projectOptions: p,
|
projectOptions: p,
|
||||||
|
|
@ -70,9 +69,14 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
|
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().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().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
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().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().MarkHidden("interactive") //nolint:errcheck
|
||||||
|
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||||
|
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||||
|
|
||||||
runCmd.Flags().SetInterspersed(false)
|
runCmd.Flags().SetInterspersed(false)
|
||||||
return runCmd
|
return runCmd
|
||||||
}
|
}
|
||||||
|
|
@ -100,27 +104,9 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||||
Index: opts.index,
|
Index: opts.index,
|
||||||
Detach: opts.detach,
|
Detach: opts.detach,
|
||||||
WorkingDir: opts.workingDir,
|
WorkingDir: opts.workingDir,
|
||||||
|
Interactive: opts.interactive,
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if execOpts.Tty {
|
|
||||||
con := console.Current()
|
|
||||||
if err := con.SetRaw(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := con.Reset(); err != nil {
|
|
||||||
fmt.Println("Unable to close the console")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
execOpts.Stdin = con
|
|
||||||
execOpts.Stdout = con
|
|
||||||
execOpts.Stderr = con
|
|
||||||
}
|
|
||||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
|
|
|
||||||
|
|
@ -19,25 +19,44 @@ package compose
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type killOptions struct {
|
||||||
|
*projectOptions
|
||||||
|
signal string
|
||||||
|
}
|
||||||
|
|
||||||
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
var opts api.KillOptions
|
opts := killOptions{
|
||||||
|
projectOptions: p,
|
||||||
|
}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "kill [options] [SERVICE...]",
|
Use: "kill [options] [SERVICE...]",
|
||||||
Short: "Force stop service containers.",
|
Short: "Force stop service containers.",
|
||||||
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
|
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||||
return backend.Kill(ctx, project, opts)
|
return runKill(ctx, backend, opts, args)
|
||||||
}),
|
}),
|
||||||
ValidArgsFunction: serviceCompletion(p),
|
ValidArgsFunction: serviceCompletion(p),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVarP(&opts.Signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
|
flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
|
||||||
|
projectName, err := opts.toProjectName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return backend.Kill(ctx, projectName, api.KillOptions{
|
||||||
|
Services: services,
|
||||||
|
Signal: opts.signal,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/compose/v2/cmd/formatter"
|
"github.com/docker/compose/v2/cmd/formatter"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
|
||||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -81,12 +82,11 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
}
|
}
|
||||||
flags := psCmd.Flags()
|
flags := psCmd.Flags()
|
||||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property. Deprecated, use --status instead")
|
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.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.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.BoolVar(&opts.Services, "services", false, "Display services")
|
flags.BoolVar(&opts.Services, "services", false, "Display services")
|
||||||
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
|
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
|
||||||
flags.Lookup("filter").Hidden = true
|
|
||||||
return psCmd
|
return psCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,14 +140,14 @@ SERVICES:
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||||
writter(containers),
|
writer(containers),
|
||||||
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writter(containers []api.ContainerSummary) func(w io.Writer) {
|
func writer(containers []api.ContainerSummary) func(w io.Writer) {
|
||||||
return func(w io.Writer) {
|
return func(w io.Writer) {
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
ports := DisplayablePorts(container)
|
ports := displayablePorts(container)
|
||||||
status := container.State
|
status := container.State
|
||||||
if status == "running" && container.Health != "" {
|
if status == "running" && container.Health != "" {
|
||||||
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
|
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
|
||||||
|
|
@ -179,72 +179,20 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type portRange struct {
|
func displayablePorts(c api.ContainerSummary) string {
|
||||||
pStart int
|
|
||||||
pEnd int
|
|
||||||
tStart int
|
|
||||||
tEnd int
|
|
||||||
IP string
|
|
||||||
protocol string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pr portRange) String() string {
|
|
||||||
var (
|
|
||||||
pub string
|
|
||||||
tgt string
|
|
||||||
)
|
|
||||||
|
|
||||||
if pr.pEnd > pr.pStart {
|
|
||||||
pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
|
|
||||||
} else if pr.pStart > 0 {
|
|
||||||
pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
|
|
||||||
}
|
|
||||||
if pr.tEnd > pr.tStart {
|
|
||||||
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
|
|
||||||
} else {
|
|
||||||
tgt = fmt.Sprintf("%d", pr.tStart)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
|
|
||||||
func DisplayablePorts(c api.ContainerSummary) string {
|
|
||||||
if c.Publishers == nil {
|
if c.Publishers == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(c.Publishers)
|
ports := make([]types.Port, len(c.Publishers))
|
||||||
|
for i, pub := range c.Publishers {
|
||||||
pr := portRange{}
|
ports[i] = types.Port{
|
||||||
ports := []string{}
|
IP: pub.URL,
|
||||||
for _, p := range c.Publishers {
|
PrivatePort: uint16(pub.TargetPort),
|
||||||
prIsRange := pr.tEnd != pr.tStart
|
PublicPort: uint16(pub.PublishedPort),
|
||||||
tOverlaps := p.TargetPort <= pr.tEnd
|
Type: pub.Protocol,
|
||||||
|
|
||||||
// Start a new port-range if:
|
|
||||||
// - the protocol is different from the current port-range
|
|
||||||
// - published or target port are not consecutive to the current port-range
|
|
||||||
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
|
|
||||||
if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
|
|
||||||
// start a new port-range, and print the previous port-range (if any)
|
|
||||||
if pr.pStart > 0 {
|
|
||||||
ports = append(ports, pr.String())
|
|
||||||
}
|
|
||||||
pr = portRange{
|
|
||||||
pStart: p.PublishedPort,
|
|
||||||
pEnd: p.PublishedPort,
|
|
||||||
tStart: p.TargetPort,
|
|
||||||
tEnd: p.TargetPort,
|
|
||||||
protocol: p.Protocol,
|
|
||||||
IP: p.URL,
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
pr.pEnd = p.PublishedPort
|
|
||||||
pr.tEnd = p.TargetPort
|
|
||||||
}
|
}
|
||||||
if pr.tStart > 0 {
|
|
||||||
ports = append(ports, pr.String())
|
return formatter2.DisplayablePorts(ports)
|
||||||
}
|
|
||||||
return strings.Join(ports, ", ")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package compose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
"github.com/docker/compose/v2/pkg/mocks"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPsPretty(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
origStdout := os.Stdout
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Stdout = origStdout
|
||||||
|
})
|
||||||
|
dir := t.TempDir()
|
||||||
|
f, err := os.Create(filepath.Join(dir, "output.txt"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create output file")
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
os.Stdout = f
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
backend := mocks.NewMockService(ctrl)
|
||||||
|
backend.EXPECT().
|
||||||
|
Ps(gomock.Eq(ctx), gomock.Any(), gomock.Any()).
|
||||||
|
DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||||
|
return []api.ContainerSummary{
|
||||||
|
{
|
||||||
|
ID: "abc123",
|
||||||
|
Name: "ABC",
|
||||||
|
Publishers: api.PortPublishers{
|
||||||
|
{
|
||||||
|
TargetPort: 8080,
|
||||||
|
PublishedPort: 8080,
|
||||||
|
Protocol: "tcp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TargetPort: 8443,
|
||||||
|
PublishedPort: 8443,
|
||||||
|
Protocol: "tcp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}).AnyTimes()
|
||||||
|
|
||||||
|
opts := psOptions{projectOptions: &projectOptions{ProjectName: "test"}}
|
||||||
|
err = runPs(ctx, backend, nil, opts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = f.Seek(0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
output := make([]byte, 256)
|
||||||
|
_, err = f.Read(output)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||||
|
}
|
||||||
|
|
@ -59,13 +59,13 @@ Any data which is not in a volume will be lost.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
||||||
project, err := opts.toProject(services)
|
project, err := opts.toProjectName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.stop {
|
if opts.stop {
|
||||||
err := backend.Stop(ctx, project.Name, api.StopOptions{
|
err := backend.Stop(ctx, project, api.StopOptions{
|
||||||
Services: services,
|
Services: services,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ package compose
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cgo "github.com/compose-spec/compose-go/cli"
|
cgo "github.com/compose-spec/compose-go/cli"
|
||||||
"github.com/compose-spec/compose-go/loader"
|
"github.com/compose-spec/compose-go/loader"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
@ -42,6 +42,7 @@ type runOptions struct {
|
||||||
Detach bool
|
Detach bool
|
||||||
Remove bool
|
Remove bool
|
||||||
noTty bool
|
noTty bool
|
||||||
|
interactive bool
|
||||||
user string
|
user string
|
||||||
workdir string
|
workdir string
|
||||||
entrypoint string
|
entrypoint string
|
||||||
|
|
@ -53,6 +54,7 @@ type runOptions struct {
|
||||||
servicePorts bool
|
servicePorts bool
|
||||||
name string
|
name string
|
||||||
noDeps bool
|
noDeps bool
|
||||||
|
ignoreOrphans bool
|
||||||
quietPull bool
|
quietPull bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,6 +63,9 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target.Tty = !opts.noTty
|
||||||
|
target.StdinOpen = opts.interactive
|
||||||
if !opts.servicePorts {
|
if !opts.servicePorts {
|
||||||
target.Ports = []types.ServicePortConfig{}
|
target.Ports = []types.ServicePortConfig{}
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +107,7 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||||
opts := runOptions{
|
opts := runOptions{
|
||||||
composeOptions: &composeOptions{
|
composeOptions: &composeOptions{
|
||||||
projectOptions: p,
|
projectOptions: p,
|
||||||
|
|
@ -134,6 +139,8 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||||
|
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||||
return runRun(ctx, backend, project, opts)
|
return runRun(ctx, backend, project, opts)
|
||||||
}),
|
}),
|
||||||
ValidArgsFunction: serviceCompletion(p),
|
ValidArgsFunction: serviceCompletion(p),
|
||||||
|
|
@ -143,7 +150,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
|
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||||
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
||||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||||
|
|
@ -155,6 +162,10 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||||
|
|
||||||
|
cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||||
|
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||||
|
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||||
|
|
||||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
@ -177,7 +188,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||||
}
|
}
|
||||||
|
|
||||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||||
return startDependencies(ctx, backend, *project, opts.Service)
|
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -199,10 +210,8 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||||
Command: opts.Command,
|
Command: opts.Command,
|
||||||
Detach: opts.Detach,
|
Detach: opts.Detach,
|
||||||
AutoRemove: opts.Remove,
|
AutoRemove: opts.Remove,
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
Tty: !opts.noTty,
|
Tty: !opts.noTty,
|
||||||
|
Interactive: opts.interactive,
|
||||||
WorkingDir: opts.workdir,
|
WorkingDir: opts.workdir,
|
||||||
User: opts.user,
|
User: opts.user,
|
||||||
Environment: opts.environment,
|
Environment: opts.environment,
|
||||||
|
|
@ -213,6 +222,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||||
Index: 0,
|
Index: 0,
|
||||||
QuietPull: opts.quietPull,
|
QuietPull: opts.quietPull,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, service := range project.Services {
|
||||||
|
if service.Name == opts.Service {
|
||||||
|
service.StdinOpen = opts.interactive
|
||||||
|
project.Services[i] = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
|
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
|
|
@ -224,7 +241,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
|
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
|
||||||
dependencies := types.Services{}
|
dependencies := types.Services{}
|
||||||
var requestedService types.ServiceConfig
|
var requestedService types.ServiceConfig
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
|
|
@ -237,8 +254,17 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||||
|
|
||||||
project.Services = dependencies
|
project.Services = dependencies
|
||||||
project.DisabledServices = append(project.DisabledServices, requestedService)
|
project.DisabledServices = append(project.DisabledServices, requestedService)
|
||||||
if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
|
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||||
|
IgnoreOrphans: ignoreOrphans,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return backend.Start(ctx, project.Name, api.StartOptions{})
|
|
||||||
|
if len(dependencies) > 0 {
|
||||||
|
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||||
|
Project: &project,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||||
return validateFlags(&up, &create)
|
return validateFlags(&up, &create)
|
||||||
}),
|
}),
|
||||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||||
create.ignoreOrphans = strings.ToLower(ignore) == "true"
|
|
||||||
if create.ignoreOrphans && create.removeOrphans {
|
if create.ignoreOrphans && create.removeOrphans {
|
||||||
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
|
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +185,9 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||||
if upOptions.attachDependencies {
|
if upOptions.attachDependencies {
|
||||||
attachTo = project.ServiceNames()
|
attachTo = project.ServiceNames()
|
||||||
}
|
}
|
||||||
|
if len(attachTo) == 0 {
|
||||||
|
attachTo = project.ServiceNames()
|
||||||
|
}
|
||||||
|
|
||||||
create := api.CreateOptions{
|
create := api.CreateOptions{
|
||||||
Services: services,
|
Services: services,
|
||||||
|
|
@ -205,6 +207,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||||
return backend.Up(ctx, project, api.UpOptions{
|
return backend.Up(ctx, project, api.UpOptions{
|
||||||
Create: create,
|
Create: create,
|
||||||
Start: api.StartOptions{
|
Start: api.StartOptions{
|
||||||
|
Project: project,
|
||||||
Attach: consumer,
|
Attach: consumer,
|
||||||
AttachTo: attachTo,
|
AttachTo: attachTo,
|
||||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func runVersion(opts versionOptions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if opts.format == formatter.JSON {
|
if opts.format == formatter.JSON {
|
||||||
fmt.Printf(`{"version":%q}\n`, internal.Version)
|
fmt.Printf("{\"version\":%q}\n", internal.Version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Docker Compose version", internal.Version)
|
fmt.Println("Docker Compose version", internal.Version)
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ func (l *logConsumer) Log(container, service, message string) {
|
||||||
}
|
}
|
||||||
p := l.getPresenter(container)
|
p := l.getPresenter(container)
|
||||||
for _, line := range strings.Split(message, "\n") {
|
for _, line := range strings.Split(message, "\n") {
|
||||||
fmt.Fprintf(l.writer, "%s %s\n", p.prefix, line) // nolint:errcheck
|
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,5 +118,5 @@ type presenter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *presenter) setPrefix(width int) {
|
func (p *presenter) setPrefix(width int) {
|
||||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s |", p.name))
|
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
cmd/main.go
11
cmd/main.go
|
|
@ -32,21 +32,16 @@ import (
|
||||||
"github.com/docker/compose/v2/pkg/compose"
|
"github.com/docker/compose/v2/pkg/compose"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
commands.Warning = "The new 'docker compose' command is currently experimental. " +
|
|
||||||
"To provide feedback or request new features please open issues at https://github.com/docker/compose"
|
|
||||||
}
|
|
||||||
|
|
||||||
func pluginMain() {
|
func pluginMain() {
|
||||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||||
lazyInit := api.NewServiceProxy()
|
lazyInit := api.NewServiceProxy()
|
||||||
cmd := commands.RootCommand(lazyInit)
|
cmd := commands.RootCommand(dockerCli, lazyInit)
|
||||||
originalPreRun := cmd.PersistentPreRunE
|
originalPreRun := cmd.PersistentPreRunE
|
||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lazyInit.WithService(compose.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile()))
|
lazyInit.WithService(compose.NewComposeService(dockerCli))
|
||||||
if originalPreRun != nil {
|
if originalPreRun != nil {
|
||||||
return originalPreRun(cmd, args)
|
return originalPreRun(cmd, args)
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +63,7 @@ func pluginMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if commands.RunningAsStandalone() {
|
if plugin.RunningStandalone() {
|
||||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||||
}
|
}
|
||||||
pluginMain()
|
pluginMain()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# syntax=docker/dockerfile:1.3-labs
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
ARG GO_VERSION=1.18.3
|
||||||
|
ARG FORMATS=md,yaml
|
||||||
|
|
||||||
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
|
||||||
|
WORKDIR /src
|
||||||
|
RUN --mount=target=. \
|
||||||
|
--mount=target=/root/.cache,type=cache \
|
||||||
|
go build -o /out/docsgen ./docs/yaml/main/generate.go
|
||||||
|
|
||||||
|
FROM --platform=${BUILDPLATFORM} alpine AS gen
|
||||||
|
RUN apk add --no-cache rsync git
|
||||||
|
WORKDIR /src
|
||||||
|
COPY --from=docsgen /out/docsgen /usr/bin
|
||||||
|
ARG FORMATS
|
||||||
|
RUN --mount=target=/context \
|
||||||
|
--mount=target=.,type=tmpfs <<EOT
|
||||||
|
set -e
|
||||||
|
rsync -a /context/. .
|
||||||
|
docsgen --formats "$FORMATS" --source "docs/reference"
|
||||||
|
mkdir /out
|
||||||
|
cp -r docs/reference /out
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM scratch AS update
|
||||||
|
COPY --from=gen /out /out
|
||||||
|
|
||||||
|
FROM gen AS validate
|
||||||
|
RUN --mount=target=/context \
|
||||||
|
--mount=target=.,type=tmpfs <<EOT
|
||||||
|
set -e
|
||||||
|
rsync -a /context/. .
|
||||||
|
git add -A
|
||||||
|
rm -rf docs/reference/*
|
||||||
|
cp -rf /out/* ./docs/
|
||||||
|
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
|
||||||
|
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
|
||||||
|
git status --porcelain -- docs/reference
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
EOT
|
||||||
|
|
@ -1,4 +1,54 @@
|
||||||
|
# docker compose
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Docker Compose
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| [`build`](compose_build.md) | Build or rebuild services |
|
||||||
|
| [`convert`](compose_convert.md) | Converts the compose file to platform's canonical format |
|
||||||
|
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||||
|
| [`create`](compose_create.md) | Creates containers for a service. |
|
||||||
|
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||||
|
| [`events`](compose_events.md) | Receive real time events from containers. |
|
||||||
|
| [`exec`](compose_exec.md) | Execute a command in a running container. |
|
||||||
|
| [`images`](compose_images.md) | List images used by the created containers |
|
||||||
|
| [`kill`](compose_kill.md) | Force stop service containers. |
|
||||||
|
| [`logs`](compose_logs.md) | View output from containers |
|
||||||
|
| [`ls`](compose_ls.md) | List running compose projects |
|
||||||
|
| [`pause`](compose_pause.md) | Pause services |
|
||||||
|
| [`port`](compose_port.md) | Print the public port for a port binding. |
|
||||||
|
| [`ps`](compose_ps.md) | List containers |
|
||||||
|
| [`pull`](compose_pull.md) | Pull service images |
|
||||||
|
| [`push`](compose_push.md) | Push service images |
|
||||||
|
| [`restart`](compose_restart.md) | Restart containers |
|
||||||
|
| [`rm`](compose_rm.md) | Removes stopped service containers |
|
||||||
|
| [`run`](compose_run.md) | Run a one-off command on a service. |
|
||||||
|
| [`start`](compose_start.md) | Start services |
|
||||||
|
| [`stop`](compose_stop.md) | Stop services |
|
||||||
|
| [`top`](compose_top.md) | Display the running processes |
|
||||||
|
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||||
|
| [`up`](compose_up.md) | Create and start containers |
|
||||||
|
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
|
||||||
|
| `--compatibility` | | | Run compose in backward compatibility mode |
|
||||||
|
| `--env-file` | `string` | | Specify an alternate environment file. |
|
||||||
|
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||||
|
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||||
|
| `--project-directory` | `string` | | Specify an alternate working directory
|
||||||
|
(default: the path of the, first specified, Compose file) |
|
||||||
|
| `-p`, `--project-name` | `string` | | Project name |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
@ -9,8 +59,8 @@ multiple services in Docker containers.
|
||||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||||
|
|
||||||
#### Specifying multiple Compose files
|
#### Specifying multiple Compose files
|
||||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||||
configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
|
configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
|
||||||
to their predecessors.
|
to their predecessors.
|
||||||
|
|
||||||
For example, consider this command line:
|
For example, consider this command line:
|
||||||
|
|
@ -30,7 +80,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- "/data"
|
- "/data"
|
||||||
```
|
```
|
||||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||||
New values, add to the `webapp` service configuration.
|
New values, add to the `webapp` service configuration.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
@ -41,22 +91,22 @@ services:
|
||||||
- DEBUG=1
|
- DEBUG=1
|
||||||
```
|
```
|
||||||
|
|
||||||
When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
|
When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
|
||||||
with `-f`. You can use the `--project-directory` option to override this base path.
|
with `-f`. You can use the `--project-directory` option to override this base path.
|
||||||
|
|
||||||
Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
|
Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
|
||||||
configuration are relative to the current working directory.
|
configuration are relative to the current working directory.
|
||||||
|
|
||||||
The `-f` flag is optional. If you don’t provide this flag on the command line, Compose traverses the working directory
|
The `-f` flag is optional. If you don’t provide this flag on the command line, Compose traverses the working directory
|
||||||
and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
|
and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
|
||||||
|
|
||||||
#### Specifying a path to a single Compose file
|
#### Specifying a path to a single Compose file
|
||||||
You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
|
You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
|
||||||
from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
|
from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
|
||||||
|
|
||||||
For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
|
For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
|
||||||
have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
|
have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
|
||||||
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
|
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
|
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
|
||||||
|
|
@ -64,17 +114,17 @@ $ docker compose -f ~/sandbox/rails/compose.yaml pull db
|
||||||
|
|
||||||
### Use `-p` to specify a project name
|
### Use `-p` to specify a project name
|
||||||
|
|
||||||
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t
|
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t
|
||||||
specify the flag, Compose uses the current directory name.
|
specify the flag, Compose uses the current directory name.
|
||||||
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
||||||
|
|
||||||
Most compose subcommand can be ran without a compose file, just passing
|
Most compose subcommand can be ran without a compose file, just passing
|
||||||
project name to retrieve the relevant resources.
|
project name to retrieve the relevant resources.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker compose -p my_project ps -a
|
$ docker compose -p my_project ps -a
|
||||||
NAME SERVICE STATUS PORTS
|
NAME SERVICE STATUS PORTS
|
||||||
my_project_demo_1 demo running
|
my_project_demo_1 demo running
|
||||||
|
|
||||||
$ docker compose -p my_project logs
|
$ docker compose -p my_project logs
|
||||||
demo_1 | PING localhost (127.0.0.1): 56 data bytes
|
demo_1 | PING localhost (127.0.0.1): 56 data bytes
|
||||||
|
|
@ -84,8 +134,8 @@ demo_1 | 64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.095 ms
|
||||||
### Use profiles to enable optional services
|
### Use profiles to enable optional services
|
||||||
|
|
||||||
Use `--profile` to specify one or more active profiles
|
Use `--profile` to specify one or more active profiles
|
||||||
Calling `docker compose --profile frontend up` will start 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.
|
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` will be 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.
|
Profiles can also be set by `COMPOSE_PROFILES` environment variable.
|
||||||
|
|
@ -99,3 +149,6 @@ Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f
|
||||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||||
|
|
||||||
If flags are explicitly set on command line, associated environment variable is ignored
|
If flags are explicitly set on command line, associated environment variable is ignored
|
||||||
|
|
||||||
|
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||||
|
containers for the project.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,30 @@
|
||||||
|
# docker compose build
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Build or rebuild services
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
|
||||||
|
| `--no-cache` | | | Do not use cache when building the image |
|
||||||
|
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
|
||||||
|
| `--pull` | | | Always attempt to pull a newer version of the image. |
|
||||||
|
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
|
||||||
|
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## 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
|
If the Compose file specifies an
|
||||||
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
|
[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
|
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).
|
[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,
|
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||||
run `docker compose build` to rebuild it.
|
run `docker compose build` to rebuild it.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,35 @@
|
||||||
|
# docker compose convert
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Converts the compose file to platform's canonical format
|
||||||
|
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
`convert`, `config`
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||||
|
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||||
|
| `--images` | | | Print the image names, one per line. |
|
||||||
|
| `--no-interpolate` | | | Don't interpolate environment variables. |
|
||||||
|
| `--no-normalize` | | | Don't normalize compose model. |
|
||||||
|
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
|
||||||
|
| `--profiles` | | | Print the profile names, one per line. |
|
||||||
|
| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
|
||||||
|
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||||
|
| `--services` | | | Print the service names, one per line. |
|
||||||
|
| `--volumes` | | | Print the volume names, one per line. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
||||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||||
fully defined Compose model.
|
fully defined Compose model.
|
||||||
|
|
||||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# docker compose cp
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Copy files/folders between a service container and the local filesystem
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||||
|
| `-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-->
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# docker compose create
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Creates containers for a service.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--build` | | | Build images before starting containers. |
|
||||||
|
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
|
||||||
|
| `--no-build` | | | Don't build an image, even if it's missing. |
|
||||||
|
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
|
# docker compose down
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Stop and remove containers, networks
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||||
|
| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
|
||||||
|
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
|
||||||
|
| `-v`, `--volumes` | | | Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
# docker compose events
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Receive real time events from containers.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--json` | | | Output events as a stream of json objects |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,26 @@
|
||||||
|
# docker compose exec
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Execute a command in a running container.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||||
|
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||||
|
| `--index` | `int` | `1` | index of the container if there are multiple instances of a service [default: 1]. |
|
||||||
|
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||||
|
| `--privileged` | | | Give extended privileges to the process. |
|
||||||
|
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||||
|
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This is the equivalent of `docker exec` targeting a Compose service.
|
This is the equivalent of `docker exec` targeting a Compose service.
|
||||||
|
|
||||||
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
|
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
|
||||||
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# docker compose images
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List images used by the created containers
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-q`, `--quiet` | | | Only display IDs |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
# docker compose kill
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Force stop service containers.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,22 @@
|
||||||
|
# docker compose logs
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
View output from containers
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-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) |
|
||||||
|
| `--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) |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
|
# docker compose ls
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List running compose projects
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-a`, `--all` | | | Show all stopped Compose projects |
|
||||||
|
| `--filter` | `filter` | | Filter output based on conditions provided. |
|
||||||
|
| `--format` | `string` | `pretty` | Format the output. Values: [pretty \| json]. |
|
||||||
|
| `-q`, `--quiet` | | | Only display IDs. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
# docker compose pause
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Pause services
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
|
# docker compose port
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Print the public port for a port binding.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--index` | `int` | `1` | index of the container if service has multiple replicas |
|
||||||
|
| `--protocol` | `string` | `tcp` | tcp or udp |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,117 @@
|
||||||
|
# docker compose ps
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List containers
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
|
||||||
|
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
|
||||||
|
| [`--format`](#format) | `string` | `pretty` | Format the output. Values: [pretty \| json] |
|
||||||
|
| `-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-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Lists containers for a Compose project, with current status and exposed ports.
|
Lists containers for a Compose project, with current status and exposed ports.
|
||||||
|
By default, both running and stopped containers are shown:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker compose ps
|
$ docker compose ps
|
||||||
NAME SERVICE STATUS PORTS
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
example_bar_1 bar exited (1)
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="format"></a> Format the output (--format)
|
||||||
|
|
||||||
|
By default, the `docker compose ps` command uses a table ("pretty") format to
|
||||||
|
show the containers. The `--format` flag allows you to specify alternative
|
||||||
|
presentations for the output. Currently supported options are `pretty` (default),
|
||||||
|
and `json`, which outputs information about the containers as a JSON array:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --format json
|
||||||
|
[{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
|
||||||
|
```
|
||||||
|
|
||||||
|
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/){:target="_blank" rel="noopener" class="_"}
|
||||||
|
to pretty-print the JSON:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --format json | jq .
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
|
||||||
|
"Name": "example-bar-1",
|
||||||
|
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
|
||||||
|
"Project": "example",
|
||||||
|
"Service": "bar",
|
||||||
|
"State": "exited",
|
||||||
|
"Health": "",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Publishers": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
|
||||||
|
"Name": "example-foo-1",
|
||||||
|
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
|
||||||
|
"Project": "example",
|
||||||
|
"Service": "foo",
|
||||||
|
"State": "running",
|
||||||
|
"Health": "",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Publishers": [
|
||||||
|
{
|
||||||
|
"URL": "0.0.0.0",
|
||||||
|
"TargetPort": 80,
|
||||||
|
"PublishedPort": 8080,
|
||||||
|
"Protocol": "tcp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="status"></a> Filter containers by status (--status)
|
||||||
|
|
||||||
|
Use the `--status` flag to filter the list of containers by status. For example,
|
||||||
|
to show only containers that are running, or only containers that have exited:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
|
|
||||||
|
$ docker compose ps --status=exited
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="filter"></a> Filter containers by status (--filter)
|
||||||
|
|
||||||
|
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
|
||||||
|
flag. The example below is the equivalent to the example from the previous section,
|
||||||
|
this time using the `--filter` flag:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --filter status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
|
|
||||||
|
$ docker compose ps --filter status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `docker compose ps` command currently only supports the `--filter status=<status>`
|
||||||
|
option, but additional filter options may be added in future.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,26 @@
|
||||||
|
# docker compose pull
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Pull service images
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--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
|
## Description
|
||||||
|
|
||||||
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
|
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
|
||||||
those images.
|
those images.
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
suppose you have this `compose.yaml`:
|
suppose you have this `compose.yaml`:
|
||||||
|
|
||||||
|
|
@ -24,8 +39,8 @@ services:
|
||||||
- db
|
- db
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
|
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
|
||||||
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
|
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
|
||||||
you would run `docker compose pull db`.
|
you would run `docker compose pull db`.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
|
@ -46,4 +61,4 @@ $ docker compose pull db
|
||||||
⠹ f63c47038e66 Waiting 9.3s
|
⠹ f63c47038e66 Waiting 9.3s
|
||||||
⠹ 77a0c198cde5 Waiting 9.3s
|
⠹ 77a0c198cde5 Waiting 9.3s
|
||||||
⠹ c8752d5b785c Waiting 9.3s
|
⠹ c8752d5b785c Waiting 9.3s
|
||||||
``̀
|
``̀`
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
# docker compose push
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Push service images
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,24 @@
|
||||||
|
# docker compose restart
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Restart containers
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
Restarts all stopped and running services.
|
Restarts all stopped and running services.
|
||||||
|
|
||||||
If you make changes to your `compose.yml` configuration, these changes are not reflected
|
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 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 a container is built, but before the container's command is executed) are not updated
|
||||||
after restarting.
|
after restarting.
|
||||||
|
|
||||||
If you are looking to configure a service's restart policy, please refer to
|
If you are looking to configure a service's restart policy, please refer to
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,23 @@
|
||||||
|
# docker compose rm
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Removes stopped service containers
|
||||||
|
|
||||||
|
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 will be lost.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-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-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,35 @@
|
||||||
|
# docker compose run
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Run a one-off command on a service.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-d`, `--detach` | | | Run container in background and print container ID |
|
||||||
|
| `--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. |
|
||||||
|
| `--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-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Runs a one-time command against a service.
|
Runs a one-time command against a service.
|
||||||
|
|
||||||
the following command starts the `web` service and runs `bash` as its command:
|
the following command starts the `web` service and runs `bash` as its command:
|
||||||
|
|
||||||
|
|
@ -12,12 +40,12 @@ $ docker compose run web bash
|
||||||
Commands you use with run start in new containers with configuration defined by that of the service,
|
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:
|
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
|
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
|
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
|
||||||
`python app.py`.
|
`python app.py`.
|
||||||
|
|
||||||
The second difference is that the `docker compose run` command does not create any of the ports specified in the
|
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 service’s ports
|
service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports
|
||||||
to be created and mapped to the host, specify the `--service-ports`
|
to be created and mapped to the host, specify the `--service-ports`
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
|
@ -30,8 +58,8 @@ Alternatively, manual port mapping can be specified with the `--publish` or `-p`
|
||||||
$ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
|
$ 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
|
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
|
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:
|
passed it. For example, you could run:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
|
|
@ -52,5 +80,5 @@ If you want to remove the container after running while overriding the container
|
||||||
$ docker compose run --rm web python manage.py db upgrade
|
$ 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
|
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
|
||||||
specified in the service configuration.
|
specified in the service configuration.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
# docker compose start
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Start services
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
# docker compose stop
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Stop services
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-t`, `--timeout` | `int` | `10` | Specify a shutdown timeout in seconds |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
# docker compose top
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Display the running processes
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
@ -9,5 +16,5 @@ Displays the running processes.
|
||||||
$ docker compose top
|
$ docker compose top
|
||||||
example_foo_1
|
example_foo_1
|
||||||
UID PID PPID C STIME TTY TIME CMD
|
UID PID PPID C STIME TTY TIME CMD
|
||||||
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
|
root 142353 142331 2 15:33 ? 00:00:00 ping localhost -c 5
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
# docker compose unpause
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Unpause services
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,35 @@
|
||||||
|
# docker compose up
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Create and start containers
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
|
||||||
|
| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
|
||||||
|
| `--attach` | `stringArray` | | Attach to service output. |
|
||||||
|
| `--attach-dependencies` | | | Attach to dependent containers. |
|
||||||
|
| `--build` | | | Build images before starting containers. |
|
||||||
|
| `-d`, `--detach` | | | Detached mode: Run containers in the background |
|
||||||
|
| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
|
||||||
|
| `--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-color` | | | Produce monochrome output. |
|
||||||
|
| `--no-deps` | | | Don't start linked services. |
|
||||||
|
| `--no-log-prefix` | | | Don't print prefix in logs. |
|
||||||
|
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||||
|
| `--no-start` | | | Don't start the services after creating them. |
|
||||||
|
| `--quiet-pull` | | | Pull without printing progress information. |
|
||||||
|
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||||
|
| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. |
|
||||||
|
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||||
|
| `-t`, `--timeout` | `int` | `10` | Use this timeout in seconds for container shutdown when attached or when containers are already running. |
|
||||||
|
| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
@ -5,12 +37,12 @@ Builds, (re)creates, starts, and attaches to containers for a service.
|
||||||
|
|
||||||
Unless they are already running, this command also starts any linked services.
|
Unless they are already running, this command also starts any linked services.
|
||||||
|
|
||||||
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
|
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
|
||||||
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
|
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
|
||||||
background and leaves them running.
|
background and leaves them running.
|
||||||
|
|
||||||
If there are existing containers for a service, and the service’s configuration or image was changed after the
|
If there are existing containers for a service, and the service’s configuration or image was changed after the
|
||||||
container’s creation, `docker compose up` picks up the changes by stopping and recreating the containers
|
container’s creation, `docker compose up` picks up the changes by stopping and recreating the containers
|
||||||
(preserving mounted volumes). To prevent Compose from picking up changes, use the `--no-recreate` flag.
|
(preserving mounted volumes). To prevent Compose from picking up changes, use the `--no-recreate` flag.
|
||||||
|
|
||||||
If you want to force Compose to stop and recreate all containers, use the `--force-recreate` flag.
|
If you want to force Compose to stop and recreate all containers, use the `--force-recreate` flag.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# docker compose version
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Show the Docker Compose version information
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-f`, `--format` | `string` | | Format the output. Values: [pretty \| json]. (Default: pretty) |
|
||||||
|
| `--short` | | | Shows only Compose's version number. |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
@ -98,6 +98,9 @@ long: |-
|
||||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||||
|
|
||||||
If flags are explicitly set on command line, associated environment variable is ignored
|
If flags are explicitly set on command line, associated environment variable is ignored
|
||||||
|
|
||||||
|
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||||
|
containers for the project.
|
||||||
usage: docker compose
|
usage: docker compose
|
||||||
pname: docker
|
pname: docker
|
||||||
plink: docker.yaml
|
plink: docker.yaml
|
||||||
|
|
@ -126,6 +129,7 @@ cname:
|
||||||
- docker compose top
|
- docker compose top
|
||||||
- docker compose unpause
|
- docker compose unpause
|
||||||
- docker compose up
|
- docker compose up
|
||||||
|
- docker compose version
|
||||||
clink:
|
clink:
|
||||||
- docker_compose_build.yaml
|
- docker_compose_build.yaml
|
||||||
- docker_compose_convert.yaml
|
- docker_compose_convert.yaml
|
||||||
|
|
@ -151,6 +155,7 @@ clink:
|
||||||
- docker_compose_top.yaml
|
- docker_compose_top.yaml
|
||||||
- docker_compose_unpause.yaml
|
- docker_compose_unpause.yaml
|
||||||
- docker_compose_up.yaml
|
- docker_compose_up.yaml
|
||||||
|
- docker_compose_version.yaml
|
||||||
options:
|
options:
|
||||||
- option: ansi
|
- option: ansi
|
||||||
value_type: string
|
value_type: string
|
||||||
|
|
@ -158,6 +163,17 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Control when to print ANSI control characters ("never"|"always"|"auto")
|
Control when to print ANSI control characters ("never"|"always"|"auto")
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: compatibility
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Run compose in backward compatibility mode
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -166,6 +182,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Specify an alternate environment file.
|
description: Specify an alternate environment file.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -176,6 +193,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Compose configuration files
|
description: Compose configuration files
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -185,6 +203,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Do not print ANSI control characters (DEPRECATED)
|
description: Do not print ANSI control characters (DEPRECATED)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -194,6 +213,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Specify a profile to enable
|
description: Specify a profile to enable
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -202,8 +222,9 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: |-
|
description: |-
|
||||||
Specify an alternate working directory
|
Specify an alternate working directory
|
||||||
(default: the path of the Compose file)
|
(default: the path of the, first specified, Compose file)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -213,6 +234,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Project name
|
description: Project name
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -222,6 +244,18 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Show more output
|
description: Show more output
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: version
|
||||||
|
shorthand: v
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Show the Docker Compose version information
|
||||||
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -231,8 +265,9 @@ options:
|
||||||
description: |-
|
description: |-
|
||||||
DEPRECATED! USE --project-directory INSTEAD.
|
DEPRECATED! USE --project-directory INSTEAD.
|
||||||
Specify an alternate working directory
|
Specify an alternate working directory
|
||||||
(default: the path of the Compose file)
|
(default: the path of the, first specified, Compose file)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Set build-time variables for services.
|
description: Set build-time variables for services.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -28,6 +29,7 @@ options:
|
||||||
default_value: "true"
|
default_value: "true"
|
||||||
description: Compress the build context using gzip. DEPRECATED
|
description: Compress the build context using gzip. DEPRECATED
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -37,6 +39,7 @@ options:
|
||||||
default_value: "true"
|
default_value: "true"
|
||||||
description: Always remove intermediate containers. DEPRECATED
|
description: Always remove intermediate containers. DEPRECATED
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -47,6 +50,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Set memory limit for the build container. Not supported on buildkit yet.
|
Set memory limit for the build container. Not supported on buildkit yet.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -56,6 +60,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Do not use cache when building the image
|
description: Do not use cache when building the image
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -66,6 +71,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Do not remove intermediate containers after a successful build. DEPRECATED
|
Do not remove intermediate containers after a successful build. DEPRECATED
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -75,6 +81,7 @@ options:
|
||||||
default_value: "true"
|
default_value: "true"
|
||||||
description: Build images in parallel. DEPRECATED
|
description: Build images in parallel. DEPRECATED
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -82,8 +89,9 @@ options:
|
||||||
- option: progress
|
- option: progress
|
||||||
value_type: string
|
value_type: string
|
||||||
default_value: auto
|
default_value: auto
|
||||||
description: Set type of progress output ("auto", "plain", "noTty")
|
description: Set type of progress output (auto, tty, plain, quiet)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -93,6 +101,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Always attempt to pull a newer version of the image.
|
description: Always attempt to pull a newer version of the image.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -103,6 +112,17 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't print anything to STDOUT
|
description: Don't print anything to STDOUT
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: ssh
|
||||||
|
value_type: string
|
||||||
|
description: |
|
||||||
|
Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ options:
|
||||||
default_value: yaml
|
default_value: yaml
|
||||||
description: 'Format the output. Values: [yaml | json]'
|
description: 'Format the output. Values: [yaml | json]'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -24,6 +25,17 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Print the service config hash, one per line.
|
description: Print the service config hash, one per line.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: images
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Print the image names, one per line.
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -33,6 +45,27 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't interpolate environment variables.
|
description: Don't interpolate environment variables.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: no-normalize
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Don't normalize compose model.
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: output
|
||||||
|
shorthand: o
|
||||||
|
value_type: string
|
||||||
|
description: Save to file (default to stdout)
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -42,6 +75,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Print the profile names, one per line.
|
description: Print the profile names, one per line.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -52,6 +86,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Only validate the configuration, don't print anything.
|
description: Only validate the configuration, don't print anything.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -61,6 +96,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Pin image tags to digests.
|
description: Pin image tags to digests.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -70,6 +106,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Print the service names, one per line.
|
description: Print the service names, one per line.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -79,6 +116,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Print the volume names, one per line.
|
description: Print the volume names, one per line.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ options:
|
||||||
value_type: bool
|
value_type: bool
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Copy to all the containers of the service.
|
description: Copy to all the containers of the service.
|
||||||
deprecated: false
|
deprecated: true
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -21,6 +22,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Archive mode (copy all uid/gid information)
|
description: Archive mode (copy all uid/gid information)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -31,16 +33,18 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Always follow symbol link in SRC_PATH
|
description: Always follow symbol link in SRC_PATH
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
- option: index
|
- option: index
|
||||||
value_type: int
|
value_type: int
|
||||||
default_value: "1"
|
default_value: "0"
|
||||||
description: |
|
description: |
|
||||||
Index of the container if there are multiple instances of a service [default: 1].
|
Index of the container if there are multiple instances of a service .
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Build images before starting containers.
|
description: Build images before starting containers.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -20,6 +21,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Recreate containers even if their configuration and image haven't changed.
|
Recreate containers even if their configuration and image haven't changed.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -29,6 +31,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't build an image, even if it's missing.
|
description: Don't build an image, even if it's missing.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -39,6 +42,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Remove containers for services not defined in the Compose file.
|
description: Remove containers for services not defined in the Compose file.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -32,6 +33,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
|
Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -42,6 +44,7 @@ options:
|
||||||
default_value: "10"
|
default_value: "10"
|
||||||
description: Specify a shutdown timeout in seconds
|
description: Specify a shutdown timeout in seconds
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -53,6 +56,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
|
Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Output events as a stream of json objects
|
description: Output events as a stream of json objects
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: 'Detached mode: Run command in the background.'
|
description: 'Detached mode: Run command in the background.'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -25,6 +26,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Set environment variables
|
description: Set environment variables
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -35,6 +37,18 @@ options:
|
||||||
description: |
|
description: |
|
||||||
index of the container if there are multiple instances of a service [default: 1].
|
index of the container if there are multiple instances of a service [default: 1].
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: interactive
|
||||||
|
shorthand: i
|
||||||
|
value_type: bool
|
||||||
|
default_value: "true"
|
||||||
|
description: Keep STDIN open even if not attached.
|
||||||
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -42,10 +56,11 @@ options:
|
||||||
- option: no-TTY
|
- option: no-TTY
|
||||||
shorthand: T
|
shorthand: T
|
||||||
value_type: bool
|
value_type: bool
|
||||||
default_value: "false"
|
default_value: "true"
|
||||||
description: |
|
description: |
|
||||||
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
|
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -55,6 +70,18 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Give extended privileges to the process.
|
description: Give extended privileges to the process.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: tty
|
||||||
|
shorthand: t
|
||||||
|
value_type: bool
|
||||||
|
default_value: "true"
|
||||||
|
description: Allocate a pseudo-TTY.
|
||||||
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -64,6 +91,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Run the command as this user.
|
description: Run the command as this user.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -73,6 +101,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Path to workdir directory for this command.
|
description: Path to workdir directory for this command.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Only display IDs
|
description: Only display IDs
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ options:
|
||||||
default_value: SIGKILL
|
default_value: SIGKILL
|
||||||
description: SIGNAL to send to the container.
|
description: SIGNAL to send to the container.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Follow log output.
|
description: Follow log output.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -20,6 +21,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Produce monochrome output.
|
description: Produce monochrome output.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -29,6 +31,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't print prefix in logs.
|
description: Don't print prefix in logs.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -38,6 +41,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -48,6 +52,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Number of lines to show from the end of the logs for each container.
|
Number of lines to show from the end of the logs for each container.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -58,6 +63,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Show timestamps.
|
description: Show timestamps.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -67,6 +73,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Show all stopped Compose projects
|
description: Show all stopped Compose projects
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -19,6 +20,7 @@ options:
|
||||||
value_type: filter
|
value_type: filter
|
||||||
description: Filter output based on conditions provided.
|
description: Filter output based on conditions provided.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -28,6 +30,7 @@ options:
|
||||||
default_value: pretty
|
default_value: pretty
|
||||||
description: 'Format the output. Values: [pretty | json].'
|
description: 'Format the output. Values: [pretty | json].'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -38,6 +41,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Only display IDs.
|
description: Only display IDs.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
command: docker compose pause
|
command: docker compose pause
|
||||||
short: pause services
|
short: Pause services
|
||||||
long: |
|
long: |
|
||||||
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
|
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
|
||||||
usage: docker compose pause [SERVICE...]
|
usage: docker compose pause [SERVICE...]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ options:
|
||||||
default_value: "1"
|
default_value: "1"
|
||||||
description: index of the container if service has multiple replicas
|
description: index of the container if service has multiple replicas
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -19,6 +20,7 @@ options:
|
||||||
default_value: tcp
|
default_value: tcp
|
||||||
description: tcp or udp
|
description: tcp or udp
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ command: docker compose ps
|
||||||
short: List containers
|
short: List containers
|
||||||
long: |-
|
long: |-
|
||||||
Lists containers for a Compose project, with current status and exposed ports.
|
Lists containers for a Compose project, with current status and exposed ports.
|
||||||
|
By default, both running and stopped containers are shown:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker compose ps
|
$ docker compose ps
|
||||||
NAME SERVICE STATUS PORTS
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
example_bar_1 bar exited (1)
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
```
|
```
|
||||||
usage: docker compose ps [SERVICE...]
|
usage: docker compose ps [SERVICE...]
|
||||||
pname: docker compose
|
pname: docker compose
|
||||||
|
|
@ -20,14 +21,17 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Show all stopped containers (including those created by the run command)
|
Show all stopped containers (including those created by the run command)
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
- option: filter
|
- option: filter
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Filter services by a property
|
description: 'Filter services by a property (supported filters: status).'
|
||||||
|
details_url: '#filter'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -36,7 +40,9 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
default_value: pretty
|
default_value: pretty
|
||||||
description: 'Format the output. Values: [pretty | json]'
|
description: 'Format the output. Values: [pretty | json]'
|
||||||
|
details_url: '#format'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -47,6 +53,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Only display IDs
|
description: Only display IDs
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -56,18 +63,108 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Display services
|
description: Display services
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
- option: status
|
- option: status
|
||||||
value_type: string
|
value_type: stringArray
|
||||||
description: Filter services by status
|
default_value: '[]'
|
||||||
|
description: |
|
||||||
|
Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
|
||||||
|
details_url: '#status'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
|
examples: |-
|
||||||
|
### Format the output (--format) {#format}
|
||||||
|
|
||||||
|
By default, the `docker compose ps` command uses a table ("pretty") format to
|
||||||
|
show the containers. The `--format` flag allows you to specify alternative
|
||||||
|
presentations for the output. Currently supported options are `pretty` (default),
|
||||||
|
and `json`, which outputs information about the containers as a JSON array:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --format json
|
||||||
|
[{"ID":"1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a","Name":"example-bar-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"bar","State":"exited","Health":"","ExitCode":0,"Publishers":null},{"ID":"f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0","Name":"example-foo-1","Command":"/docker-entrypoint.sh nginx -g 'daemon off;'","Project":"example","Service":"foo","State":"running","Health":"","ExitCode":0,"Publishers":[{"URL":"0.0.0.0","TargetPort":80,"PublishedPort":8080,"Protocol":"tcp"}]}]
|
||||||
|
```
|
||||||
|
|
||||||
|
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/){:target="_blank" rel="noopener" class="_"}
|
||||||
|
to pretty-print the JSON:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --format json | jq .
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ID": "1553b0236cf4d2715845f053a4ee97042c4f9a2ef655731ee34f1f7940eaa41a",
|
||||||
|
"Name": "example-bar-1",
|
||||||
|
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
|
||||||
|
"Project": "example",
|
||||||
|
"Service": "bar",
|
||||||
|
"State": "exited",
|
||||||
|
"Health": "",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Publishers": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "f02a4efaabb67416e1ff127d51c4b5578634a0ad5743bd65225ff7d1909a3fa0",
|
||||||
|
"Name": "example-foo-1",
|
||||||
|
"Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
|
||||||
|
"Project": "example",
|
||||||
|
"Service": "foo",
|
||||||
|
"State": "running",
|
||||||
|
"Health": "",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Publishers": [
|
||||||
|
{
|
||||||
|
"URL": "0.0.0.0",
|
||||||
|
"TargetPort": 80,
|
||||||
|
"PublishedPort": 8080,
|
||||||
|
"Protocol": "tcp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter containers by status (--status) {#status}
|
||||||
|
|
||||||
|
Use the `--status` flag to filter the list of containers by status. For example,
|
||||||
|
to show only containers that are running, or only containers that have exited:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
|
|
||||||
|
$ docker compose ps --status=exited
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter containers by status (--filter) {#filter}
|
||||||
|
|
||||||
|
The [`--status` flag](#status) is a convenience shorthand for the `--filter status=<status>`
|
||||||
|
flag. The example below is the equivalent to the example from the previous section,
|
||||||
|
this time using the `--filter` flag:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose ps --filter status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||||
|
|
||||||
|
$ docker compose ps --filter status=running
|
||||||
|
NAME COMMAND SERVICE STATUS PORTS
|
||||||
|
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `docker compose ps` command currently only supports the `--filter status=<status>`
|
||||||
|
option, but additional filter options may be added in future.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Pull what it can and ignores images with pull failures
|
description: Pull what it can and ignores images with pull failures
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -21,6 +22,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Also pull services declared as dependencies
|
description: Also pull services declared as dependencies
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -30,6 +32,7 @@ options:
|
||||||
default_value: "true"
|
default_value: "true"
|
||||||
description: DEPRECATED disable parallel pulling.
|
description: DEPRECATED disable parallel pulling.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -39,6 +42,7 @@ options:
|
||||||
default_value: "true"
|
default_value: "true"
|
||||||
description: DEPRECATED pull multiple images in parallel.
|
description: DEPRECATED pull multiple images in parallel.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -49,10 +53,52 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Pull without printing progress information
|
description: Pull without printing progress information
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
|
examples: |-
|
||||||
|
suppose you have this `compose.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
command: bundle exec rails s -p 3000 -b '0.0.0.0'
|
||||||
|
volumes:
|
||||||
|
- .:/myapp
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run `docker compose pull ServiceName` in the same directory as the `compose.yaml` file that defines the service,
|
||||||
|
Docker pulls the associated image. For example, to call the postgres image configured as the db service in our example,
|
||||||
|
you would run `docker compose pull db`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker compose pull db
|
||||||
|
[+] Running 1/15
|
||||||
|
⠸ db Pulling 12.4s
|
||||||
|
⠿ 45b42c59be33 Already exists 0.0s
|
||||||
|
⠹ 40adec129f1a Downloading 3.374MB/4.178MB 9.3s
|
||||||
|
⠹ b4c431d00c78 Download complete 9.3s
|
||||||
|
⠹ 2696974e2815 Download complete 9.3s
|
||||||
|
⠹ 564b77596399 Downloading 5.622MB/7.965MB 9.3s
|
||||||
|
⠹ 5044045cf6f2 Downloading 216.7kB/391.1kB 9.3s
|
||||||
|
⠹ d736e67e6ac3 Waiting 9.3s
|
||||||
|
⠹ 390c1c9a5ae4 Waiting 9.3s
|
||||||
|
⠹ c0e62f172284 Waiting 9.3s
|
||||||
|
⠹ ebcdc659c5bf Waiting 9.3s
|
||||||
|
⠹ 29be22cb3acc Waiting 9.3s
|
||||||
|
⠹ f63c47038e66 Waiting 9.3s
|
||||||
|
⠹ 77a0c198cde5 Waiting 9.3s
|
||||||
|
⠹ c8752d5b785c Waiting 9.3s
|
||||||
|
``̀`
|
||||||
deprecated: false
|
deprecated: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Push what it can and ignores images with push failures
|
description: Push what it can and ignores images with push failures
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
command: docker compose restart
|
command: docker compose restart
|
||||||
short: Restart containers
|
short: Restart containers
|
||||||
long: Restart containers
|
long: |-
|
||||||
|
Restarts all stopped and running services.
|
||||||
|
|
||||||
|
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, please refer to
|
||||||
|
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
|
||||||
|
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
|
||||||
usage: docker compose restart
|
usage: docker compose restart
|
||||||
pname: docker compose
|
pname: docker compose
|
||||||
plink: docker_compose.yaml
|
plink: docker_compose.yaml
|
||||||
|
|
@ -11,6 +21,7 @@ options:
|
||||||
default_value: "10"
|
default_value: "10"
|
||||||
description: Specify a shutdown timeout in seconds
|
description: Specify a shutdown timeout in seconds
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Deprecated - no effect
|
description: Deprecated - no effect
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -36,6 +37,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't ask to confirm removal
|
description: Don't ask to confirm removal
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -46,6 +48,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Stop the containers, if required, before removing
|
description: Stop the containers, if required, before removing
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -56,6 +59,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Remove any anonymous volumes attached to containers
|
description: Remove any anonymous volumes attached to containers
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Run container in background and print container ID
|
description: Run container in background and print container ID
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -73,6 +74,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Override the entrypoint of the image
|
description: Override the entrypoint of the image
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -83,16 +85,29 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Set environment variables
|
description: Set environment variables
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
- option: labels
|
- option: interactive
|
||||||
|
shorthand: i
|
||||||
|
value_type: bool
|
||||||
|
default_value: "true"
|
||||||
|
description: Keep STDIN open even if not attached.
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: label
|
||||||
shorthand: l
|
shorthand: l
|
||||||
value_type: stringArray
|
value_type: stringArray
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Add or override a label
|
description: Add or override a label
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -101,6 +116,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Assign a name to the container
|
description: Assign a name to the container
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -108,10 +124,10 @@ options:
|
||||||
- option: no-TTY
|
- option: no-TTY
|
||||||
shorthand: T
|
shorthand: T
|
||||||
value_type: bool
|
value_type: bool
|
||||||
default_value: "false"
|
default_value: "true"
|
||||||
description: |
|
description: 'Disable pseudo-TTY allocation (default: auto-detected).'
|
||||||
Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
|
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -121,6 +137,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't start linked services.
|
description: Don't start linked services.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -131,6 +148,17 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Publish a container's port(s) to the host.
|
description: Publish a container's port(s) to the host.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: quiet-pull
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Pull without printing progress information.
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -140,6 +168,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Automatically remove the container when it exits
|
description: Automatically remove the container when it exits
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -150,6 +179,18 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Run command with the service's ports enabled and mapped to the host.
|
Run command with the service's ports enabled and mapped to the host.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: tty
|
||||||
|
shorthand: t
|
||||||
|
value_type: bool
|
||||||
|
default_value: "true"
|
||||||
|
description: Allocate a pseudo-TTY.
|
||||||
|
deprecated: false
|
||||||
|
hidden: true
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -160,6 +201,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Use the service's network useAliases in the network(s) the container connects to.
|
Use the service's network useAliases in the network(s) the container connects to.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -169,6 +211,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Run as specified username or uid
|
description: Run as specified username or uid
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -179,6 +222,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Bind mount a volume.
|
description: Bind mount a volume.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -188,6 +232,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: Working directory inside the container
|
description: Working directory inside the container
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ options:
|
||||||
default_value: "10"
|
default_value: "10"
|
||||||
description: Specify a shutdown timeout in seconds
|
description: Specify a shutdown timeout in seconds
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
command: docker compose unpause
|
command: docker compose unpause
|
||||||
short: unpause services
|
short: Unpause services
|
||||||
long: Unpauses paused containers of a service.
|
long: Unpauses paused containers of a service.
|
||||||
usage: docker compose unpause [SERVICE...]
|
usage: docker compose unpause [SERVICE...]
|
||||||
pname: docker compose
|
pname: docker compose
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Stops all containers if any container was stopped. Incompatible with -d
|
Stops all containers if any container was stopped. Incompatible with -d
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -36,6 +37,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Recreate dependent containers. Incompatible with --no-recreate.
|
description: Recreate dependent containers. Incompatible with --no-recreate.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -45,6 +47,7 @@ options:
|
||||||
default_value: '[]'
|
default_value: '[]'
|
||||||
description: Attach to service output.
|
description: Attach to service output.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -54,6 +57,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Attach to dependent containers.
|
description: Attach to dependent containers.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -63,6 +67,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Build images before starting containers.
|
description: Build images before starting containers.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -73,16 +78,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: 'Detached mode: Run containers in the background'
|
description: 'Detached mode: Run containers in the background'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
experimental: false
|
hidden: false
|
||||||
experimentalcli: false
|
|
||||||
kubernetes: false
|
|
||||||
swarm: false
|
|
||||||
- option: environment
|
|
||||||
shorthand: e
|
|
||||||
value_type: stringArray
|
|
||||||
default_value: '[]'
|
|
||||||
description: Environment variables
|
|
||||||
deprecated: false
|
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -92,6 +88,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Return the exit code of the selected service container. Implies --abort-on-container-exit
|
Return the exit code of the selected service container. Implies --abort-on-container-exit
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -102,6 +99,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Recreate containers even if their configuration and image haven't changed.
|
Recreate containers even if their configuration and image haven't changed.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -111,6 +109,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't build an image, even if it's missing.
|
description: Don't build an image, even if it's missing.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -120,6 +119,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Produce monochrome output.
|
description: Produce monochrome output.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -129,6 +129,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't start linked services.
|
description: Don't start linked services.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -138,6 +139,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't print prefix in logs.
|
description: Don't print prefix in logs.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -148,6 +150,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -157,6 +160,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't start the services after creating them.
|
description: Don't start the services after creating them.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -166,6 +170,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Pull without printing progress information.
|
description: Pull without printing progress information.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -175,6 +180,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Remove containers for services not defined in the Compose file.
|
description: Remove containers for services not defined in the Compose file.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -186,6 +192,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Recreate anonymous volumes instead of retrieving data from the previous containers.
|
Recreate anonymous volumes instead of retrieving data from the previous containers.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -196,6 +203,7 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
|
Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -207,6 +215,17 @@ options:
|
||||||
description: |
|
description: |
|
||||||
Use this timeout in seconds for container shutdown when attached or when containers are already running.
|
Use this timeout in seconds for container shutdown when attached or when containers are already running.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
|
- option: wait
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Wait for services to be running|healthy. Implies detached mode.
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ options:
|
||||||
value_type: string
|
value_type: string
|
||||||
description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
|
description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
@ -19,6 +20,7 @@ options:
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Shows only Compose's version number.
|
description: Shows only Compose's version number.
|
||||||
deprecated: false
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
experimental: false
|
experimental: false
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,23 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
clidocstool "github.com/docker/cli-docs-tool"
|
clidocstool "github.com/docker/cli-docs-tool"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/compose/v2/cmd/compose"
|
"github.com/docker/compose/v2/cmd/compose"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateCliYaml(opts *options) error {
|
func generateDocs(opts *options) error {
|
||||||
cmd := &cobra.Command{Use: "docker"}
|
dockerCLI, err := command.NewDockerCli()
|
||||||
cmd.AddCommand(compose.RootCommand(nil))
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "docker",
|
||||||
|
DisableAutoGenTag: true,
|
||||||
|
}
|
||||||
|
cmd.AddCommand(compose.RootCommand(dockerCLI, nil))
|
||||||
disableFlagsInUseLine(cmd)
|
disableFlagsInUseLine(cmd)
|
||||||
|
|
||||||
cmd.DisableAutoGenTag = true
|
|
||||||
tool, err := clidocstool.New(clidocstool.Options{
|
tool, err := clidocstool.New(clidocstool.Options{
|
||||||
Root: cmd,
|
Root: cmd,
|
||||||
SourceDir: opts.source,
|
SourceDir: opts.source,
|
||||||
|
|
@ -41,7 +48,7 @@ func generateCliYaml(opts *options) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tool.GenYamlTree(cmd)
|
return tool.GenAllTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableFlagsInUseLine(cmd *cobra.Command) {
|
func disableFlagsInUseLine(cmd *cobra.Command) {
|
||||||
|
|
@ -69,12 +76,12 @@ type options struct {
|
||||||
func main() {
|
func main() {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
opts := &options{
|
opts := &options{
|
||||||
source: cwd,
|
source: filepath.Join(cwd, "docs", "reference"),
|
||||||
target: filepath.Join(cwd, "docs", "reference"),
|
target: filepath.Join(cwd, "docs", "reference"),
|
||||||
}
|
}
|
||||||
fmt.Printf("Project root: %s\n", opts.source)
|
fmt.Printf("Project root: %s\n", opts.source)
|
||||||
fmt.Printf("Generating yaml files into %s\n", opts.target)
|
fmt.Printf("Generating yaml files into %s\n", opts.target)
|
||||||
if err := generateCliYaml(opts); err != nil {
|
if err := generateDocs(opts); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error())
|
_, _ = fmt.Fprintf(os.Stderr, "Failed to generate documentation: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
go.mod
143
go.mod
|
|
@ -1,144 +1,153 @@
|
||||||
module github.com/docker/compose/v2
|
module github.com/docker/compose/v2
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||||
github.com/buger/goterm v1.0.4
|
github.com/buger/goterm v1.0.4
|
||||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
github.com/cnabio/cnab-to-oci v0.3.4
|
||||||
github.com/compose-spec/compose-go v1.1.0
|
github.com/compose-spec/compose-go v1.2.8
|
||||||
github.com/containerd/console v1.0.3
|
github.com/containerd/console v1.0.3
|
||||||
github.com/containerd/containerd v1.6.0
|
github.com/containerd/containerd v1.6.6
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||||
github.com/docker/buildx v0.7.1
|
github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
|
||||||
github.com/docker/cli v20.10.12+incompatible
|
github.com/docker/cli v20.10.17+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.2.1
|
github.com/docker/cli-docs-tool v0.4.0
|
||||||
github.com/docker/docker v20.10.7+incompatible
|
github.com/docker/docker v20.10.17+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.4.0
|
github.com/docker/go-units v0.4.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/go-version v1.3.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
|
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||||
github.com/morikuni/aec v1.0.0
|
github.com/morikuni/aec v1.0.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.0.2
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.8.0
|
||||||
|
github.com/theupdateframework/notary v0.7.0
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
gotest.tools v2.2.0+incompatible
|
gotest.tools v2.2.0+incompatible
|
||||||
gotest.tools/v3 v3.1.0
|
gotest.tools/v3 v3.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
|
github.com/cnabio/cnab-go v0.23.4 // indirect
|
||||||
github.com/containerd/continuity v0.2.2 // indirect
|
github.com/containerd/continuity v0.2.2 // indirect
|
||||||
|
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||||
github.com/containerd/typeurl v1.0.2 // indirect
|
github.com/containerd/typeurl v1.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.0+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||||
github.com/go-logr/logr v1.2.2 // indirect
|
github.com/go-logr/logr v1.2.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/gofrs/flock v0.8.0 // indirect
|
github.com/gofrs/flock v0.8.0 // indirect
|
||||||
github.com/gogo/googleapis v1.4.0 // indirect
|
github.com/gogo/googleapis v1.4.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.6 // indirect
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.13.5 // indirect
|
github.com/klauspost/compress v1.15.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/locker v1.0.1 // indirect
|
github.com/moby/locker v1.0.1 // indirect
|
||||||
github.com/moby/sys/mount v0.2.0 // indirect
|
|
||||||
github.com/moby/sys/mountinfo v0.5.0 // indirect
|
|
||||||
github.com/moby/sys/signal v0.6.0 // indirect
|
github.com/moby/sys/signal v0.6.0 // indirect
|
||||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.1.0 // indirect
|
github.com/opencontainers/runc v1.1.2 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.30.0 // indirect
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/qri-io/jsonpointer v0.1.0 // indirect
|
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||||
github.com/qri-io/jsonschema v0.1.1 // indirect
|
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
|
|
||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
go.opentelemetry.io/contrib v0.21.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
|
go.opentelemetry.io/otel v1.4.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.3.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
|
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||||
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
|
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v0.21.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.3.0 // indirect
|
go.opentelemetry.io/otel/trace v1.4.1 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.3.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.11.0 // indirect
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||||
google.golang.org/grpc v1.43.0 // indirect
|
google.golang.org/grpc v1.45.0 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/apimachinery v0.22.5 // indirect
|
k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
|
||||||
k8s.io/client-go v0.22.5 // indirect
|
k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used
|
||||||
k8s.io/klog/v2 v2.30.0 // indirect
|
k8s.io/klog/v2 v2.60.1 // indirect
|
||||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
// (for buildx)
|
require (
|
||||||
replace (
|
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
|
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
|
github.com/zmap/zlint v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939
|
)
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939
|
replace (
|
||||||
|
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
|
||||||
|
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
||||||
|
|
||||||
|
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||||
|
|
||||||
|
// For k8s dependencies, we use a replace directive, to prevent them being
|
||||||
|
// upgraded to the version specified in containerd, which is not relevant to the
|
||||||
|
// version needed.
|
||||||
|
// See https://github.com/docker/buildx/pull/948 for details.
|
||||||
|
// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
|
||||||
|
k8s.io/api => k8s.io/api v0.22.4
|
||||||
|
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||||
|
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package api
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -33,9 +32,9 @@ type Service interface {
|
||||||
// Push executes the equivalent ot a `compose push`
|
// Push executes the equivalent ot a `compose push`
|
||||||
Push(ctx context.Context, project *types.Project, options PushOptions) error
|
Push(ctx context.Context, project *types.Project, options PushOptions) error
|
||||||
// Pull executes the equivalent of a `compose pull`
|
// Pull executes the equivalent of a `compose pull`
|
||||||
Pull(ctx context.Context, project *types.Project, opts PullOptions) error
|
Pull(ctx context.Context, project *types.Project, options PullOptions) error
|
||||||
// Create executes the equivalent to a `compose create`
|
// Create executes the equivalent to a `compose create`
|
||||||
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
|
Create(ctx context.Context, project *types.Project, options CreateOptions) error
|
||||||
// Start executes the equivalent to a `compose start`
|
// Start executes the equivalent to a `compose start`
|
||||||
Start(ctx context.Context, projectName string, options StartOptions) error
|
Start(ctx context.Context, projectName string, options StartOptions) error
|
||||||
// Restart restarts containers
|
// Restart restarts containers
|
||||||
|
|
@ -55,25 +54,25 @@ type Service interface {
|
||||||
// Convert translate compose model into backend's native format
|
// Convert translate compose model into backend's native format
|
||||||
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
||||||
// Kill executes the equivalent to a `compose kill`
|
// Kill executes the equivalent to a `compose kill`
|
||||||
Kill(ctx context.Context, project *types.Project, options KillOptions) error
|
Kill(ctx context.Context, projectName string, options KillOptions) error
|
||||||
// RunOneOffContainer creates a service oneoff container and starts its dependencies
|
// RunOneOffContainer creates a service oneoff container and starts its dependencies
|
||||||
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||||
// Remove executes the equivalent to a `compose rm`
|
// Remove executes the equivalent to a `compose rm`
|
||||||
Remove(ctx context.Context, project *types.Project, options RemoveOptions) error
|
Remove(ctx context.Context, projectName string, options RemoveOptions) error
|
||||||
// Exec executes a command in a running service container
|
// Exec executes a command in a running service container
|
||||||
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
|
Exec(ctx context.Context, projectName string, options RunOptions) (int, error)
|
||||||
// Copy copies a file/folder between a service container and the local filesystem
|
// Copy copies a file/folder between a service container and the local filesystem
|
||||||
Copy(ctx context.Context, project string, options CopyOptions) error
|
Copy(ctx context.Context, projectName string, options CopyOptions) error
|
||||||
// Pause executes the equivalent to a `compose pause`
|
// Pause executes the equivalent to a `compose pause`
|
||||||
Pause(ctx context.Context, project string, options PauseOptions) error
|
Pause(ctx context.Context, projectName string, options PauseOptions) error
|
||||||
// UnPause executes the equivalent to a `compose unpause`
|
// UnPause executes the equivalent to a `compose unpause`
|
||||||
UnPause(ctx context.Context, project string, options PauseOptions) error
|
UnPause(ctx context.Context, projectName string, options PauseOptions) error
|
||||||
// Top executes the equivalent to a `compose top`
|
// Top executes the equivalent to a `compose top`
|
||||||
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||||
// Events executes the equivalent to a `compose events`
|
// Events executes the equivalent to a `compose events`
|
||||||
Events(ctx context.Context, project string, options EventsOptions) error
|
Events(ctx context.Context, projectName string, options EventsOptions) error
|
||||||
// Port executes the equivalent to a `compose port`
|
// Port executes the equivalent to a `compose port`
|
||||||
Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
|
Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
|
||||||
// Images executes the equivalent of a `compose images`
|
// Images executes the equivalent of a `compose images`
|
||||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +91,8 @@ type BuildOptions struct {
|
||||||
Quiet bool
|
Quiet bool
|
||||||
// Services passed in the command line to be built
|
// Services passed in the command line to be built
|
||||||
Services []string
|
Services []string
|
||||||
|
// Ssh authentications passed in the command line
|
||||||
|
SSHs []types.SSHKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOptions group options of the Create API
|
// CreateOptions group options of the Create API
|
||||||
|
|
@ -116,6 +117,8 @@ type CreateOptions struct {
|
||||||
|
|
||||||
// StartOptions group options of the Start API
|
// StartOptions group options of the Start API
|
||||||
type StartOptions struct {
|
type StartOptions struct {
|
||||||
|
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
|
||||||
|
Project *types.Project
|
||||||
// Attach to container and forward logs if not nil
|
// Attach to container and forward logs if not nil
|
||||||
Attach LogConsumer
|
Attach LogConsumer
|
||||||
// AttachTo set the services to attach to
|
// AttachTo set the services to attach to
|
||||||
|
|
@ -216,10 +219,8 @@ type RunOptions struct {
|
||||||
Entrypoint []string
|
Entrypoint []string
|
||||||
Detach bool
|
Detach bool
|
||||||
AutoRemove bool
|
AutoRemove bool
|
||||||
Stdin io.ReadCloser
|
|
||||||
Stdout io.WriteCloser
|
|
||||||
Stderr io.WriteCloser
|
|
||||||
Tty bool
|
Tty bool
|
||||||
|
Interactive bool
|
||||||
WorkingDir string
|
WorkingDir string
|
||||||
User string
|
User string
|
||||||
Environment []string
|
Environment []string
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ type ServiceProxy struct {
|
||||||
PsFn func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
|
PsFn func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
|
||||||
ListFn func(ctx context.Context, options ListOptions) ([]Stack, error)
|
ListFn func(ctx context.Context, options ListOptions) ([]Stack, error)
|
||||||
ConvertFn func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
ConvertFn func(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
||||||
KillFn func(ctx context.Context, project *types.Project, options KillOptions) error
|
KillFn func(ctx context.Context, project string, options KillOptions) error
|
||||||
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||||
RemoveFn func(ctx context.Context, project *types.Project, options RemoveOptions) error
|
RemoveFn func(ctx context.Context, project string, options RemoveOptions) error
|
||||||
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
|
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||||
CopyFn func(ctx context.Context, project string, options CopyOptions) error
|
CopyFn func(ctx context.Context, project string, options CopyOptions) error
|
||||||
PauseFn func(ctx context.Context, project string, options PauseOptions) error
|
PauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||||
|
|
@ -219,14 +219,11 @@ func (s *ServiceProxy) Convert(ctx context.Context, project *types.Project, opti
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill implements Service interface
|
// Kill implements Service interface
|
||||||
func (s *ServiceProxy) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
|
func (s *ServiceProxy) Kill(ctx context.Context, projectName string, options KillOptions) error {
|
||||||
if s.KillFn == nil {
|
if s.KillFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
for _, i := range s.interceptors {
|
return s.KillFn(ctx, projectName, options)
|
||||||
i(ctx, project)
|
|
||||||
}
|
|
||||||
return s.KillFn(ctx, project, options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunOneOffContainer implements Service interface
|
// RunOneOffContainer implements Service interface
|
||||||
|
|
@ -241,46 +238,43 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove implements Service interface
|
// Remove implements Service interface
|
||||||
func (s *ServiceProxy) Remove(ctx context.Context, project *types.Project, options RemoveOptions) error {
|
func (s *ServiceProxy) Remove(ctx context.Context, projectName string, options RemoveOptions) error {
|
||||||
if s.RemoveFn == nil {
|
if s.RemoveFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
for _, i := range s.interceptors {
|
return s.RemoveFn(ctx, projectName, options)
|
||||||
i(ctx, project)
|
|
||||||
}
|
|
||||||
return s.RemoveFn(ctx, project, options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec implements Service interface
|
// Exec implements Service interface
|
||||||
func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOptions) (int, error) {
|
func (s *ServiceProxy) Exec(ctx context.Context, projectName string, options RunOptions) (int, error) {
|
||||||
if s.ExecFn == nil {
|
if s.ExecFn == nil {
|
||||||
return 0, ErrNotImplemented
|
return 0, ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.ExecFn(ctx, project, options)
|
return s.ExecFn(ctx, projectName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy implements Service interface
|
// Copy implements Service interface
|
||||||
func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
|
func (s *ServiceProxy) Copy(ctx context.Context, projectName string, options CopyOptions) error {
|
||||||
if s.CopyFn == nil {
|
if s.CopyFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.CopyFn(ctx, project, options)
|
return s.CopyFn(ctx, projectName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause implements Service interface
|
// Pause implements Service interface
|
||||||
func (s *ServiceProxy) Pause(ctx context.Context, project string, options PauseOptions) error {
|
func (s *ServiceProxy) Pause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||||
if s.PauseFn == nil {
|
if s.PauseFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.PauseFn(ctx, project, options)
|
return s.PauseFn(ctx, projectName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnPause implements Service interface
|
// UnPause implements Service interface
|
||||||
func (s *ServiceProxy) UnPause(ctx context.Context, project string, options PauseOptions) error {
|
func (s *ServiceProxy) UnPause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||||
if s.UnPauseFn == nil {
|
if s.UnPauseFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.UnPauseFn(ctx, project, options)
|
return s.UnPauseFn(ctx, projectName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top implements Service interface
|
// Top implements Service interface
|
||||||
|
|
@ -292,19 +286,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events implements Service interface
|
// Events implements Service interface
|
||||||
func (s *ServiceProxy) Events(ctx context.Context, project string, options EventsOptions) error {
|
func (s *ServiceProxy) Events(ctx context.Context, projectName string, options EventsOptions) error {
|
||||||
if s.EventsFn == nil {
|
if s.EventsFn == nil {
|
||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.EventsFn(ctx, project, options)
|
return s.EventsFn(ctx, projectName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port implements Service interface
|
// Port implements Service interface
|
||||||
func (s *ServiceProxy) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
|
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
|
||||||
if s.PortFn == nil {
|
if s.PortFn == nil {
|
||||||
return "", 0, ErrNotImplemented
|
return "", 0, ErrNotImplemented
|
||||||
}
|
}
|
||||||
return s.PortFn(ctx, project, service, port, options)
|
return s.PortFn(ctx, projectName, service, port, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Images implements Service interface
|
// Images implements Service interface
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||||
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
err := s.attachContainer(ctx, container, listener, project)
|
err := s.attachContainer(ctx, container, listener)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -56,13 +56,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||||
return containers, err
|
return containers, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener, project *types.Project) error {
|
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener) error {
|
||||||
serviceName := container.Labels[api.ServiceLabel]
|
serviceName := container.Labels[api.ServiceLabel]
|
||||||
containerName := getContainerNameWithoutProject(container)
|
containerName := getContainerNameWithoutProject(container)
|
||||||
service, err := project.GetService(serviceName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
listener(api.ContainerEvent{
|
listener(api.ContainerEvent{
|
||||||
Type: api.ContainerEventAttach,
|
Type: api.ContainerEventAttach,
|
||||||
|
|
@ -78,7 +74,13 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||||
Line: line,
|
Line: line,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w, w)
|
|
||||||
|
inspect, err := s.dockerCli.Client().ContainerInspect(ctx, container.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, w, w)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +139,7 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
|
||||||
func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
|
func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
|
||||||
var stdout io.ReadCloser
|
var stdout io.ReadCloser
|
||||||
var stdin io.WriteCloser
|
var stdin io.WriteCloser
|
||||||
cnx, err := s.apiClient.ContainerAttach(ctx, container, moby.ContainerAttachOptions{
|
cnx, err := s.apiClient().ContainerAttach(ctx, container, moby.ContainerAttachOptions{
|
||||||
Stream: true,
|
Stream: true,
|
||||||
Stdin: true,
|
Stdin: true,
|
||||||
Stdout: true,
|
Stdout: true,
|
||||||
|
|
@ -151,7 +153,7 @@ func (s *composeService) getContainerStreams(ctx context.Context, container stri
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to logs API
|
// Fallback to logs API
|
||||||
logs, err := s.apiClient.ContainerLogs(ctx, container, moby.ContainerLogsOptions{
|
logs, err := s.apiClient().ContainerLogs(ctx, container, moby.ContainerLogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package compose
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
|
@ -28,11 +27,12 @@ import (
|
||||||
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
|
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
xprogress "github.com/docker/buildx/util/progress"
|
xprogress "github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
bclient "github.com/moby/buildkit/client"
|
bclient "github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/session"
|
"github.com/moby/buildkit/session"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||||
|
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
|
@ -64,7 +64,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||||
if service.Build != nil {
|
if service.Build != nil {
|
||||||
imageName := getImageName(service, project.Name)
|
imageName := getImageName(service, project.Name)
|
||||||
imagesToBuild = append(imagesToBuild, imageName)
|
imagesToBuild = append(imagesToBuild, imageName)
|
||||||
buildOptions, err := s.toBuildOptions(project, service, imageName)
|
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||||
Attrs: map[string]string{"ref": image},
|
Attrs: map[string]string{"ref": image},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
opts[imageName] = buildOptions
|
opts[imageName] = buildOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +160,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
|
||||||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
opt, err := s.toBuildOptions(project, service, imageName)
|
opt, err := s.toBuildOptions(project, service, imageName, []types.SSHKey{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -189,37 +188,29 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||||
for name, info := range imgs {
|
for name, info := range imgs {
|
||||||
images[name] = info.ID
|
images[name] = info.ID
|
||||||
}
|
}
|
||||||
return images, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
|
for _, s := range project.Services {
|
||||||
ping, err := s.apiClient.Ping(ctx)
|
imgName := getImageName(s, project.Name)
|
||||||
if err != nil {
|
digest, ok := images[imgName]
|
||||||
return command.ServerInfo{}, err
|
if ok {
|
||||||
|
s.CustomLabels[api.ImageDigestLabel] = digest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
serverInfo := command.ServerInfo{
|
|
||||||
HasExperimental: ping.Experimental,
|
return images, nil
|
||||||
OSType: ping.OSType,
|
|
||||||
BuildkitVersion: ping.BuilderVersion,
|
|
||||||
}
|
|
||||||
return serverInfo, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||||
if len(opts) == 0 {
|
if len(opts) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
serverInfo, err := s.serverInfo(ctx)
|
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
|
||||||
if err != nil {
|
return s.doBuildClassic(ctx, project, opts)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
|
|
||||||
return s.doBuildClassic(ctx, opts)
|
|
||||||
}
|
}
|
||||||
return s.doBuildBuildkit(ctx, project, opts, mode)
|
return s.doBuildBuildkit(ctx, project, opts, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
|
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
|
||||||
var tags []string
|
var tags []string
|
||||||
tags = append(tags, imageTag)
|
tags = append(tags, imageTag)
|
||||||
|
|
||||||
|
|
@ -244,11 +235,59 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||||
plats = append(plats, p)
|
plats = append(plats, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
|
||||||
|
if err != nil {
|
||||||
|
return build.Options{}, err
|
||||||
|
}
|
||||||
|
cacheTo, err := buildflags.ParseCacheEntry(service.Build.CacheTo)
|
||||||
|
if err != nil {
|
||||||
|
return build.Options{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionConfig := []session.Attachable{
|
||||||
|
authprovider.NewDockerAuthProvider(s.stderr()),
|
||||||
|
}
|
||||||
|
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
|
||||||
|
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
|
||||||
|
if err != nil {
|
||||||
|
return build.Options{}, err
|
||||||
|
}
|
||||||
|
sessionConfig = append(sessionConfig, sshAgentProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(service.Build.Secrets) > 0 {
|
||||||
|
var sources []secretsprovider.Source
|
||||||
|
for _, secret := range service.Build.Secrets {
|
||||||
|
config := project.Secrets[secret.Source]
|
||||||
|
if config.File == "" {
|
||||||
|
return build.Options{}, fmt.Errorf("build.secrets only supports file-based secrets: %q", secret.Source)
|
||||||
|
}
|
||||||
|
sources = append(sources, secretsprovider.Source{
|
||||||
|
ID: secret.Source,
|
||||||
|
FilePath: config.File,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
store, err := secretsprovider.NewStore(sources)
|
||||||
|
if err != nil {
|
||||||
|
return build.Options{}, err
|
||||||
|
}
|
||||||
|
p := secretsprovider.NewSecretProvider(store)
|
||||||
|
sessionConfig = append(sessionConfig, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(service.Build.Tags) > 0 {
|
||||||
|
tags = append(tags, service.Build.Tags...)
|
||||||
|
}
|
||||||
|
|
||||||
return build.Options{
|
return build.Options{
|
||||||
Inputs: build.Inputs{
|
Inputs: build.Inputs{
|
||||||
ContextPath: service.Build.Context,
|
ContextPath: service.Build.Context,
|
||||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||||
},
|
},
|
||||||
|
CacheFrom: cacheFrom,
|
||||||
|
CacheTo: cacheTo,
|
||||||
|
NoCache: service.Build.NoCache,
|
||||||
|
Pull: service.Build.Pull,
|
||||||
BuildArgs: buildArgs,
|
BuildArgs: buildArgs,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Target: service.Build.Target,
|
Target: service.Build.Target,
|
||||||
|
|
@ -256,10 +295,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||||
Platforms: plats,
|
Platforms: plats,
|
||||||
Labels: service.Build.Labels,
|
Labels: service.Build.Labels,
|
||||||
NetworkMode: service.Build.Network,
|
NetworkMode: service.Build.Network,
|
||||||
ExtraHosts: service.Build.ExtraHosts,
|
ExtraHosts: service.Build.ExtraHosts.AsList(),
|
||||||
Session: []session.Attachable{
|
Session: sessionConfig,
|
||||||
authprovider.NewDockerAuthProvider(os.Stderr),
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,3 +330,14 @@ func dockerFilePath(context string, dockerfile string) string {
|
||||||
}
|
}
|
||||||
return filepath.Join(context, dockerfile)
|
return filepath.Join(context, dockerfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
|
||||||
|
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
|
||||||
|
for _, sshKey := range sshKeys {
|
||||||
|
sshConfig = append(sshConfig, sshprovider.AgentConfig{
|
||||||
|
ID: sshKey.ID,
|
||||||
|
Paths: []string{sshKey.Path},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sshprovider.NewSSHAgentProvider(sshConfig)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
|
|
||||||
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||||
const drivername = "default"
|
const drivername = "default"
|
||||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, nil, nil, nil, project.WorkingDir)
|
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient(), s.configFile(), nil, nil, nil, nil, nil, project.WorkingDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -45,10 +45,10 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
|
||||||
// build and will lock
|
// build and will lock
|
||||||
progressCtx, cancel := context.WithCancel(context.Background())
|
progressCtx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
|
w := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
|
||||||
|
|
||||||
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
||||||
response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile.Filename), w)
|
response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile().Filename), w)
|
||||||
errW := w.Wait()
|
errW := w.Wait()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errW
|
err = errW
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/compose-spec/compose-go/types"
|
||||||
buildx "github.com/docker/buildx/build"
|
buildx "github.com/docker/buildx/build"
|
||||||
"github.com/docker/cli/cli/command/image/build"
|
"github.com/docker/cli/cli/command/image/build"
|
||||||
dockertypes "github.com/docker/docker/api/types"
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
|
|
@ -41,15 +41,24 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) doBuildClassic(ctx context.Context, opts map[string]buildx.Options) (map[string]string, error) {
|
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, opts map[string]buildx.Options) (map[string]string, error) {
|
||||||
var nameDigests = make(map[string]string)
|
var nameDigests = make(map[string]string)
|
||||||
var errs error
|
var errs error
|
||||||
for name, o := range opts {
|
err := project.WithServices(nil, func(service types.ServiceConfig) error {
|
||||||
|
imageName := getImageName(service, project.Name)
|
||||||
|
o, ok := opts[imageName]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
digest, err := s.doBuildClassicSimpleImage(ctx, o)
|
digest, err := s.doBuildClassicSimpleImage(ctx, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = multierror.Append(errs, err).ErrorOrNil()
|
errs = multierror.Append(errs, err).ErrorOrNil()
|
||||||
}
|
}
|
||||||
nameDigests[name] = digest
|
nameDigests[imageName] = digest
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameDigests, errs
|
return nameDigests, errs
|
||||||
|
|
@ -69,8 +78,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
|
|
||||||
dockerfileName := options.Inputs.DockerfilePath
|
dockerfileName := options.Inputs.DockerfilePath
|
||||||
specifiedContext := options.Inputs.ContextPath
|
specifiedContext := options.Inputs.ContextPath
|
||||||
progBuff := os.Stdout
|
progBuff := s.stdout()
|
||||||
buildBuff := os.Stdout
|
buildBuff := s.stdout()
|
||||||
if options.ImageIDFile != "" {
|
if options.ImageIDFile != "" {
|
||||||
// Avoid leaving a stale file if we eventually fail
|
// Avoid leaving a stale file if we eventually fail
|
||||||
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
|
||||||
|
|
@ -143,19 +152,10 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if up to this point nothing has set the context then we must have another
|
|
||||||
// way for sending it(streaming) and set the context to the Dockerfile
|
|
||||||
if dockerfileCtx != nil && buildCtx == nil {
|
|
||||||
buildCtx = dockerfileCtx
|
|
||||||
}
|
|
||||||
|
|
||||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||||
var body io.Reader
|
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||||
if buildCtx != nil {
|
|
||||||
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
|
||||||
}
|
|
||||||
|
|
||||||
configFile := s.configFile
|
configFile := s.configFile()
|
||||||
creds, err := configFile.GetAllCredentials()
|
creds, err := configFile.GetAllCredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -171,7 +171,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
|
response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -181,13 +181,13 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
aux := func(msg jsonmessage.JSONMessage) {
|
aux := func(msg jsonmessage.JSONMessage) {
|
||||||
var result dockertypes.BuildResult
|
var result dockertypes.BuildResult
|
||||||
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to parse aux message: %s", err)
|
fmt.Fprintf(s.stderr(), "Failed to parse aux message: %s", err)
|
||||||
} else {
|
} else {
|
||||||
imageID = result.ID
|
imageID = result.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.Fd(), true, aux)
|
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||||
// If no error code is set, default to 1
|
// If no error code is set, default to 1
|
||||||
|
|
@ -203,7 +203,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
// daemon isn't running Windows.
|
// daemon isn't running Windows.
|
||||||
if response.OSType != "windows" && runtime.GOOS == "windows" {
|
if response.OSType != "windows" && runtime.GOOS == "windows" {
|
||||||
// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
// if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
||||||
fmt.Fprintln(os.Stdout, "SECURITY WARNING: You are building a Docker "+
|
fmt.Fprintln(s.stdout(), "SECURITY WARNING: You are building a Docker "+
|
||||||
"image from Windows against a non-Windows Docker host. All files and "+
|
"image from Windows against a non-Windows Docker host. All files and "+
|
||||||
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
||||||
"It is recommended to double check and reset permissions for sensitive "+
|
"It is recommended to double check and reset permissions for sensitive "+
|
||||||
|
|
@ -214,7 +214,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||||
if imageID == "" {
|
if imageID == "" {
|
||||||
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
|
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
|
if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sanathkr/go-yaml"
|
"github.com/sanathkr/go-yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,19 +40,41 @@ import (
|
||||||
var Separator = "-"
|
var Separator = "-"
|
||||||
|
|
||||||
// NewComposeService create a local implementation of the compose.Service API
|
// NewComposeService create a local implementation of the compose.Service API
|
||||||
func NewComposeService(apiClient client.APIClient, configFile *configfile.ConfigFile) api.Service {
|
func NewComposeService(dockerCli command.Cli) api.Service {
|
||||||
return &composeService{
|
return &composeService{
|
||||||
apiClient: apiClient,
|
dockerCli: dockerCli,
|
||||||
configFile: configFile,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type composeService struct {
|
type composeService struct {
|
||||||
apiClient client.APIClient
|
dockerCli command.Cli
|
||||||
configFile *configfile.ConfigFile
|
}
|
||||||
|
|
||||||
|
func (s *composeService) apiClient() client.APIClient {
|
||||||
|
return s.dockerCli.Client()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) configFile() *configfile.ConfigFile {
|
||||||
|
return s.dockerCli.ConfigFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) stdout() *streams.Out {
|
||||||
|
return s.dockerCli.Out()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) stdin() *streams.In {
|
||||||
|
return s.dockerCli.In()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) stderr() io.Writer {
|
||||||
|
return s.dockerCli.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCanonicalContainerName(c moby.Container) string {
|
func getCanonicalContainerName(c moby.Container) string {
|
||||||
|
if len(c.Names) == 0 {
|
||||||
|
// corner case, sometime happens on removal. return short ID as a safeguard value
|
||||||
|
return c.ID[:12]
|
||||||
|
}
|
||||||
// Names return container canonical name /foo + link aliases /linked_by/foo
|
// Names return container canonical name /foo + link aliases /linked_by/foo
|
||||||
for _, name := range c.Names {
|
for _, name := range c.Names {
|
||||||
if strings.LastIndex(name, "/") == 0 {
|
if strings.LastIndex(name, "/") == 0 {
|
||||||
|
|
@ -100,7 +125,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||||
Name: projectName,
|
Name: projectName,
|
||||||
}
|
}
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
return project, errors.New("no such project: " + projectName)
|
return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
|
||||||
}
|
}
|
||||||
set := map[string]*types.ServiceConfig{}
|
set := map[string]*types.ServiceConfig{}
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
|
|
@ -140,7 +165,7 @@ SERVICES:
|
||||||
continue SERVICES
|
continue SERVICES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return project, errors.New("no such service: " + qs)
|
return project, errors.Wrapf(api.ErrNotFound, "no such service: %q", qs)
|
||||||
}
|
}
|
||||||
err := project.ForServices(services)
|
err := project.ForServices(services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -149,3 +174,59 @@ SERVICES:
|
||||||
|
|
||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// actualState list resources labelled by projectName to rebuild compose project model
|
||||||
|
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
|
||||||
|
var containers Containers
|
||||||
|
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
|
||||||
|
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err := s.projectFromName(containers, projectName, services...)
|
||||||
|
if err != nil && !api.IsNotFoundError(err) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(services) > 0 {
|
||||||
|
containers = containers.filter(isService(services...))
|
||||||
|
}
|
||||||
|
return containers, project, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
|
||||||
|
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := types.Volumes{}
|
||||||
|
for _, vol := range volumes.Volumes {
|
||||||
|
actual[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
|
||||||
|
Name: vol.Name,
|
||||||
|
Driver: vol.Driver,
|
||||||
|
Labels: vol.Labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) {
|
||||||
|
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||||
|
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := types.Networks{}
|
||||||
|
for _, net := range networks {
|
||||||
|
actual[net.Labels[api.NetworkLabel]] = types.NetworkConfig{
|
||||||
|
Name: net.Name,
|
||||||
|
Driver: net.Driver,
|
||||||
|
Labels: net.Labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,13 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
moby "github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
|
moby "github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Containers is a set of moby Container
|
// Containers is a set of moby Container
|
||||||
|
|
@ -41,18 +40,8 @@ const (
|
||||||
|
|
||||||
func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
|
func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
|
||||||
var containers Containers
|
var containers Containers
|
||||||
f := []filters.KeyValuePair{projectFilter(project)}
|
f := getDefaultFilters(project, oneOff, selectedServices...)
|
||||||
if len(selectedServices) == 1 {
|
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||||
f = append(f, serviceFilter(selectedServices[0]))
|
|
||||||
}
|
|
||||||
switch oneOff {
|
|
||||||
case oneOffOnly:
|
|
||||||
f = append(f, oneOffFilter(true))
|
|
||||||
case oneOffExclude:
|
|
||||||
f = append(f, oneOffFilter(false))
|
|
||||||
case oneOffInclude:
|
|
||||||
}
|
|
||||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
|
||||||
Filters: filters.NewArgs(f...),
|
Filters: filters.NewArgs(f...),
|
||||||
All: stopped,
|
All: stopped,
|
||||||
})
|
})
|
||||||
|
|
@ -65,6 +54,40 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
|
||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) []filters.KeyValuePair {
|
||||||
|
f := []filters.KeyValuePair{projectFilter(projectName)}
|
||||||
|
if len(selectedServices) == 1 {
|
||||||
|
f = append(f, serviceFilter(selectedServices[0]))
|
||||||
|
}
|
||||||
|
switch oneOff {
|
||||||
|
case oneOffOnly:
|
||||||
|
f = append(f, oneOffFilter(true))
|
||||||
|
case oneOffExclude:
|
||||||
|
f = append(f, oneOffFilter(false))
|
||||||
|
case oneOffInclude:
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
|
||||||
|
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
|
||||||
|
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
|
||||||
|
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||||
|
Filters: filters.NewArgs(
|
||||||
|
defaultFilters...,
|
||||||
|
),
|
||||||
|
All: stopped,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return moby.Container{}, err
|
||||||
|
}
|
||||||
|
if len(containers) < 1 {
|
||||||
|
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
|
||||||
|
}
|
||||||
|
container := containers[0]
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
|
||||||
// containerPredicate define a predicate we want container to satisfy for filtering operations
|
// containerPredicate define a predicate we want container to satisfy for filtering operations
|
||||||
type containerPredicate func(c moby.Container) bool
|
type containerPredicate func(c moby.Container) bool
|
||||||
|
|
||||||
|
|
@ -87,14 +110,6 @@ func isNotOneOff(c moby.Container) bool {
|
||||||
return !ok || v == "False"
|
return !ok || v == "False"
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexed(index int) containerPredicate {
|
|
||||||
return func(c moby.Container) bool {
|
|
||||||
number := c.Labels[api.ContainerNumberLabel]
|
|
||||||
idx, err := strconv.Atoi(number)
|
|
||||||
return err == nil && index == idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter return Containers with elements to match predicate
|
// filter return Containers with elements to match predicate
|
||||||
func (containers Containers) filter(predicate containerPredicate) Containers {
|
func (containers Containers) filter(predicate containerPredicate) Containers {
|
||||||
var filtered Containers
|
var filtered Containers
|
||||||
|
|
|
||||||
|
|
@ -180,26 +180,20 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
// Scale Down
|
// Scale Down
|
||||||
container := container
|
container := container
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout)
|
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.service.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
|
return c.service.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if recreate == api.RecreateNever {
|
mustRecreate, err := mustRecreate(service, container, recreate)
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Re-create diverged containers
|
|
||||||
configHash, err := ServiceHash(service)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
name := getContainerProgressName(container)
|
if mustRecreate {
|
||||||
diverged := container.Labels[api.ConfigHashLabel] != configHash
|
|
||||||
if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
|
|
||||||
i, container := i, container
|
i, container := i, container
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
|
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
|
||||||
|
|
@ -211,6 +205,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
|
|
||||||
// Enforce non-diverged containers are running
|
// Enforce non-diverged containers are running
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
|
name := getContainerProgressName(container)
|
||||||
switch container.State {
|
switch container.State {
|
||||||
case ContainerRunning:
|
case ContainerRunning:
|
||||||
w.Event(progress.RunningEvent(name))
|
w.Event(progress.RunningEvent(name))
|
||||||
|
|
@ -249,6 +244,22 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
|
||||||
|
if policy == api.RecreateNever {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if policy == api.RecreateForce || expected.Extensions[extLifecycle] == forceRecreate {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
configHash, err := ServiceHash(expected)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
|
||||||
|
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
|
||||||
|
return configChanged || imageUpdated, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
|
func getContainerName(projectName string, service types.ServiceConfig, number int) string {
|
||||||
name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
|
name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
|
||||||
if service.ContainerName != "" {
|
if service.ContainerName != "" {
|
||||||
|
|
@ -395,13 +406,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||||
var created moby.Container
|
var created moby.Container
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||||
err := s.apiClient.ContainerStop(ctx, replaced.ID, timeout)
|
err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
name := getCanonicalContainerName(replaced)
|
name := getCanonicalContainerName(replaced)
|
||||||
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
||||||
err = s.apiClient.ContainerRename(ctx, replaced.ID, tmpName)
|
err = s.apiClient().ContainerRename(ctx, replaced.ID, tmpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
@ -419,7 +430,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
err = s.apiClient.ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
|
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +455,7 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
|
||||||
func (s *composeService) startContainer(ctx context.Context, container moby.Container) error {
|
func (s *composeService) startContainer(ctx context.Context, container moby.Container) error {
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Restart"))
|
w.Event(progress.NewEvent(getContainerProgressName(container), progress.Working, "Restart"))
|
||||||
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -468,11 +479,11 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||||
}
|
}
|
||||||
plat = &p
|
plat = &p
|
||||||
}
|
}
|
||||||
response, err := s.apiClient.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
|
response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
inspectedContainer, err := s.apiClient.ContainerInspect(ctx, response.ID)
|
inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
@ -502,7 +513,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||||
if shortIDAliasExists(created.ID, val.Aliases...) {
|
if shortIDAliasExists(created.ID, val.Aliases...) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = s.apiClient.NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
|
err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
@ -512,6 +523,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,7 +609,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
||||||
IPv6Address: ipv6Address,
|
IPv6Address: ipv6Address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.apiClient.NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
||||||
Aliases: aliases,
|
Aliases: aliases,
|
||||||
IPAddress: ipv4Address,
|
IPAddress: ipv4Address,
|
||||||
GlobalIPv6Address: ipv6Address,
|
GlobalIPv6Address: ipv6Address,
|
||||||
|
|
@ -619,7 +632,7 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
|
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -651,7 +664,7 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
||||||
return false, 0, err
|
return false, 0, err
|
||||||
}
|
}
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
|
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, err
|
return false, 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -671,7 +684,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||||
Filters: filters.NewArgs(
|
Filters: filters.NewArgs(
|
||||||
projectFilter(project.Name),
|
projectFilter(project.Name),
|
||||||
serviceFilter(service.Name),
|
serviceFilter(service.Name),
|
||||||
|
|
@ -700,7 +713,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
eventName := getContainerProgressName(container)
|
eventName := getContainerProgressName(container)
|
||||||
w.Event(progress.StartingEvent(eventName))
|
w.Event(progress.StartingEvent(eventName))
|
||||||
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
err := s.apiClient().ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.Event(progress.StartedEvent(eventName))
|
w.Event(progress.StartedEvent(eventName))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,11 @@ func TestServiceLinks(t *testing.T) {
|
||||||
t.Run("service links default", func(t *testing.T) {
|
t.Run("service links default", func(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = apiClient
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
s.Links = []string{"db"}
|
s.Links = []string{"db"}
|
||||||
|
|
||||||
|
|
@ -95,7 +98,9 @@ func TestServiceLinks(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = apiClient
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
s.Links = []string{"db:db"}
|
s.Links = []string{"db:db"}
|
||||||
|
|
||||||
|
|
@ -115,7 +120,9 @@ func TestServiceLinks(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = apiClient
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
s.Links = []string{"db:dbname"}
|
s.Links = []string{"db:dbname"}
|
||||||
|
|
||||||
|
|
@ -135,7 +142,9 @@ func TestServiceLinks(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = apiClient
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
s.Links = []string{"db:dbname"}
|
s.Links = []string{"db:dbname"}
|
||||||
s.ExternalLinks = []string{"db1:db2"}
|
s.ExternalLinks = []string{"db1:db2"}
|
||||||
|
|
@ -159,7 +168,9 @@ func TestServiceLinks(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = apiClient
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
s.Links = []string{}
|
s.Links = []string{}
|
||||||
s.ExternalLinks = []string{}
|
s.ExternalLinks = []string{}
|
||||||
|
|
@ -189,8 +200,11 @@ func TestServiceLinks(t *testing.T) {
|
||||||
func TestWaitDependencies(t *testing.T) {
|
func TestWaitDependencies(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
api := mocks.NewMockAPIClient(mockCtrl)
|
|
||||||
tested.apiClient = api
|
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||||
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||||
|
|
||||||
t.Run("should skip dependencies with scale 0", func(t *testing.T) {
|
t.Run("should skip dependencies with scale 0", func(t *testing.T) {
|
||||||
dbService := types.ServiceConfig{Name: "db", Scale: 0}
|
dbService := types.ServiceConfig{Name: "db", Scale: 0}
|
||||||
|
|
|
||||||
|
|
@ -42,59 +42,80 @@ const (
|
||||||
acrossServices = fromService | toService
|
acrossServices = fromService | toService
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
|
func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
||||||
srcService, srcPath := splitCpArg(opts.Source)
|
projectName = strings.ToLower(projectName)
|
||||||
destService, dstPath := splitCpArg(opts.Destination)
|
srcService, srcPath := splitCpArg(options.Source)
|
||||||
|
destService, dstPath := splitCpArg(options.Destination)
|
||||||
|
|
||||||
var direction copyDirection
|
var direction copyDirection
|
||||||
var serviceName string
|
var serviceName string
|
||||||
|
var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
|
||||||
if srcService != "" {
|
if srcService != "" {
|
||||||
direction |= fromService
|
direction |= fromService
|
||||||
serviceName = srcService
|
serviceName = srcService
|
||||||
|
copyFunc = s.copyFromContainer
|
||||||
|
|
||||||
// copying from multiple containers of a services doesn't make sense.
|
// copying from multiple containers of a services doesn't make sense.
|
||||||
if opts.All {
|
if options.All {
|
||||||
return errors.New("cannot use the --all flag when copying from a service")
|
return errors.New("cannot use the --all flag when copying from a service")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if destService != "" {
|
if destService != "" {
|
||||||
direction |= toService
|
direction |= toService
|
||||||
serviceName = destService
|
serviceName = destService
|
||||||
|
copyFunc = s.copyToContainer
|
||||||
|
}
|
||||||
|
if direction == acrossServices {
|
||||||
|
return errors.New("copying between services is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
|
if direction == 0 {
|
||||||
|
return errors.New("unknown copy direction")
|
||||||
|
}
|
||||||
|
|
||||||
|
containers, err := s.listContainersTargetedForCopy(ctx, projectName, options.Index, direction, serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(containers) < 1 {
|
|
||||||
return fmt.Errorf("no container found for service %q", serviceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.All {
|
|
||||||
containers = containers.filter(indexed(opts.Index))
|
|
||||||
}
|
|
||||||
|
|
||||||
g := errgroup.Group{}
|
g := errgroup.Group{}
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
containerID := container.ID
|
containerID := container.ID
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
switch direction {
|
return copyFunc(ctx, containerID, srcPath, dstPath, options)
|
||||||
case fromService:
|
|
||||||
return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts)
|
|
||||||
case toService:
|
|
||||||
return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts)
|
|
||||||
case acrossServices:
|
|
||||||
return errors.New("copying between services is not supported")
|
|
||||||
default:
|
|
||||||
return errors.New("unknown copy direction")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.Wait()
|
return g.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
|
||||||
|
var containers Containers
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case index > 0:
|
||||||
|
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(containers, container), nil
|
||||||
|
default:
|
||||||
|
containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(containers) < 1 {
|
||||||
|
return nil, fmt.Errorf("no container found for service %q", serviceName)
|
||||||
|
}
|
||||||
|
if direction == fromService {
|
||||||
|
return containers[:1], err
|
||||||
|
|
||||||
|
}
|
||||||
|
return containers, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
|
func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
|
||||||
var err error
|
var err error
|
||||||
if srcPath != "-" {
|
if srcPath != "-" {
|
||||||
|
|
@ -107,7 +128,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||||
|
|
||||||
// Prepare destination copy info by stat-ing the container path.
|
// Prepare destination copy info by stat-ing the container path.
|
||||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
dstInfo := archive.CopyInfo{Path: dstPath}
|
||||||
dstStat, err := s.apiClient.ContainerStatPath(ctx, containerID, dstPath)
|
dstStat, err := s.apiClient().ContainerStatPath(ctx, containerID, dstPath)
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should evaluate it.
|
// If the destination is a symbolic link, we should evaluate it.
|
||||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||||
|
|
@ -119,7 +140,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||||
}
|
}
|
||||||
|
|
||||||
dstInfo.Path = linkTarget
|
dstInfo.Path = linkTarget
|
||||||
dstStat, err = s.apiClient.ContainerStatPath(ctx, containerID, linkTarget)
|
dstStat, err = s.apiClient().ContainerStatPath(ctx, containerID, linkTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the destination path
|
// Validate the destination path
|
||||||
|
|
@ -143,7 +164,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||||
)
|
)
|
||||||
|
|
||||||
if srcPath == "-" {
|
if srcPath == "-" {
|
||||||
content = os.Stdin
|
content = s.stdin()
|
||||||
resolvedDstPath = dstInfo.Path
|
resolvedDstPath = dstInfo.Path
|
||||||
if !dstInfo.IsDir {
|
if !dstInfo.IsDir {
|
||||||
return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
|
return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
|
||||||
|
|
@ -187,7 +208,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||||
AllowOverwriteDirWithFile: false,
|
AllowOverwriteDirWithFile: false,
|
||||||
CopyUIDGID: opts.CopyUIDGID,
|
CopyUIDGID: opts.CopyUIDGID,
|
||||||
}
|
}
|
||||||
return s.apiClient.CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
|
return s.apiClient().CopyToContainer(ctx, containerID, resolvedDstPath, content, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
|
func (s *composeService) copyFromContainer(ctx context.Context, containerID, srcPath, dstPath string, opts api.CopyOptions) error {
|
||||||
|
|
@ -207,7 +228,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
|
||||||
// if client requests to follow symbol link, then must decide target file to be copied
|
// if client requests to follow symbol link, then must decide target file to be copied
|
||||||
var rebaseName string
|
var rebaseName string
|
||||||
if opts.FollowLink {
|
if opts.FollowLink {
|
||||||
srcStat, err := s.apiClient.ContainerStatPath(ctx, containerID, srcPath)
|
srcStat, err := s.apiClient().ContainerStatPath(ctx, containerID, srcPath)
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should follow it.
|
// If the destination is a symbolic link, we should follow it.
|
||||||
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
|
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
|
||||||
|
|
@ -223,14 +244,14 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, stat, err := s.apiClient.CopyFromContainer(ctx, containerID, srcPath)
|
content, stat, err := s.apiClient().CopyFromContainer(ctx, containerID, srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer content.Close() //nolint:errcheck
|
defer content.Close() //nolint:errcheck
|
||||||
|
|
||||||
if dstPath == "-" {
|
if dstPath == "-" {
|
||||||
_, err = io.Copy(os.Stdout, content)
|
_, err = io.Copy(s.stdout(), content)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/blkiodev"
|
"github.com/docker/docker/api/types/blkiodev"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
"github.com/docker/docker/api/types/strslice"
|
||||||
|
|
@ -173,13 +174,21 @@ func prepareServicesDependsOn(p *types.Project) error {
|
||||||
dependencies = append(dependencies, spec[0])
|
dependencies = append(dependencies, spec[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, link := range service.Links {
|
||||||
|
dependencies = append(dependencies, strings.Split(link, ":")[0])
|
||||||
|
}
|
||||||
|
|
||||||
if len(dependencies) == 0 {
|
if len(dependencies) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if service.DependsOn == nil {
|
if service.DependsOn == nil {
|
||||||
service.DependsOn = make(types.DependsOnConfig)
|
service.DependsOn = make(types.DependsOnConfig)
|
||||||
}
|
}
|
||||||
deps, err := p.GetServices(dependencies...)
|
|
||||||
|
// Verify dependencies exist in the project, whether disabled or not
|
||||||
|
projAllServices := types.Project{}
|
||||||
|
projAllServices.Services = p.AllServices()
|
||||||
|
deps, err := projAllServices.GetServices(dependencies...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +264,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyConfig := types.MappingWithEquals(s.configFile.ParseProxyConfig(s.apiClient.DaemonHost(), nil))
|
proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
|
||||||
env := proxyConfig.OverrideBy(service.Environment)
|
env := proxyConfig.OverrideBy(service.Environment)
|
||||||
|
|
||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
|
|
@ -347,6 +356,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||||
volumesFrom = append(volumesFrom, v[len("container:"):])
|
volumesFrom = append(volumesFrom, v[len("container:"):])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
links, err := s.getLinks(ctx, p.Name, service, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
|
securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
|
@ -371,7 +385,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||||
DNS: service.DNS,
|
DNS: service.DNS,
|
||||||
DNSSearch: service.DNSSearch,
|
DNSSearch: service.DNSSearch,
|
||||||
DNSOptions: service.DNSOpts,
|
DNSOptions: service.DNSOpts,
|
||||||
ExtraHosts: service.ExtraHosts,
|
ExtraHosts: service.ExtraHosts.AsList(),
|
||||||
SecurityOpt: securityOpts,
|
SecurityOpt: securityOpts,
|
||||||
UsernsMode: container.UsernsMode(service.UserNSMode),
|
UsernsMode: container.UsernsMode(service.UserNSMode),
|
||||||
Privileged: service.Privileged,
|
Privileged: service.Privileged,
|
||||||
|
|
@ -381,6 +395,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||||
Runtime: service.Runtime,
|
Runtime: service.Runtime,
|
||||||
LogConfig: logConfig,
|
LogConfig: logConfig,
|
||||||
GroupAdd: service.GroupAdd,
|
GroupAdd: service.GroupAdd,
|
||||||
|
Links: links,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &containerConfig, &hostConfig, networkConfig, nil
|
return &containerConfig, &hostConfig, networkConfig, nil
|
||||||
|
|
@ -399,7 +414,7 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if con[0] == "seccomp" && con[1] != "unconfined" {
|
if con[0] == "seccomp" && con[1] != "unconfined" {
|
||||||
f, err := ioutil.ReadFile(p.RelativePath(con[1]))
|
f, err := os.ReadFile(p.RelativePath(con[1]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
|
return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
|
||||||
}
|
}
|
||||||
|
|
@ -500,6 +515,7 @@ func getDeployResources(s types.ServiceConfig) container.Resources {
|
||||||
CPUShares: s.CPUShares,
|
CPUShares: s.CPUShares,
|
||||||
CPUPercent: int64(s.CPUS * 100),
|
CPUPercent: int64(s.CPUS * 100),
|
||||||
CpusetCpus: s.CPUSet,
|
CpusetCpus: s.CPUSet,
|
||||||
|
DeviceCgroupRules: s.DeviceCgroupRules,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.PidsLimit != 0 {
|
if s.PidsLimit != 0 {
|
||||||
|
|
@ -579,8 +595,12 @@ func setLimits(limits *types.Resource, resources *container.Resources) {
|
||||||
resources.Memory = int64(limits.MemoryBytes)
|
resources.Memory = int64(limits.MemoryBytes)
|
||||||
}
|
}
|
||||||
if limits.NanoCPUs != "" {
|
if limits.NanoCPUs != "" {
|
||||||
i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
|
if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
|
||||||
resources.NanoCPUs = i
|
resources.NanoCPUs = int64(f * 1e9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limits.PIds > 0 {
|
||||||
|
resources.PidsLimit = &limits.PIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -693,7 +713,7 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
|
||||||
var mounts = []mount.Mount{}
|
var mounts = []mount.Mount{}
|
||||||
|
|
||||||
image := getImageName(service, p.Name)
|
image := getImageName(service, p.Name)
|
||||||
imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
|
imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -708,12 +728,20 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
|
||||||
MOUNTS:
|
MOUNTS:
|
||||||
for _, m := range mountOptions {
|
for _, m := range mountOptions {
|
||||||
volumeMounts[m.Target] = struct{}{}
|
volumeMounts[m.Target] = struct{}{}
|
||||||
// `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
|
|
||||||
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
|
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
|
||||||
|
// `Mount` is preferred but does not offer option to created host path if missing
|
||||||
|
// so `Bind` API is used here with raw volume string
|
||||||
|
// see https://github.com/moby/moby/issues/43483
|
||||||
for _, v := range service.Volumes {
|
for _, v := range service.Volumes {
|
||||||
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
|
if v.Target == m.Target {
|
||||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
|
switch {
|
||||||
continue MOUNTS
|
case string(m.Type) != v.Type:
|
||||||
|
v.Source = m.Source
|
||||||
|
fallthrough
|
||||||
|
case v.Bind != nil && v.Bind.CreateHostPath:
|
||||||
|
binds = append(binds, v.String())
|
||||||
|
continue MOUNTS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -722,23 +750,6 @@ MOUNTS:
|
||||||
return volumeMounts, binds, mounts, nil
|
return volumeMounts, binds, mounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBindMode(bind *types.ServiceVolumeBind, readOnly bool) string {
|
|
||||||
mode := "rw"
|
|
||||||
|
|
||||||
if readOnly {
|
|
||||||
mode = "ro"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch bind.SELinux {
|
|
||||||
case types.SELinuxShared:
|
|
||||||
mode += ",z"
|
|
||||||
case types.SELinuxPrivate:
|
|
||||||
mode += ",Z"
|
|
||||||
}
|
|
||||||
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
||||||
var mounts = map[string]mount.Mount{}
|
var mounts = map[string]mount.Mount{}
|
||||||
if inherit != nil {
|
if inherit != nil {
|
||||||
|
|
@ -878,6 +889,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||||
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if definedSecret.Environment != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||||
Type: types.VolumeTypeBind,
|
Type: types.VolumeTypeBind,
|
||||||
Source: definedSecret.File,
|
Source: definedSecret.File,
|
||||||
|
|
@ -921,10 +936,14 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bind, vol, tmpfs := buildMountOptions(volume)
|
bind, vol, tmpfs := buildMountOptions(project, volume)
|
||||||
|
|
||||||
volume.Target = path.Clean(volume.Target)
|
volume.Target = path.Clean(volume.Target)
|
||||||
|
|
||||||
|
if bind != nil {
|
||||||
|
volume.Type = types.VolumeTypeBind
|
||||||
|
}
|
||||||
|
|
||||||
return mount.Mount{
|
return mount.Mount{
|
||||||
Type: mount.Type(volume.Type),
|
Type: mount.Type(volume.Type),
|
||||||
Source: source,
|
Source: source,
|
||||||
|
|
@ -937,7 +956,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
|
func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
|
||||||
switch volume.Type {
|
switch volume.Type {
|
||||||
case "bind":
|
case "bind":
|
||||||
if volume.Volume != nil {
|
if volume.Volume != nil {
|
||||||
|
|
@ -954,6 +973,11 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
|
||||||
if volume.Tmpfs != nil {
|
if volume.Tmpfs != nil {
|
||||||
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
|
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
|
||||||
}
|
}
|
||||||
|
if v, ok := project.Volumes[volume.Source]; ok && v.DriverOpts["o"] == types.VolumeTypeBind {
|
||||||
|
return buildBindOption(&types.ServiceVolumeBind{
|
||||||
|
CreateHostPath: true,
|
||||||
|
}), nil, nil
|
||||||
|
}
|
||||||
return nil, buildVolumeOptions(volume.Volume), nil
|
return nil, buildVolumeOptions(volume.Volume), nil
|
||||||
case "tmpfs":
|
case "tmpfs":
|
||||||
if volume.Bind != nil {
|
if volume.Bind != nil {
|
||||||
|
|
@ -1007,92 +1031,88 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
||||||
_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
// NetworkInspect will match on ID prefix, so NetworkList with a name
|
||||||
|
// filter is used to look for an exact match to prevent e.g. a network
|
||||||
|
// named `db` from getting erroneously matched to a network with an ID
|
||||||
|
// like `db9086999caf`
|
||||||
|
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||||
|
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
|
||||||
if n.External.External {
|
|
||||||
if n.Driver == "overlay" {
|
|
||||||
// Swarm nodes do not register overlay networks that were
|
|
||||||
// created on a different node unless they're in use.
|
|
||||||
// Here we assume `driver` is relevant for a network we don't manage
|
|
||||||
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
|
||||||
// networkAttach will later fail anyway if network actually doesn't exists
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
|
||||||
}
|
|
||||||
var ipam *network.IPAM
|
|
||||||
if n.Ipam.Config != nil {
|
|
||||||
var config []network.IPAMConfig
|
|
||||||
for _, pool := range n.Ipam.Config {
|
|
||||||
config = append(config, network.IPAMConfig{
|
|
||||||
Subnet: pool.Subnet,
|
|
||||||
IPRange: pool.IPRange,
|
|
||||||
Gateway: pool.Gateway,
|
|
||||||
AuxAddress: pool.AuxiliaryAddresses,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ipam = &network.IPAM{
|
|
||||||
Driver: n.Ipam.Driver,
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createOpts := moby.NetworkCreate{
|
|
||||||
// TODO NameSpace Labels
|
|
||||||
Labels: n.Labels,
|
|
||||||
Driver: n.Driver,
|
|
||||||
Options: n.DriverOpts,
|
|
||||||
Internal: n.Internal,
|
|
||||||
Attachable: n.Attachable,
|
|
||||||
IPAM: ipam,
|
|
||||||
EnableIPv6: n.EnableIPv6,
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
|
||||||
createOpts.IPAM = &network.IPAM{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.Ipam.Driver != "" {
|
|
||||||
createOpts.IPAM.Driver = n.Ipam.Driver
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ipamConfig := range n.Ipam.Config {
|
|
||||||
config := network.IPAMConfig{
|
|
||||||
Subnet: ipamConfig.Subnet,
|
|
||||||
}
|
|
||||||
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
|
||||||
}
|
|
||||||
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
|
||||||
w := progress.ContextWriter(ctx)
|
|
||||||
w.Event(progress.CreatingEvent(networkEventName))
|
|
||||||
if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
|
||||||
w.Event(progress.ErrorEvent(networkEventName))
|
|
||||||
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
|
||||||
}
|
|
||||||
w.Event(progress.CreatedEvent(networkEventName))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
if len(networks) == 0 {
|
||||||
}
|
if n.External.External {
|
||||||
|
if n.Driver == "overlay" {
|
||||||
|
// Swarm nodes do not register overlay networks that were
|
||||||
|
// created on a different node unless they're in use.
|
||||||
|
// Here we assume `driver` is relevant for a network we don't manage
|
||||||
|
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
||||||
|
// networkAttach will later fail anyway if network actually doesn't exists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||||
|
}
|
||||||
|
var ipam *network.IPAM
|
||||||
|
if n.Ipam.Config != nil {
|
||||||
|
var config []network.IPAMConfig
|
||||||
|
for _, pool := range n.Ipam.Config {
|
||||||
|
config = append(config, network.IPAMConfig{
|
||||||
|
Subnet: pool.Subnet,
|
||||||
|
IPRange: pool.IPRange,
|
||||||
|
Gateway: pool.Gateway,
|
||||||
|
AuxAddress: pool.AuxiliaryAddresses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ipam = &network.IPAM{
|
||||||
|
Driver: n.Ipam.Driver,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createOpts := moby.NetworkCreate{
|
||||||
|
CheckDuplicate: true,
|
||||||
|
// TODO NameSpace Labels
|
||||||
|
Labels: n.Labels,
|
||||||
|
Driver: n.Driver,
|
||||||
|
Options: n.DriverOpts,
|
||||||
|
Internal: n.Internal,
|
||||||
|
Attachable: n.Attachable,
|
||||||
|
IPAM: ipam,
|
||||||
|
EnableIPv6: n.EnableIPv6,
|
||||||
|
}
|
||||||
|
|
||||||
func (s *composeService) removeNetwork(ctx context.Context, networkID string, networkName string) error {
|
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
||||||
w := progress.ContextWriter(ctx)
|
createOpts.IPAM = &network.IPAM{}
|
||||||
eventName := fmt.Sprintf("Network %s", networkName)
|
}
|
||||||
w.Event(progress.RemovingEvent(eventName))
|
|
||||||
|
|
||||||
if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
|
if n.Ipam.Driver != "" {
|
||||||
w.Event(progress.ErrorEvent(eventName))
|
createOpts.IPAM.Driver = n.Ipam.Driver
|
||||||
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
|
}
|
||||||
|
|
||||||
|
for _, ipamConfig := range n.Ipam.Config {
|
||||||
|
config := network.IPAMConfig{
|
||||||
|
Subnet: ipamConfig.Subnet,
|
||||||
|
IPRange: ipamConfig.IPRange,
|
||||||
|
Gateway: ipamConfig.Gateway,
|
||||||
|
AuxAddress: ipamConfig.AuxiliaryAddresses,
|
||||||
|
}
|
||||||
|
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
||||||
|
}
|
||||||
|
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
||||||
|
w := progress.ContextWriter(ctx)
|
||||||
|
w.Event(progress.CreatingEvent(networkEventName))
|
||||||
|
if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
||||||
|
w.Event(progress.ErrorEvent(networkEventName))
|
||||||
|
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
||||||
|
}
|
||||||
|
w.Event(progress.CreatedEvent(networkEventName))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Event(progress.RemovedEvent(eventName))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) error {
|
||||||
inspected, err := s.apiClient.VolumeInspect(ctx, volume.Name)
|
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errdefs.IsNotFound(err) {
|
if !errdefs.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1123,7 +1143,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
|
||||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.CreatingEvent(eventName))
|
w.Event(progress.CreatingEvent(eventName))
|
||||||
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||||
Labels: volume.Labels,
|
Labels: volume.Labels,
|
||||||
Name: volume.Name,
|
Name: volume.Name,
|
||||||
Driver: volume.Driver,
|
Driver: volume.Driver,
|
||||||
|
|
|
||||||
|
|
@ -143,15 +143,6 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||||
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBindMode(t *testing.T) {
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, false), "rw")
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, true), "ro")
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, false), "rw,z")
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, false), "rw,Z")
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, true), "ro,z")
|
|
||||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, true), "ro,Z")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDefaultNetworkMode(t *testing.T) {
|
func TestGetDefaultNetworkMode(t *testing.T) {
|
||||||
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
|
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
|
||||||
service := composetypes.ServiceConfig{
|
service := composetypes.ServiceConfig{
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ func getParents(v *Vertex) []*Vertex {
|
||||||
return v.GetParents()
|
return v.GetParents()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetParents returns a slice with the parent vertexes of the a Vertex
|
// GetParents returns a slice with the parent vertices of the a Vertex
|
||||||
func (v *Vertex) GetParents() []*Vertex {
|
func (v *Vertex) GetParents() []*Vertex {
|
||||||
var res []*Vertex
|
var res []*Vertex
|
||||||
for _, p := range v.Parents {
|
for _, p := range v.Parents {
|
||||||
|
|
@ -145,7 +145,7 @@ func getChildren(v *Vertex) []*Vertex {
|
||||||
return v.GetChildren()
|
return v.GetChildren()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChildren returns a slice with the child vertexes of the a Vertex
|
// GetChildren returns a slice with the child vertices of the a Vertex
|
||||||
func (v *Vertex) GetChildren() []*Vertex {
|
func (v *Vertex) GetChildren() []*Vertex {
|
||||||
var res []*Vertex
|
var res []*Vertex
|
||||||
for _, p := range v.Children {
|
for _, p := range v.Children {
|
||||||
|
|
@ -194,7 +194,7 @@ func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatu
|
||||||
g.Vertices[key] = v
|
g.Vertices[key] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddEdge adds a relationship of dependency between vertexes `source` and `destination`
|
// AddEdge adds a relationship of dependency between vertices `source` and `destination`
|
||||||
func (g *Graph) AddEdge(source string, destination string) error {
|
func (g *Graph) AddEdge(source string, destination string) error {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
|
@ -41,7 +42,6 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
|
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||||
builtFromResources := options.Project == nil
|
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
resourceToRemove := false
|
resourceToRemove := false
|
||||||
|
|
||||||
|
|
@ -51,8 +51,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if builtFromResources {
|
project := options.Project
|
||||||
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
|
if project == nil {
|
||||||
|
project, err = s.getProjectWithResources(ctx, containers, projectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +63,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
resourceToRemove = true
|
resourceToRemove = true
|
||||||
}
|
}
|
||||||
|
|
||||||
err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service string) error {
|
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||||
serviceContainers := containers.filter(isService(service))
|
serviceContainers := containers.filter(isService(service))
|
||||||
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
|
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
|
||||||
return err
|
return err
|
||||||
|
|
@ -71,7 +72,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
|
orphans := containers.filter(isNotService(project.ServiceNames()...))
|
||||||
if options.RemoveOrphans && len(orphans) > 0 {
|
if options.RemoveOrphans && len(orphans) > 0 {
|
||||||
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
|
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -79,21 +80,18 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ops, err := s.ensureNetworksDown(ctx, projectName)
|
ops := s.ensureNetworksDown(ctx, project, w)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Images != "" {
|
if options.Images != "" {
|
||||||
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
|
ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Volumes {
|
if options.Volumes {
|
||||||
ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
|
ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resourceToRemove && len(ops) == 0 {
|
if !resourceToRemove && len(ops) == 0 {
|
||||||
w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
|
fmt.Fprintf(s.stderr(), "Warning: No resource found to remove for project %q.\n", projectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
|
@ -106,6 +104,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||||
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||||
var ops []downOp
|
var ops []downOp
|
||||||
for _, vol := range project.Volumes {
|
for _, vol := range project.Volumes {
|
||||||
|
if vol.External.External {
|
||||||
|
continue
|
||||||
|
}
|
||||||
volumeName := vol.Name
|
volumeName := vol.Name
|
||||||
ops = append(ops, func() error {
|
ops = append(ops, func() error {
|
||||||
return s.removeVolume(ctx, volumeName, w)
|
return s.removeVolume(ctx, volumeName, w)
|
||||||
|
|
@ -114,9 +115,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
|
||||||
return ops
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
|
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
|
||||||
var ops []downOp
|
var ops []downOp
|
||||||
for image := range s.getServiceImages(options, projectName) {
|
for image := range s.getServiceImages(options, project) {
|
||||||
image := image
|
image := image
|
||||||
ops = append(ops, func() error {
|
ops = append(ops, func() error {
|
||||||
return s.removeImage(ctx, image, w)
|
return s.removeImage(ctx, image, w)
|
||||||
|
|
@ -125,31 +126,74 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
|
||||||
return ops
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
|
func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||||
var ops []downOp
|
var ops []downOp
|
||||||
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
|
for _, n := range project.Networks {
|
||||||
if err != nil {
|
if n.External.External {
|
||||||
return ops, err
|
continue
|
||||||
}
|
}
|
||||||
for _, n := range networks {
|
// loop capture variable for op closure
|
||||||
networkID := n.ID
|
|
||||||
networkName := n.Name
|
networkName := n.Name
|
||||||
ops = append(ops, func() error {
|
ops = append(ops, func() error {
|
||||||
return s.removeNetwork(ctx, networkID, networkName)
|
return s.removeNetwork(ctx, networkName, w)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return ops, nil
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
|
func (s *composeService) removeNetwork(ctx context.Context, name string, w progress.Writer) error {
|
||||||
|
// networks are guaranteed to have unique IDs but NOT names, so it's
|
||||||
|
// possible to get into a situation where a compose down will fail with
|
||||||
|
// an error along the lines of:
|
||||||
|
// failed to remove network test: Error response from daemon: network test is ambiguous (2 matches found based on name)
|
||||||
|
// as a workaround here, the delete is done by ID after doing a list using
|
||||||
|
// the name as a filter (99.9% of the time this will return a single result)
|
||||||
|
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||||
|
Filters: filters.NewArgs(filters.Arg("name", name)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, fmt.Sprintf("failed to inspect network %s", name))
|
||||||
|
}
|
||||||
|
if len(networks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
eventName := fmt.Sprintf("Network %s", name)
|
||||||
|
w.Event(progress.RemovingEvent(eventName))
|
||||||
|
|
||||||
|
var removed int
|
||||||
|
for _, net := range networks {
|
||||||
|
if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Event(progress.ErrorEvent(eventName))
|
||||||
|
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", name))
|
||||||
|
}
|
||||||
|
removed++
|
||||||
|
}
|
||||||
|
|
||||||
|
if removed == 0 {
|
||||||
|
// in practice, it's extremely unlikely for this to ever occur, as it'd
|
||||||
|
// mean the network was present when we queried at the start of this
|
||||||
|
// method but was then deleted by something else in the interim
|
||||||
|
w.Event(progress.NewEvent(eventName, progress.Done, "Warning: No resource found to remove"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Event(progress.RemovedEvent(eventName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
|
||||||
images := map[string]struct{}{}
|
images := map[string]struct{}{}
|
||||||
for _, service := range options.Project.Services {
|
for _, service := range project.Services {
|
||||||
image := service.Image
|
image := service.Image
|
||||||
if options.Images == "local" && image != "" {
|
if options.Images == "local" && image != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if image == "" {
|
if image == "" {
|
||||||
image = getImageName(service, projectName)
|
image = getImageName(service, project.Name)
|
||||||
}
|
}
|
||||||
images[image] = struct{}{}
|
images[image] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +203,7 @@ func (s *composeService) getServiceImages(options api.DownOptions, projectName s
|
||||||
func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
|
func (s *composeService) removeImage(ctx context.Context, image string, w progress.Writer) error {
|
||||||
id := fmt.Sprintf("Image %s", image)
|
id := fmt.Sprintf("Image %s", image)
|
||||||
w.Event(progress.NewEvent(id, progress.Working, "Removing"))
|
w.Event(progress.NewEvent(id, progress.Working, "Removing"))
|
||||||
_, err := s.apiClient.ImageRemove(ctx, image, moby.ImageRemoveOptions{})
|
_, err := s.apiClient().ImageRemove(ctx, image, moby.ImageRemoveOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.Event(progress.NewEvent(id, progress.Done, "Removed"))
|
w.Event(progress.NewEvent(id, progress.Done, "Removed"))
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -174,7 +218,7 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre
|
||||||
func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
|
func (s *composeService) removeVolume(ctx context.Context, id string, w progress.Writer) error {
|
||||||
resource := fmt.Sprintf("Volume %s", id)
|
resource := fmt.Sprintf("Volume %s", id)
|
||||||
w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
|
w.Event(progress.NewEvent(resource, progress.Working, "Removing"))
|
||||||
err := s.apiClient.VolumeRemove(ctx, id, true)
|
err := s.apiClient().VolumeRemove(ctx, id, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
|
w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -193,7 +237,7 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
eventName := getContainerProgressName(container)
|
eventName := getContainerProgressName(container)
|
||||||
w.Event(progress.StoppingEvent(eventName))
|
w.Event(progress.StoppingEvent(eventName))
|
||||||
err := s.apiClient.ContainerStop(ctx, container.ID, timeout)
|
err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
||||||
return err
|
return err
|
||||||
|
|
@ -218,7 +262,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Event(progress.RemovingEvent(eventName))
|
w.Event(progress.RemovingEvent(eventName))
|
||||||
err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
|
err = s.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{
|
||||||
Force: true,
|
Force: true,
|
||||||
RemoveVolumes: volumes,
|
RemoveVolumes: volumes,
|
||||||
})
|
})
|
||||||
|
|
@ -233,21 +277,23 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
|
func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
|
||||||
containers = containers.filter(isNotOneOff)
|
containers = containers.filter(isNotOneOff)
|
||||||
project, _ := s.projectFromName(containers, projectName)
|
project, err := s.projectFromName(containers, projectName)
|
||||||
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
if err != nil && !api.IsNotFoundError(err) {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
project.Volumes = types.Volumes{}
|
volumes, err := s.actualVolumes(ctx, projectName)
|
||||||
for _, vol := range volumes.Volumes {
|
if err != nil {
|
||||||
project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
|
return nil, err
|
||||||
Name: vol.Name,
|
|
||||||
Driver: vol.Driver,
|
|
||||||
Labels: vol.Labels,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
project.Volumes = volumes
|
||||||
|
|
||||||
|
networks, err := s.actualNetworks(ctx, projectName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
project.Networks = networks
|
||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,24 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
compose "github.com/docker/compose/v2/pkg/api"
|
|
||||||
"github.com/docker/compose/v2/pkg/mocks"
|
|
||||||
|
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
|
|
||||||
|
compose "github.com/docker/compose/v2/pkg/api"
|
||||||
|
"github.com/docker/compose/v2/pkg/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDown(t *testing.T) {
|
func TestDown(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
api := mocks.NewMockAPIClient(mockCtrl)
|
api := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = api
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||||
|
|
||||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||||
[]moby.Container{
|
[]moby.Container{
|
||||||
|
|
@ -47,6 +50,14 @@ func TestDown(t *testing.T) {
|
||||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||||
Return(volume.VolumeListOKBody{}, nil)
|
Return(volume.VolumeListOKBody{}, nil)
|
||||||
|
|
||||||
|
// network names are not guaranteed to be unique, ensure Compose handles
|
||||||
|
// cleanup properly if duplicates are inadvertently created
|
||||||
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||||
|
Return([]moby.NetworkResource{
|
||||||
|
{ID: "abc123", Name: "myProject_default"},
|
||||||
|
{ID: "def456", Name: "myProject_default"},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
||||||
|
|
@ -55,10 +66,14 @@ func TestDown(t *testing.T) {
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
|
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||||
nil)
|
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
||||||
|
}).Return([]moby.NetworkResource{
|
||||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
{ID: "abc123", Name: "myProject_default"},
|
||||||
|
{ID: "def456", Name: "myProject_default"},
|
||||||
|
}, nil)
|
||||||
|
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
||||||
|
api.EXPECT().NetworkRemove(gomock.Any(), "def456").Return(nil)
|
||||||
|
|
||||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
|
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
@ -67,8 +82,11 @@ func TestDown(t *testing.T) {
|
||||||
func TestDownRemoveOrphans(t *testing.T) {
|
func TestDownRemoveOrphans(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
api := mocks.NewMockAPIClient(mockCtrl)
|
api := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = api
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||||
|
|
||||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||||
[]moby.Container{
|
[]moby.Container{
|
||||||
|
|
@ -78,6 +96,8 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||||
}, nil)
|
}, nil)
|
||||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||||
Return(volume.VolumeListOKBody{}, nil)
|
Return(volume.VolumeListOKBody{}, nil)
|
||||||
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||||
|
Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
|
||||||
|
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
||||||
|
|
@ -87,10 +107,10 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||||
|
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return([]moby.NetworkResource{{ID: "myProject_default"}},
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||||
nil)
|
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
||||||
|
}).Return([]moby.NetworkResource{{ID: "abc123", Name: "myProject_default"}}, nil)
|
||||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
||||||
|
|
||||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
|
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
@ -99,8 +119,11 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||||
func TestDownRemoveVolumes(t *testing.T) {
|
func TestDownRemoveVolumes(t *testing.T) {
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
api := mocks.NewMockAPIClient(mockCtrl)
|
api := mocks.NewMockAPIClient(mockCtrl)
|
||||||
tested.apiClient = api
|
cli := mocks.NewMockCli(mockCtrl)
|
||||||
|
tested.dockerCli = cli
|
||||||
|
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||||
|
|
||||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||||
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
||||||
|
|
@ -108,12 +131,12 @@ func TestDownRemoveVolumes(t *testing.T) {
|
||||||
Return(volume.VolumeListOKBody{
|
Return(volume.VolumeListOKBody{
|
||||||
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||||
|
Return(nil, nil)
|
||||||
|
|
||||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
|
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
|
||||||
|
|
||||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return(nil, nil)
|
|
||||||
|
|
||||||
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
|
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
|
||||||
|
|
||||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
|
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ import (
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
|
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
|
||||||
events, errors := s.apiClient.Events(ctx, moby.EventsOptions{
|
projectName = strings.ToLower(projectName)
|
||||||
Filters: filters.NewArgs(projectFilter(project)),
|
events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
|
||||||
|
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||||
})
|
})
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -18,149 +18,44 @@ package compose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"strings"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
|
||||||
moby "github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/moby/term"
|
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command/container"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
moby "github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) {
|
func (s *composeService) Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) {
|
||||||
container, err := s.getExecTarget(ctx, project, opts)
|
projectName = strings.ToLower(projectName)
|
||||||
|
target, err := s.getExecTarget(ctx, projectName, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
exec, err := s.apiClient.ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
|
exec := container.NewExecOptions()
|
||||||
Cmd: opts.Command,
|
exec.Interactive = options.Interactive
|
||||||
Env: opts.Environment,
|
exec.TTY = options.Tty
|
||||||
User: opts.User,
|
exec.Detach = options.Detach
|
||||||
Privileged: opts.Privileged,
|
exec.User = options.User
|
||||||
Tty: opts.Tty,
|
exec.Privileged = options.Privileged
|
||||||
Detach: opts.Detach,
|
exec.Workdir = options.WorkingDir
|
||||||
WorkingDir: opts.WorkingDir,
|
exec.Container = target.ID
|
||||||
|
exec.Command = options.Command
|
||||||
AttachStdin: true,
|
for _, v := range options.Environment {
|
||||||
AttachStdout: true,
|
err := exec.Env.Set(v)
|
||||||
AttachStderr: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Detach {
|
|
||||||
return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{
|
|
||||||
Detach: true,
|
|
||||||
Tty: opts.Tty,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.apiClient.ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{
|
|
||||||
Tty: opts.Tty,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer resp.Close() //nolint:errcheck
|
|
||||||
|
|
||||||
if opts.Tty {
|
|
||||||
s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.interactiveExec(ctx, opts, resp)
|
err = container.RunExec(s.dockerCli, exec)
|
||||||
if err != nil {
|
if sterr, ok := err.(cli.StatusError); ok {
|
||||||
return 0, err
|
return sterr.StatusCode, nil
|
||||||
}
|
|
||||||
|
|
||||||
return s.getExecExitStatus(ctx, exec.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// inspired by https://github.com/docker/cli/blob/master/cli/command/container/exec.go#L116
|
|
||||||
func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOptions, resp moby.HijackedResponse) error {
|
|
||||||
outputDone := make(chan error)
|
|
||||||
inputDone := make(chan error)
|
|
||||||
|
|
||||||
stdout := ContainerStdout{HijackedResponse: resp}
|
|
||||||
stdin := ContainerStdin{HijackedResponse: resp}
|
|
||||||
r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
in := streams.NewIn(opts.Stdin)
|
|
||||||
if in.IsTerminal() && opts.Tty {
|
|
||||||
state, err := term.SetRawTerminal(in.FD())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if opts.Tty {
|
|
||||||
_, err := io.Copy(opts.Stdout, stdout)
|
|
||||||
outputDone <- err
|
|
||||||
} else {
|
|
||||||
_, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout)
|
|
||||||
outputDone <- err
|
|
||||||
}
|
|
||||||
stdout.Close() //nolint:errcheck
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(stdin, r)
|
|
||||||
inputDone <- err
|
|
||||||
stdin.Close() //nolint:errcheck
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err := <-outputDone:
|
|
||||||
return err
|
|
||||||
case err := <-inputDone:
|
|
||||||
if _, ok := err.(term.EscapeError); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Wait for output to complete streaming
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
|
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
|
||||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
return s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, opts.Service, opts.Index)
|
||||||
Filters: filters.NewArgs(
|
|
||||||
projectFilter(projectName),
|
|
||||||
serviceFilter(opts.Service),
|
|
||||||
containerNumberFilter(opts.Index),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return moby.Container{}, err
|
|
||||||
}
|
|
||||||
if len(containers) < 1 {
|
|
||||||
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", opts.Service, opts.Index)
|
|
||||||
}
|
|
||||||
container := containers[0]
|
|
||||||
return container, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) {
|
|
||||||
resp, err := s.apiClient.ContainerExecInspect(ctx, execID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return resp.ExitCode, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||||
allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
projectName = strings.ToLower(projectName)
|
||||||
|
allContainers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||||
})
|
})
|
||||||
|
|
@ -83,7 +84,7 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
|
||||||
for _, img := range images {
|
for _, img := range images {
|
||||||
img := img
|
img := img
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
|
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -93,7 +94,6 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
|
||||||
tag := ""
|
tag := ""
|
||||||
repository := ""
|
repository := ""
|
||||||
if len(inspect.RepoTags) > 0 {
|
if len(inspect.RepoTags) > 0 {
|
||||||
|
|
||||||
repotag := strings.Split(inspect.RepoTags[0], ":")
|
repotag := strings.Split(inspect.RepoTags[0], ":")
|
||||||
repository = repotag[0]
|
repository = repotag[0]
|
||||||
if len(repotag) > 1 {
|
if len(repotag) > 1 {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue