mirror of
https://github.com/docker/compose.git
synced 2025-04-08 17:05:13 +02:00
Merge branch 'v2' into 8768-avoid-pulling-same-image-multiple-times
This commit is contained in:
commit
960453fa22
@ -1,3 +1,2 @@
|
||||
.git/
|
||||
bin/
|
||||
dist/
|
||||
|
10
.github/workflows/artifacts.yml
vendored
10
.github/workflows/artifacts.yml
vendored
@ -7,10 +7,10 @@ jobs:
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@ -42,6 +42,12 @@ jobs:
|
||||
name: docker-compose-linux-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
||||
|
||||
- name: Upload linux-ppc64le binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-linux-ppc64le
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
|
||||
|
||||
- name: Upload windows-amd64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -5,6 +5,12 @@ on:
|
||||
branches:
|
||||
- v2
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
description: 'To run with tmate enter "debug_enabled"'
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -13,16 +19,16 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
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
|
||||
|
||||
- name: Run golangci-lint
|
||||
@ -40,10 +46,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@ -65,10 +71,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@ -90,7 +96,7 @@ jobs:
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
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
|
||||
run: make e2e-compose
|
||||
@ -101,10 +107,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@ -123,7 +129,17 @@ jobs:
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
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
|
||||
run: make e2e-compose-standalone
|
||||
run: |
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
|
51
.github/workflows/docs.yml
vendored
Normal file
51
.github/workflows/docs.yml
vendored
Normal file
@ -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
|
9
.github/workflows/release.yaml
vendored
9
.github/workflows/release.yaml
vendored
@ -11,10 +11,10 @@ jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
||||
|
||||
- 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
|
||||
run: cp packaging/* bin/
|
||||
@ -44,7 +44,8 @@ jobs:
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "bin/*"
|
||||
prerelease: true
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
commit: "v2"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag }}
|
||||
|
@ -1,12 +1,11 @@
|
||||
run:
|
||||
concurrency: 2
|
||||
linters:
|
||||
run:
|
||||
concurrency: 2
|
||||
skip-dirs:
|
||||
- tests/composefiles
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- depguard
|
||||
- errcheck
|
||||
- gocyclo
|
||||
- gofmt
|
||||
@ -26,6 +25,13 @@ linters:
|
||||
- unused
|
||||
- varcheck
|
||||
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:
|
||||
min-complexity: 16
|
||||
lll:
|
||||
|
@ -8,7 +8,7 @@
|
||||
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
|
||||
* make
|
||||
* Linux:
|
||||
* [Docker 19.03 or later](https://docs.docker.com/engine/install/)
|
||||
* [Docker 20.10 or later](https://docs.docker.com/engine/install/)
|
||||
* make
|
||||
|
||||
### Building the CLI
|
||||
|
@ -83,7 +83,7 @@ don't get discouraged! Our contributor's guide explains
|
||||
<tr>
|
||||
<td>Community Slack</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>
|
||||
</tr>
|
||||
<tr>
|
||||
|
10
Dockerfile
10
Dockerfile
@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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 PROTOC_GEN_GO_VERSION=v1.4.3
|
||||
|
||||
@ -88,7 +88,7 @@ RUN --mount=target=. \
|
||||
make -f builder.Makefile test
|
||||
|
||||
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=. \
|
||||
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
|
||||
COPY . .
|
||||
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
|
||||
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
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
go 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
|
||||
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) \
|
||||
--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
|
||||
check-dependencies: ## check dependency updates
|
||||
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
|
||||
@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
|
||||
|
||||
|
@ -47,6 +47,7 @@ compose-plugin:
|
||||
.PHONY: cross
|
||||
cross:
|
||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
||||
GOOS=linux GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./cmd
|
||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
||||
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
||||
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
||||
@ -70,7 +71,3 @@ check-license-headers:
|
||||
.PHONY: check-go-mod
|
||||
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)
|
||||
for i := 0; i < l; 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
|
||||
if arg == compose.PluginName {
|
||||
i++
|
||||
@ -58,17 +58,18 @@ func Convert(args []string) []string {
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
if arg == "--verbose" {
|
||||
|
||||
switch arg {
|
||||
case "--verbose":
|
||||
arg = "--debug"
|
||||
}
|
||||
if arg == "-h" {
|
||||
case "-h":
|
||||
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
||||
arg = "--help"
|
||||
}
|
||||
if arg == "--version" || arg == "-v" {
|
||||
case "--version", "-v":
|
||||
// redirect --version pseudo-command to actual command
|
||||
arg = "version"
|
||||
}
|
||||
|
||||
if contains(getBoolFlags(), arg) {
|
||||
rootFlags = append(rootFlags, arg)
|
||||
continue
|
||||
|
@ -43,11 +43,21 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "compose --verbose",
|
||||
args: []string{"--verbose"},
|
||||
want: []string{"--debug", "compose"},
|
||||
},
|
||||
{
|
||||
name: "compose --version",
|
||||
args: []string{"--version"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "compose -v",
|
||||
args: []string{"-v"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
args: []string{"-h"},
|
||||
@ -68,6 +78,11 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--log-level", "INFO", "up"},
|
||||
want: []string{"--log-level", "INFO", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "empty string argument",
|
||||
args: []string{"--project-directory", "", "ps"},
|
||||
want: []string{"compose", "--project-directory", "", "ps"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@ -40,6 +41,28 @@ type buildOptions struct {
|
||||
args []string
|
||||
noCache bool
|
||||
memory string
|
||||
ssh string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
var SSHKeys []types.SSHKey
|
||||
var err error
|
||||
if opts.ssh != "" {
|
||||
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{
|
||||
@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
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)
|
||||
}),
|
||||
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().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Build(ctx, project, api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Progress: opts.progress,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
})
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
@ -27,17 +27,21 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"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/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"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
|
||||
@ -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 {
|
||||
ProjectName string
|
||||
Profiles []string
|
||||
@ -129,8 +130,8 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||
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.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify 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, first specified, Compose file)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
@ -140,6 +141,11 @@ func (o *projectOptions) toProjectName() (string, error) {
|
||||
return o.ProjectName, nil
|
||||
}
|
||||
|
||||
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||
if envProjectName != "" {
|
||||
return envProjectName, nil
|
||||
}
|
||||
|
||||
project, err := o.toProject(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -158,13 +164,16 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
|
||||
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
|
||||
ef := o.EnvFile
|
||||
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 {
|
||||
s.CustomLabels = map[string]string{
|
||||
@ -205,9 +214,9 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
cli.WithOsEnv,
|
||||
cli.WithEnvFile(o.EnvFile),
|
||||
cli.WithDotEnv,
|
||||
cli.WithOsEnv,
|
||||
cli.WithConfigFileEnv,
|
||||
cli.WithDefaultConfigPath,
|
||||
cli.WithName(o.ProjectName))...)
|
||||
@ -222,7 +231,7 @@ func RunningAsStandalone() bool {
|
||||
}
|
||||
|
||||
// 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{}
|
||||
var (
|
||||
ansi string
|
||||
@ -249,6 +258,10 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
}
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := setEnvWithDotEnv(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent := cmd.Root()
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
@ -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"`)
|
||||
}
|
||||
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 {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
formatter.SetANSIMode(ansi)
|
||||
switch ansi {
|
||||
case "never":
|
||||
progress.Mode = progress.ModePlain
|
||||
case "tty":
|
||||
progress.Mode = progress.ModeTTY
|
||||
}
|
||||
if opts.WorkDir != "" {
|
||||
if opts.ProjectDir != "" {
|
||||
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),
|
||||
convertCommand(&opts, backend),
|
||||
killCommand(&opts, backend),
|
||||
runCommand(&opts, backend),
|
||||
runCommand(&opts, dockerCli, backend),
|
||||
removeCommand(&opts, backend),
|
||||
execCommand(&opts, backend),
|
||||
execCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, backend),
|
||||
unpauseCommand(&opts, backend),
|
||||
topCommand(&opts, backend),
|
||||
@ -319,3 +338,27 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
command.Flags().MarkHidden("verbose") //nolint:errcheck
|
||||
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
|
||||
}),
|
||||
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.destination = args[1]
|
||||
return runCopy(ctx, backend, opts)
|
||||
@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
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.MarkHidden("all") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
|
@ -20,10 +20,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"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 {
|
||||
return runDown(ctx, backend, opts)
|
||||
}),
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
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.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.")
|
||||
|
@ -18,12 +18,10 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/spf13/cobra"
|
||||
@ -37,14 +35,15 @@ type execOpts struct {
|
||||
environment []string
|
||||
workingDir string
|
||||
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
privileged bool
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
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{
|
||||
composeOptions: &composeOptions{
|
||||
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().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", 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().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)
|
||||
return runCmd
|
||||
}
|
||||
@ -100,27 +104,9 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
Index: opts.index,
|
||||
Detach: opts.detach,
|
||||
WorkingDir: opts.workingDir,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Interactive: opts.interactive,
|
||||
}
|
||||
|
||||
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)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
|
@ -19,25 +19,44 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type killOptions struct {
|
||||
*projectOptions
|
||||
signal string
|
||||
}
|
||||
|
||||
func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
var opts api.KillOptions
|
||||
opts := killOptions{
|
||||
projectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [options] [SERVICE...]",
|
||||
Short: "Force stop service containers.",
|
||||
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
|
||||
return backend.Kill(ctx, project, opts)
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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/pkg/utils"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/pkg/errors"
|
||||
@ -81,12 +82,11 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
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.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
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.Lookup("filter").Hidden = true
|
||||
return psCmd
|
||||
}
|
||||
|
||||
@ -140,14 +140,14 @@ SERVICES:
|
||||
}
|
||||
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
writter(containers),
|
||||
writer(containers),
|
||||
"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) {
|
||||
for _, container := range containers {
|
||||
ports := DisplayablePorts(container)
|
||||
ports := displayablePorts(container)
|
||||
status := container.State
|
||||
if status == "running" && 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
|
||||
}
|
||||
|
||||
type portRange struct {
|
||||
pStart int
|
||||
pEnd int
|
||||
tStart int
|
||||
tEnd int
|
||||
IP string
|
||||
protocol string
|
||||
}
|
||||
|
||||
func (pr portRange) String() string {
|
||||
var (
|
||||
pub string
|
||||
tgt string
|
||||
)
|
||||
|
||||
if pr.pEnd > pr.pStart {
|
||||
pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
|
||||
} else if pr.pStart > 0 {
|
||||
pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
|
||||
}
|
||||
if pr.tEnd > pr.tStart {
|
||||
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
|
||||
} else {
|
||||
tgt = fmt.Sprintf("%d", pr.tStart)
|
||||
}
|
||||
return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
|
||||
}
|
||||
|
||||
// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
|
||||
func DisplayablePorts(c api.ContainerSummary) string {
|
||||
func displayablePorts(c api.ContainerSummary) string {
|
||||
if c.Publishers == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
sort.Sort(c.Publishers)
|
||||
|
||||
pr := portRange{}
|
||||
ports := []string{}
|
||||
for _, p := range c.Publishers {
|
||||
prIsRange := pr.tEnd != pr.tStart
|
||||
tOverlaps := p.TargetPort <= pr.tEnd
|
||||
|
||||
// Start a new port-range if:
|
||||
// - the protocol is different from the current port-range
|
||||
// - published or target port are not consecutive to the current port-range
|
||||
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
|
||||
if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
|
||||
// start a new port-range, and print the previous port-range (if any)
|
||||
if pr.pStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
pr = portRange{
|
||||
pStart: p.PublishedPort,
|
||||
pEnd: p.PublishedPort,
|
||||
tStart: p.TargetPort,
|
||||
tEnd: p.TargetPort,
|
||||
protocol: p.Protocol,
|
||||
IP: p.URL,
|
||||
}
|
||||
continue
|
||||
ports := make([]types.Port, len(c.Publishers))
|
||||
for i, pub := range c.Publishers {
|
||||
ports[i] = types.Port{
|
||||
IP: pub.URL,
|
||||
PrivatePort: uint16(pub.TargetPort),
|
||||
PublicPort: uint16(pub.PublishedPort),
|
||||
Type: pub.Protocol,
|
||||
}
|
||||
pr.pEnd = p.PublishedPort
|
||||
pr.tEnd = p.TargetPort
|
||||
}
|
||||
if pr.tStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
|
||||
return formatter2.DisplayablePorts(ports)
|
||||
}
|
||||
|
84
cmd/compose/ps_test.go
Normal file
84
cmd/compose/ps_test.go
Normal file
@ -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 {
|
||||
project, err := opts.toProject(services)
|
||||
project, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.stop {
|
||||
err := backend.Stop(ctx, project.Name, api.StopOptions{
|
||||
err := backend.Stop(ctx, project, api.StopOptions{
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -19,12 +19,12 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cgo "github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@ -42,6 +42,7 @@ type runOptions struct {
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
interactive bool
|
||||
user string
|
||||
workdir string
|
||||
entrypoint string
|
||||
@ -53,6 +54,7 @@ type runOptions struct {
|
||||
servicePorts bool
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
@ -61,6 +63,9 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target.Tty = !opts.noTty
|
||||
target.StdinOpen = opts.interactive
|
||||
if !opts.servicePorts {
|
||||
target.Ports = []types.ServicePortConfig{}
|
||||
}
|
||||
@ -102,7 +107,7 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||
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{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
@ -134,6 +139,8 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
}),
|
||||
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.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", 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.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||
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.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.SetInterspersed(false)
|
||||
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 {
|
||||
return startDependencies(ctx, backend, *project, opts.Service)
|
||||
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -199,10 +210,8 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
Command: opts.Command,
|
||||
Detach: opts.Detach,
|
||||
AutoRemove: opts.Remove,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Tty: !opts.noTty,
|
||||
Interactive: opts.interactive,
|
||||
WorkingDir: opts.workdir,
|
||||
User: opts.user,
|
||||
Environment: opts.environment,
|
||||
@ -213,6 +222,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
Index: 0,
|
||||
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)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
@ -224,7 +241,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for _, service := range project.Services {
|
||||
@ -237,8 +254,17 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
|
||||
project.Services = dependencies
|
||||
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 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)
|
||||
}),
|
||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
create.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||
if create.ignoreOrphans && create.removeOrphans {
|
||||
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 {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
if len(attachTo) == 0 {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
|
||||
create := api.CreateOptions{
|
||||
Services: services,
|
||||
@ -205,6 +207,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
return backend.Up(ctx, project, api.UpOptions{
|
||||
Create: create,
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attachTo,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
|
@ -57,7 +57,7 @@ func runVersion(opts versionOptions) {
|
||||
return
|
||||
}
|
||||
if opts.format == formatter.JSON {
|
||||
fmt.Printf(`{"version":%q}\n`, internal.Version)
|
||||
fmt.Printf("{\"version\":%q}\n", internal.Version)
|
||||
return
|
||||
}
|
||||
fmt.Println("Docker Compose version", internal.Version)
|
||||
|
@ -79,7 +79,7 @@ func (l *logConsumer) Log(container, service, message string) {
|
||||
}
|
||||
p := l.getPresenter(container)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
fmt.Fprintf(l.writer, "%s %s\n", p.prefix, line) // nolint:errcheck
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,5 +118,5 @@ type presenter struct {
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
lazyInit := api.NewServiceProxy()
|
||||
cmd := commands.RootCommand(lazyInit)
|
||||
cmd := commands.RootCommand(dockerCli, lazyInit)
|
||||
originalPreRun := cmd.PersistentPreRunE
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile()))
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli))
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
}
|
||||
@ -68,7 +63,7 @@ func pluginMain() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if commands.RunningAsStandalone() {
|
||||
if plugin.RunningStandalone() {
|
||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||
}
|
||||
pluginMain()
|
||||
|
57
docs/docs.Dockerfile
Normal file
57
docs/docs.Dockerfile
Normal file
@ -0,0 +1,57 @@
|
||||
# syntax=docker/dockerfile:1.3-labs
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.18.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
|
||||
|
||||
@ -9,8 +59,8 @@ multiple services in Docker containers.
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
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
|
||||
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
|
||||
to their predecessors.
|
||||
|
||||
For example, consider this command line:
|
||||
@ -30,7 +80,7 @@ services:
|
||||
volumes:
|
||||
- "/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.
|
||||
|
||||
```yaml
|
||||
@ -41,22 +91,22 @@ services:
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#### 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.
|
||||
|
||||
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
|
||||
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
|
||||
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
|
||||
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
|
||||
|
||||
```console
|
||||
$ 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
|
||||
|
||||
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.
|
||||
Each configuration has a project name. If you supply a `-p` flag, you can specify a project name. If you don’t
|
||||
specify the flag, Compose uses the current directory name.
|
||||
Project name can also be set by `COMPOSE_PROJECT_NAME` environment variable.
|
||||
|
||||
Most compose subcommand can be ran without a compose file, just passing
|
||||
Most compose subcommand can be ran without a compose file, just passing
|
||||
project name to retrieve the relevant resources.
|
||||
|
||||
```console
|
||||
$ docker compose -p my_project ps -a
|
||||
NAME SERVICE STATUS PORTS
|
||||
my_project_demo_1 demo running
|
||||
my_project_demo_1 demo running
|
||||
|
||||
$ docker compose -p my_project logs
|
||||
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 `--profile` to specify one or more active profiles
|
||||
Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
|
||||
without any specified profiles.
|
||||
Calling `docker compose --profile frontend up` will start the services with the profile `frontend` and services
|
||||
without any specified profiles.
|
||||
You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` will be enabled.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Services are built once and then tagged, by default as `project_service`.
|
||||
Services are built once and then tagged, by default as `project_service`.
|
||||
|
||||
If the Compose file specifies an
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
|
||||
the image is tagged with that name, substituting any variables beforehand. See
|
||||
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
|
||||
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
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
|
||||
|
||||
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
|
||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||
fully defined Compose model.
|
||||
it merges the Compose files set by `-f` flags, resolves variables in Compose file, and expands short-notation into
|
||||
fully defined Compose model.
|
||||
|
||||
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
# docker compose pause
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Pause services
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -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
|
||||
|
||||
Lists containers for a Compose project, with current status and exposed ports.
|
||||
By default, both running and stopped containers are shown:
|
||||
|
||||
```console
|
||||
$ docker compose ps
|
||||
NAME SERVICE STATUS PORTS
|
||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
||||
example_bar_1 bar exited (1)
|
||||
NAME COMMAND SERVICE STATUS PORTS
|
||||
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Examples
|
||||
## Examples
|
||||
|
||||
suppose you have this `compose.yaml`:
|
||||
|
||||
@ -24,8 +39,8 @@ services:
|
||||
- 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,
|
||||
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
|
||||
@ -46,4 +61,4 @@ $ docker compose pull db
|
||||
⠹ f63c47038e66 Waiting 9.3s
|
||||
⠹ 77a0c198cde5 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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
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:
|
||||
|
||||
@ -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,
|
||||
including volumes, links, and other details. However, there are two important differences:
|
||||
|
||||
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
|
||||
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
|
||||
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
|
||||
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
|
||||
`python app.py`.
|
||||
|
||||
The second difference is that the `docker compose run` command does not create any of the ports specified in the
|
||||
service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports
|
||||
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
|
||||
to be created and mapped to the host, specify the `--service-ports`
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
If you start a service configured with links, the run command first checks to see if the linked service is running
|
||||
and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
|
||||
passed it. For example, you could run:
|
||||
|
||||
```console
|
||||
@ -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
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -1,3 +1,10 @@
|
||||
# docker compose start
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Start services
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
# docker compose top
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Display the running processes
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
@ -9,5 +16,5 @@ Displays the running processes.
|
||||
$ docker compose top
|
||||
example_foo_1
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
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
|
||||
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
|
||||
background and leaves them running.
|
||||
|
||||
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
|
||||
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
|
||||
(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.
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
pname: docker
|
||||
plink: docker.yaml
|
||||
@ -126,6 +129,7 @@ cname:
|
||||
- docker compose top
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
clink:
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_convert.yaml
|
||||
@ -151,6 +155,7 @@ clink:
|
||||
- docker_compose_top.yaml
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
options:
|
||||
- option: ansi
|
||||
value_type: string
|
||||
@ -158,6 +163,17 @@ options:
|
||||
description: |
|
||||
Control when to print ANSI control characters ("never"|"always"|"auto")
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -166,6 +182,7 @@ options:
|
||||
value_type: string
|
||||
description: Specify an alternate environment file.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -176,6 +193,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Compose configuration files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -185,6 +203,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Do not print ANSI control characters (DEPRECATED)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -194,6 +213,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Specify a profile to enable
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -202,8 +222,9 @@ options:
|
||||
value_type: string
|
||||
description: |-
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -213,6 +234,7 @@ options:
|
||||
value_type: string
|
||||
description: Project name
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -222,6 +244,18 @@ options:
|
||||
default_value: "false"
|
||||
description: Show more output
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -231,8 +265,9 @@ options:
|
||||
description: |-
|
||||
DEPRECATED! USE --project-directory INSTEAD.
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -19,6 +19,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set build-time variables for services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -28,6 +29,7 @@ options:
|
||||
default_value: "true"
|
||||
description: Compress the build context using gzip. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -37,6 +39,7 @@ options:
|
||||
default_value: "true"
|
||||
description: Always remove intermediate containers. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -47,6 +50,7 @@ options:
|
||||
description: |
|
||||
Set memory limit for the build container. Not supported on buildkit yet.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -56,6 +60,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Do not use cache when building the image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -66,6 +71,7 @@ options:
|
||||
description: |
|
||||
Do not remove intermediate containers after a successful build. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -75,6 +81,7 @@ options:
|
||||
default_value: "true"
|
||||
description: Build images in parallel. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -82,8 +89,9 @@ options:
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of progress output ("auto", "plain", "noTty")
|
||||
description: Set type of progress output (auto, tty, plain, quiet)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -93,6 +101,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Always attempt to pull a newer version of the image.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -103,6 +112,17 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't print anything to STDOUT
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -16,6 +16,7 @@ options:
|
||||
default_value: yaml
|
||||
description: 'Format the output. Values: [yaml | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -24,6 +25,17 @@ options:
|
||||
value_type: string
|
||||
description: Print the service config hash, one per line.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -33,6 +45,27 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't interpolate environment variables.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -42,6 +75,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the profile names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -52,6 +86,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Only validate the configuration, don't print anything.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -61,6 +96,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Pin image tags to digests.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -70,6 +106,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the service names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -79,6 +116,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the volume names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -10,7 +10,8 @@ options:
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Copy to all the containers of the service.
|
||||
deprecated: false
|
||||
deprecated: true
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -21,6 +22,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Archive mode (copy all uid/gid information)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -31,16 +33,18 @@ options:
|
||||
default_value: "false"
|
||||
description: Always follow symbol link in SRC_PATH
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "1"
|
||||
default_value: "0"
|
||||
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
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -10,6 +10,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Build images before starting containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -20,6 +21,7 @@ options:
|
||||
description: |
|
||||
Recreate containers even if their configuration and image haven't changed.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -29,6 +31,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't build an image, even if it's missing.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -39,6 +42,7 @@ options:
|
||||
description: |
|
||||
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -23,6 +23,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Remove containers for services not defined in the Compose file.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -32,6 +33,7 @@ options:
|
||||
description: |
|
||||
Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -42,6 +44,7 @@ options:
|
||||
default_value: "10"
|
||||
description: Specify a shutdown timeout in seconds
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -53,6 +56,7 @@ options:
|
||||
description: |
|
||||
Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -29,6 +29,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Output events as a stream of json objects
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -15,6 +15,7 @@ options:
|
||||
default_value: "false"
|
||||
description: 'Detached mode: Run command in the background.'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -25,6 +26,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set environment variables
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -35,6 +37,18 @@ options:
|
||||
description: |
|
||||
index of the container if there are multiple instances of a service [default: 1].
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -42,10 +56,11 @@ options:
|
||||
- option: no-TTY
|
||||
shorthand: T
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
default_value: "true"
|
||||
description: |
|
||||
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -55,6 +70,18 @@ options:
|
||||
default_value: "false"
|
||||
description: Give extended privileges to the process.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -64,6 +91,7 @@ options:
|
||||
value_type: string
|
||||
description: Run the command as this user.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -73,6 +101,7 @@ options:
|
||||
value_type: string
|
||||
description: Path to workdir directory for this command.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -11,6 +11,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Only display IDs
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -16,6 +16,7 @@ options:
|
||||
default_value: SIGKILL
|
||||
description: SIGNAL to send to the container.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -11,6 +11,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Follow log output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -20,6 +21,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Produce monochrome output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -29,6 +31,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't print prefix in logs.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -38,6 +41,7 @@ options:
|
||||
description: |
|
||||
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -48,6 +52,7 @@ options:
|
||||
description: |
|
||||
Number of lines to show from the end of the logs for each container.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -58,6 +63,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Show timestamps.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -67,6 +73,7 @@ options:
|
||||
description: |
|
||||
Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -11,6 +11,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Show all stopped Compose projects
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -19,6 +20,7 @@ options:
|
||||
value_type: filter
|
||||
description: Filter output based on conditions provided.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -28,6 +30,7 @@ options:
|
||||
default_value: pretty
|
||||
description: 'Format the output. Values: [pretty | json].'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -38,6 +41,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Only display IDs.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -1,5 +1,5 @@
|
||||
command: docker compose pause
|
||||
short: pause services
|
||||
short: Pause services
|
||||
long: |
|
||||
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
|
||||
usage: docker compose pause [SERVICE...]
|
||||
|
@ -10,6 +10,7 @@ options:
|
||||
default_value: "1"
|
||||
description: index of the container if service has multiple replicas
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -19,6 +20,7 @@ options:
|
||||
default_value: tcp
|
||||
description: tcp or udp
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -2,12 +2,13 @@ command: docker compose ps
|
||||
short: List containers
|
||||
long: |-
|
||||
Lists containers for a Compose project, with current status and exposed ports.
|
||||
By default, both running and stopped containers are shown:
|
||||
|
||||
```console
|
||||
$ docker compose ps
|
||||
NAME SERVICE STATUS PORTS
|
||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
||||
example_bar_1 bar exited (1)
|
||||
NAME COMMAND SERVICE STATUS PORTS
|
||||
example-bar-1 "/docker-entrypoint.…" bar exited (0)
|
||||
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
|
||||
```
|
||||
usage: docker compose ps [SERVICE...]
|
||||
pname: docker compose
|
||||
@ -20,14 +21,17 @@ options:
|
||||
description: |
|
||||
Show all stopped containers (including those created by the run command)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: filter
|
||||
value_type: string
|
||||
description: Filter services by a property
|
||||
description: 'Filter services by a property (supported filters: status).'
|
||||
details_url: '#filter'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -36,7 +40,9 @@ options:
|
||||
value_type: string
|
||||
default_value: pretty
|
||||
description: 'Format the output. Values: [pretty | json]'
|
||||
details_url: '#format'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -47,6 +53,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Only display IDs
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -56,18 +63,108 @@ options:
|
||||
default_value: "false"
|
||||
description: Display services
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: status
|
||||
value_type: string
|
||||
description: Filter services by status
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: |
|
||||
Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
|
||||
details_url: '#status'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: 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
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
@ -12,6 +12,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Pull what it can and ignores images with pull failures
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -21,6 +22,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Also pull services declared as dependencies
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -30,6 +32,7 @@ options:
|
||||
default_value: "true"
|
||||
description: DEPRECATED disable parallel pulling.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -39,6 +42,7 @@ options:
|
||||
default_value: "true"
|
||||
description: DEPRECATED pull multiple images in parallel.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -49,10 +53,52 @@ options:
|
||||
default_value: "false"
|
||||
description: Pull without printing progress information
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: 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
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
@ -28,6 +28,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Push what it can and ignores images with push failures
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -1,6 +1,16 @@
|
||||
command: docker compose restart
|
||||
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
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
@ -11,6 +21,7 @@ options:
|
||||
default_value: "10"
|
||||
description: Specify a shutdown timeout in seconds
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -26,6 +26,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Deprecated - no effect
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -36,6 +37,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't ask to confirm removal
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -46,6 +48,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Stop the containers, if required, before removing
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -56,6 +59,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Remove any anonymous volumes attached to containers
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -65,6 +65,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Run container in background and print container ID
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -73,6 +74,7 @@ options:
|
||||
value_type: string
|
||||
description: Override the entrypoint of the image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -83,16 +85,29 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set environment variables
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: 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
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Add or override a label
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -101,6 +116,7 @@ options:
|
||||
value_type: string
|
||||
description: Assign a name to the container
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -108,10 +124,10 @@ options:
|
||||
- option: no-TTY
|
||||
shorthand: T
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: |
|
||||
Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
|
||||
default_value: "true"
|
||||
description: 'Disable pseudo-TTY allocation (default: auto-detected).'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -121,6 +137,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start linked services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -131,6 +148,17 @@ options:
|
||||
default_value: '[]'
|
||||
description: Publish a container's port(s) to the host.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -140,6 +168,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Automatically remove the container when it exits
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -150,6 +179,18 @@ options:
|
||||
description: |
|
||||
Run command with the service's ports enabled and mapped to the host.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -160,6 +201,7 @@ options:
|
||||
description: |
|
||||
Use the service's network useAliases in the network(s) the container connects to.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -169,6 +211,7 @@ options:
|
||||
value_type: string
|
||||
description: Run as specified username or uid
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -179,6 +222,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Bind mount a volume.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -188,6 +232,7 @@ options:
|
||||
value_type: string
|
||||
description: Working directory inside the container
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -12,6 +12,7 @@ options:
|
||||
default_value: "10"
|
||||
description: Specify a shutdown timeout in seconds
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -1,5 +1,5 @@
|
||||
command: docker compose unpause
|
||||
short: unpause services
|
||||
short: Unpause services
|
||||
long: Unpauses paused containers of a service.
|
||||
usage: docker compose unpause [SERVICE...]
|
||||
pname: docker compose
|
||||
|
@ -27,6 +27,7 @@ options:
|
||||
description: |
|
||||
Stops all containers if any container was stopped. Incompatible with -d
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -36,6 +37,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Recreate dependent containers. Incompatible with --no-recreate.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -45,6 +47,7 @@ options:
|
||||
default_value: '[]'
|
||||
description: Attach to service output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -54,6 +57,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Attach to dependent containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -63,6 +67,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Build images before starting containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -73,16 +78,7 @@ options:
|
||||
default_value: "false"
|
||||
description: 'Detached mode: Run containers in the background'
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: environment
|
||||
shorthand: e
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Environment variables
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -92,6 +88,7 @@ options:
|
||||
description: |
|
||||
Return the exit code of the selected service container. Implies --abort-on-container-exit
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -102,6 +99,7 @@ options:
|
||||
description: |
|
||||
Recreate containers even if their configuration and image haven't changed.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -111,6 +109,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't build an image, even if it's missing.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -120,6 +119,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Produce monochrome output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -129,6 +129,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start linked services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -138,6 +139,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't print prefix in logs.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -148,6 +150,7 @@ options:
|
||||
description: |
|
||||
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -157,6 +160,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start the services after creating them.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -166,6 +170,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Pull without printing progress information.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -175,6 +180,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Remove containers for services not defined in the Compose file.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -186,6 +192,7 @@ options:
|
||||
description: |
|
||||
Recreate anonymous volumes instead of retrieving data from the previous containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -196,6 +203,7 @@ options:
|
||||
description: |
|
||||
Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -207,6 +215,17 @@ options:
|
||||
description: |
|
||||
Use this timeout in seconds for container shutdown when attached or when containers are already running.
|
||||
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
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -10,6 +10,7 @@ options:
|
||||
value_type: string
|
||||
description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@ -19,6 +20,7 @@ options:
|
||||
default_value: "false"
|
||||
description: Shows only Compose's version number.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
@ -22,16 +22,23 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
clidocstool "github.com/docker/cli-docs-tool"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func generateCliYaml(opts *options) error {
|
||||
cmd := &cobra.Command{Use: "docker"}
|
||||
cmd.AddCommand(compose.RootCommand(nil))
|
||||
func generateDocs(opts *options) error {
|
||||
dockerCLI, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "docker",
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
cmd.AddCommand(compose.RootCommand(dockerCLI, nil))
|
||||
disableFlagsInUseLine(cmd)
|
||||
|
||||
cmd.DisableAutoGenTag = true
|
||||
tool, err := clidocstool.New(clidocstool.Options{
|
||||
Root: cmd,
|
||||
SourceDir: opts.source,
|
||||
@ -41,7 +48,7 @@ func generateCliYaml(opts *options) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tool.GenYamlTree(cmd)
|
||||
return tool.GenAllTree()
|
||||
}
|
||||
|
||||
func disableFlagsInUseLine(cmd *cobra.Command) {
|
||||
@ -69,12 +76,12 @@ type options struct {
|
||||
func main() {
|
||||
cwd, _ := os.Getwd()
|
||||
opts := &options{
|
||||
source: cwd,
|
||||
source: filepath.Join(cwd, "docs", "reference"),
|
||||
target: filepath.Join(cwd, "docs", "reference"),
|
||||
}
|
||||
fmt.Printf("Project root: %s\n", opts.source)
|
||||
fmt.Printf("Generating yaml files into %s\n", opts.target)
|
||||
if err := generateCliYaml(opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error())
|
||||
if err := generateDocs(opts); err != nil {
|
||||
_, _ = 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
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
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/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v1.1.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.4
|
||||
github.com/compose-spec/compose-go v1.2.8
|
||||
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/docker/buildx v0.7.1
|
||||
github.com/docker/cli v20.10.12+incompatible
|
||||
github.com/docker/cli-docs-tool v0.2.1
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.17+incompatible
|
||||
github.com/docker/cli-docs-tool v0.4.0
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/golang/mock v1.6.0
|
||||
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-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/morikuni/aec 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/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
||||
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/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
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.1.0
|
||||
gotest.tools/v3 v3.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // 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/ttrpc v1.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.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/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
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/stdr v1.2.2 // 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/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/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.13.5 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // 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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/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/symlink v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // 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/qri-io/jsonpointer v0.1.0 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opentelemetry.io/contrib v0.21.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.21.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.3.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.4.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apimachinery v0.22.5 // indirect
|
||||
k8s.io/client-go v0.22.5 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
|
||||
k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
// (for buildx)
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
|
||||
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
|
||||
require (
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||
github.com/zmap/zlint v1.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -33,9 +32,9 @@ type Service interface {
|
||||
// Push executes the equivalent ot a `compose push`
|
||||
Push(ctx context.Context, project *types.Project, options PushOptions) error
|
||||
// Pull executes the equivalent of a `compose pull`
|
||||
Pull(ctx context.Context, project *types.Project, opts PullOptions) error
|
||||
Pull(ctx context.Context, project *types.Project, options PullOptions) error
|
||||
// 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(ctx context.Context, projectName string, options StartOptions) error
|
||||
// Restart restarts containers
|
||||
@ -55,25 +54,25 @@ type Service interface {
|
||||
// Convert translate compose model into backend's native format
|
||||
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
||||
// 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(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||
// 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(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(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(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(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(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||
// 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(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(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
}
|
||||
@ -92,6 +91,8 @@ type BuildOptions struct {
|
||||
Quiet bool
|
||||
// Services passed in the command line to be built
|
||||
Services []string
|
||||
// Ssh authentications passed in the command line
|
||||
SSHs []types.SSHKey
|
||||
}
|
||||
|
||||
// CreateOptions group options of the Create API
|
||||
@ -116,6 +117,8 @@ type CreateOptions struct {
|
||||
|
||||
// StartOptions group options of the Start API
|
||||
type StartOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
|
||||
Project *types.Project
|
||||
// Attach to container and forward logs if not nil
|
||||
Attach LogConsumer
|
||||
// AttachTo set the services to attach to
|
||||
@ -216,10 +219,8 @@ type RunOptions struct {
|
||||
Entrypoint []string
|
||||
Detach bool
|
||||
AutoRemove bool
|
||||
Stdin io.ReadCloser
|
||||
Stdout io.WriteCloser
|
||||
Stderr io.WriteCloser
|
||||
Tty bool
|
||||
Interactive bool
|
||||
WorkingDir string
|
||||
User string
|
||||
Environment []string
|
||||
|
@ -37,9 +37,9 @@ type ServiceProxy struct {
|
||||
PsFn func(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
|
||||
ListFn func(ctx context.Context, options ListOptions) ([]Stack, 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)
|
||||
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)
|
||||
CopyFn func(ctx context.Context, project string, options CopyOptions) 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
|
||||
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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.KillFn(ctx, project, options)
|
||||
return s.KillFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// RunOneOffContainer implements Service interface
|
||||
@ -241,46 +238,43 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.RemoveFn(ctx, project, options)
|
||||
return s.RemoveFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
return s.ExecFn(ctx, project, options)
|
||||
return s.ExecFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.CopyFn(ctx, project, options)
|
||||
return s.CopyFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.PauseFn(ctx, project, options)
|
||||
return s.PauseFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.UnPauseFn(ctx, project, options)
|
||||
return s.UnPauseFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Top implements Service interface
|
||||
@ -292,19 +286,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.EventsFn(ctx, project, options)
|
||||
return s.EventsFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return "", 0, ErrNotImplemented
|
||||
}
|
||||
return s.PortFn(ctx, project, service, port, options)
|
||||
return s.PortFn(ctx, projectName, service, port, options)
|
||||
}
|
||||
|
||||
// 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, ", "))
|
||||
|
||||
for _, container := range containers {
|
||||
err := s.attachContainer(ctx, container, listener, project)
|
||||
err := s.attachContainer(ctx, container, listener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -56,13 +56,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||
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]
|
||||
containerName := getContainerNameWithoutProject(container)
|
||||
service, err := project.GetService(serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
@ -78,7 +74,13 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||
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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
var stdout io.ReadCloser
|
||||
var stdin io.WriteCloser
|
||||
cnx, err := s.apiClient.ContainerAttach(ctx, container, moby.ContainerAttachOptions{
|
||||
cnx, err := s.apiClient().ContainerAttach(ctx, container, moby.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
@ -151,7 +153,7 @@ func (s *composeService) getContainerStreams(ctx context.Context, container stri
|
||||
}
|
||||
|
||||
// Fallback to logs API
|
||||
logs, err := s.apiClient.ContainerLogs(ctx, container, moby.ContainerLogsOptions{
|
||||
logs, err := s.apiClient().ContainerLogs(ctx, container, moby.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: true,
|
||||
|
@ -19,7 +19,6 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"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/util/buildflags"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
bclient "github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"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"
|
||||
|
||||
"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 {
|
||||
imageName := getImageName(service, project.Name)
|
||||
imagesToBuild = append(imagesToBuild, imageName)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -82,7 +82,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
|
||||
opts[imageName] = buildOptions
|
||||
}
|
||||
}
|
||||
@ -161,7 +160,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
|
||||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||
continue
|
||||
}
|
||||
opt, err := s.toBuildOptions(project, service, imageName)
|
||||
opt, err := s.toBuildOptions(project, service, imageName, []types.SSHKey{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -189,37 +188,29 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
for name, info := range imgs {
|
||||
images[name] = info.ID
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) {
|
||||
ping, err := s.apiClient.Ping(ctx)
|
||||
if err != nil {
|
||||
return command.ServerInfo{}, err
|
||||
for _, s := range project.Services {
|
||||
imgName := getImageName(s, project.Name)
|
||||
digest, ok := images[imgName]
|
||||
if ok {
|
||||
s.CustomLabels[api.ImageDigestLabel] = digest
|
||||
}
|
||||
}
|
||||
serverInfo := command.ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
BuildkitVersion: ping.BuilderVersion,
|
||||
}
|
||||
return serverInfo, err
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
serverInfo, err := s.serverInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled {
|
||||
return s.doBuildClassic(ctx, opts)
|
||||
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
|
||||
return s.doBuildClassic(ctx, project, opts)
|
||||
}
|
||||
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
|
||||
tags = append(tags, imageTag)
|
||||
|
||||
@ -244,11 +235,59 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
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{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: service.Build.Context,
|
||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||
},
|
||||
CacheFrom: cacheFrom,
|
||||
CacheTo: cacheTo,
|
||||
NoCache: service.Build.NoCache,
|
||||
Pull: service.Build.Pull,
|
||||
BuildArgs: buildArgs,
|
||||
Tags: tags,
|
||||
Target: service.Build.Target,
|
||||
@ -256,10 +295,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
Platforms: plats,
|
||||
Labels: service.Build.Labels,
|
||||
NetworkMode: service.Build.Network,
|
||||
ExtraHosts: service.Build.ExtraHosts,
|
||||
Session: []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(os.Stderr),
|
||||
},
|
||||
ExtraHosts: service.Build.ExtraHosts.AsList(),
|
||||
Session: sessionConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -293,3 +330,14 @@ func dockerFilePath(context string, dockerfile string) string {
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -45,10 +45,10 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
|
||||
// build and will lock
|
||||
progressCtx, cancel := context.WithCancel(context.Background())
|
||||
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
|
||||
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()
|
||||
if err == nil {
|
||||
err = errW
|
||||
|
@ -21,12 +21,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
@ -41,15 +41,24 @@ import (
|
||||
"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 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)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err).ErrorOrNil()
|
||||
}
|
||||
nameDigests[name] = digest
|
||||
nameDigests[imageName] = digest
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nameDigests, errs
|
||||
@ -69,8 +78,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
|
||||
dockerfileName := options.Inputs.DockerfilePath
|
||||
specifiedContext := options.Inputs.ContextPath
|
||||
progBuff := os.Stdout
|
||||
buildBuff := os.Stdout
|
||||
progBuff := s.stdout()
|
||||
buildBuff := s.stdout()
|
||||
if options.ImageIDFile != "" {
|
||||
// Avoid leaving a stale file if we eventually fail
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
var body io.Reader
|
||||
if buildCtx != nil {
|
||||
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
}
|
||||
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
|
||||
configFile := s.configFile
|
||||
configFile := s.configFile()
|
||||
creds, err := configFile.GetAllCredentials()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -171,7 +171,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
response, err := s.apiClient.ImageBuild(ctx, body, buildOptions)
|
||||
response, err := s.apiClient().ImageBuild(ctx, body, buildOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -181,13 +181,13 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
aux := func(msg jsonmessage.JSONMessage) {
|
||||
var result dockertypes.BuildResult
|
||||
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 {
|
||||
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 jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// 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.
|
||||
if response.OSType != "windows" && runtime.GOOS == "windows" {
|
||||
// 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 "+
|
||||
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
||||
"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 == "" {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -21,15 +21,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sanathkr/go-yaml"
|
||||
)
|
||||
|
||||
@ -37,19 +40,41 @@ import (
|
||||
var Separator = "-"
|
||||
|
||||
// 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{
|
||||
apiClient: apiClient,
|
||||
configFile: configFile,
|
||||
dockerCli: dockerCli,
|
||||
}
|
||||
}
|
||||
|
||||
type composeService struct {
|
||||
apiClient client.APIClient
|
||||
configFile *configfile.ConfigFile
|
||||
dockerCli command.Cli
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
for _, name := range c.Names {
|
||||
if strings.LastIndex(name, "/") == 0 {
|
||||
@ -100,7 +125,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
|
||||
Name: projectName,
|
||||
}
|
||||
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{}
|
||||
for _, c := range containers {
|
||||
@ -140,7 +165,7 @@ 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)
|
||||
if err != nil {
|
||||
@ -149,3 +174,59 @@ SERVICES:
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
// actualState list resources labelled by projectName to rebuild compose project model
|
||||
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
|
||||
var containers Containers
|
||||
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
project, err := s.projectFromName(containers, projectName, services...)
|
||||
if err != nil && !api.IsNotFoundError(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
containers = containers.filter(isService(services...))
|
||||
}
|
||||
return containers, project, nil
|
||||
}
|
||||
|
||||
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
|
||||
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/utils"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
var containers Containers
|
||||
f := []filters.KeyValuePair{projectFilter(project)}
|
||||
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:
|
||||
}
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
f := getDefaultFilters(project, oneOff, selectedServices...)
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(f...),
|
||||
All: stopped,
|
||||
})
|
||||
@ -65,6 +54,40 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
|
||||
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
|
||||
type containerPredicate func(c moby.Container) bool
|
||||
|
||||
@ -87,14 +110,6 @@ func isNotOneOff(c moby.Container) bool {
|
||||
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
|
||||
func (containers Containers) filter(predicate containerPredicate) Containers {
|
||||
var filtered Containers
|
||||
|
@ -180,26 +180,20 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
// Scale Down
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
err := c.service.apiClient.ContainerStop(ctx, container.ID, timeout)
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.service.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
|
||||
return c.service.apiClient().ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if recreate == api.RecreateNever {
|
||||
continue
|
||||
}
|
||||
// Re-create diverged containers
|
||||
configHash, err := ServiceHash(service)
|
||||
mustRecreate, err := mustRecreate(service, container, recreate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := getContainerProgressName(container)
|
||||
diverged := container.Labels[api.ConfigHashLabel] != configHash
|
||||
if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
|
||||
if mustRecreate {
|
||||
i, container := i, container
|
||||
eg.Go(func() error {
|
||||
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
|
||||
w := progress.ContextWriter(ctx)
|
||||
name := getContainerProgressName(container)
|
||||
switch container.State {
|
||||
case ContainerRunning:
|
||||
w.Event(progress.RunningEvent(name))
|
||||
@ -249,6 +244,22 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
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 {
|
||||
name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator)
|
||||
if service.ContainerName != "" {
|
||||
@ -395,13 +406,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
var created moby.Container
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||
err := s.apiClient.ContainerStop(ctx, replaced.ID, timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
name := getCanonicalContainerName(replaced)
|
||||
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 {
|
||||
return created, err
|
||||
}
|
||||
@ -419,7 +430,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
err = s.apiClient.ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
|
||||
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
|
||||
if err != nil {
|
||||
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 {
|
||||
w := progress.ContextWriter(ctx)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -468,11 +479,11 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
}
|
||||
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 {
|
||||
return created, err
|
||||
}
|
||||
inspectedContainer, err := s.apiClient.ContainerInspect(ctx, response.ID)
|
||||
inspectedContainer, err := s.apiClient().ContainerInspect(ctx, response.ID)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@ -502,7 +513,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
if shortIDAliasExists(created.ID, val.Aliases...) {
|
||||
continue
|
||||
}
|
||||
err = s.apiClient.NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
|
||||
err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
@ -512,6 +523,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
return created, err
|
||||
}
|
||||
|
||||
@ -596,7 +609,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
|
||||
IPv6Address: ipv6Address,
|
||||
}
|
||||
}
|
||||
err := s.apiClient.NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
||||
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
|
||||
Aliases: aliases,
|
||||
IPAddress: ipv4Address,
|
||||
GlobalIPv6Address: ipv6Address,
|
||||
@ -619,7 +632,7 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||
return false, nil
|
||||
}
|
||||
for _, c := range containers {
|
||||
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
|
||||
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -651,7 +664,7 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
||||
return false, 0, err
|
||||
}
|
||||
for _, c := range containers {
|
||||
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
|
||||
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
@ -671,7 +684,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
serviceFilter(service.Name),
|
||||
@ -700,7 +713,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(container)
|
||||
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 {
|
||||
w.Event(progress.StartedEvent(eventName))
|
||||
}
|
||||
|
@ -74,8 +74,11 @@ func TestServiceLinks(t *testing.T) {
|
||||
t.Run("service links default", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db"}
|
||||
|
||||
@ -95,7 +98,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
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"}
|
||||
|
||||
@ -115,7 +120,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
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"}
|
||||
|
||||
@ -135,7 +142,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
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.ExternalLinks = []string{"db1:db2"}
|
||||
@ -159,7 +168,9 @@ func TestServiceLinks(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{}
|
||||
s.ExternalLinks = []string{}
|
||||
@ -189,8 +200,11 @@ func TestServiceLinks(t *testing.T) {
|
||||
func TestWaitDependencies(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
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) {
|
||||
dbService := types.ServiceConfig{Name: "db", Scale: 0}
|
||||
|
@ -42,59 +42,80 @@ const (
|
||||
acrossServices = fromService | toService
|
||||
)
|
||||
|
||||
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
|
||||
srcService, srcPath := splitCpArg(opts.Source)
|
||||
destService, dstPath := splitCpArg(opts.Destination)
|
||||
func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
srcService, srcPath := splitCpArg(options.Source)
|
||||
destService, dstPath := splitCpArg(options.Destination)
|
||||
|
||||
var direction copyDirection
|
||||
var serviceName string
|
||||
var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
|
||||
if srcService != "" {
|
||||
direction |= fromService
|
||||
serviceName = srcService
|
||||
copyFunc = s.copyFromContainer
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
if destService != "" {
|
||||
direction |= toService
|
||||
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 {
|
||||
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{}
|
||||
for _, container := range containers {
|
||||
containerID := container.ID
|
||||
g.Go(func() error {
|
||||
switch direction {
|
||||
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 copyFunc(ctx, containerID, srcPath, dstPath, options)
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
var err error
|
||||
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.
|
||||
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 err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||
@ -119,7 +140,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||
}
|
||||
|
||||
dstInfo.Path = linkTarget
|
||||
dstStat, err = s.apiClient.ContainerStatPath(ctx, containerID, linkTarget)
|
||||
dstStat, err = s.apiClient().ContainerStatPath(ctx, containerID, linkTarget)
|
||||
}
|
||||
|
||||
// Validate the destination path
|
||||
@ -143,7 +164,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||
)
|
||||
|
||||
if srcPath == "-" {
|
||||
content = os.Stdin
|
||||
content = s.stdin()
|
||||
resolvedDstPath = dstInfo.Path
|
||||
if !dstInfo.IsDir {
|
||||
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,
|
||||
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 {
|
||||
@ -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
|
||||
var rebaseName string
|
||||
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 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 {
|
||||
return err
|
||||
}
|
||||
defer content.Close() //nolint:errcheck
|
||||
|
||||
if dstPath == "-" {
|
||||
_, err = io.Copy(os.Stdout, content)
|
||||
_, err = io.Copy(s.stdout(), content)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@ -31,6 +31,7 @@ import (
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
"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/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
@ -173,13 +174,21 @@ func prepareServicesDependsOn(p *types.Project) error {
|
||||
dependencies = append(dependencies, spec[0])
|
||||
}
|
||||
|
||||
for _, link := range service.Links {
|
||||
dependencies = append(dependencies, strings.Split(link, ":")[0])
|
||||
}
|
||||
|
||||
if len(dependencies) == 0 {
|
||||
continue
|
||||
}
|
||||
if service.DependsOn == nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -255,7 +264,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
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)
|
||||
|
||||
containerConfig := container.Config{
|
||||
@ -347,6 +356,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@ -371,7 +385,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
DNS: service.DNS,
|
||||
DNSSearch: service.DNSSearch,
|
||||
DNSOptions: service.DNSOpts,
|
||||
ExtraHosts: service.ExtraHosts,
|
||||
ExtraHosts: service.ExtraHosts.AsList(),
|
||||
SecurityOpt: securityOpts,
|
||||
UsernsMode: container.UsernsMode(service.UserNSMode),
|
||||
Privileged: service.Privileged,
|
||||
@ -381,6 +395,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
Runtime: service.Runtime,
|
||||
LogConfig: logConfig,
|
||||
GroupAdd: service.GroupAdd,
|
||||
Links: links,
|
||||
}
|
||||
|
||||
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" {
|
||||
f, err := ioutil.ReadFile(p.RelativePath(con[1]))
|
||||
f, err := os.ReadFile(p.RelativePath(con[1]))
|
||||
if err != nil {
|
||||
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,
|
||||
CPUPercent: int64(s.CPUS * 100),
|
||||
CpusetCpus: s.CPUSet,
|
||||
DeviceCgroupRules: s.DeviceCgroupRules,
|
||||
}
|
||||
|
||||
if s.PidsLimit != 0 {
|
||||
@ -579,8 +595,12 @@ func setLimits(limits *types.Resource, resources *container.Resources) {
|
||||
resources.Memory = int64(limits.MemoryBytes)
|
||||
}
|
||||
if limits.NanoCPUs != "" {
|
||||
i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
|
||||
resources.NanoCPUs = i
|
||||
if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
|
||||
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{}
|
||||
|
||||
image := getImageName(service, p.Name)
|
||||
imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
|
||||
imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -708,12 +728,20 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
|
||||
MOUNTS:
|
||||
for _, m := range mountOptions {
|
||||
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 {
|
||||
// `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 {
|
||||
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
|
||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
|
||||
continue MOUNTS
|
||||
if v.Target == m.Target {
|
||||
switch {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
var mounts = map[string]mount.Mount{}
|
||||
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)
|
||||
}
|
||||
|
||||
if definedSecret.Environment != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
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)
|
||||
|
||||
if bind != nil {
|
||||
volume.Type = types.VolumeTypeBind
|
||||
}
|
||||
|
||||
return mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: source,
|
||||
@ -937,7 +956,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||
}, 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 {
|
||||
case "bind":
|
||||
if volume.Volume != nil {
|
||||
@ -954,6 +973,11 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
|
||||
if volume.Tmpfs != nil {
|
||||
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
|
||||
case "tmpfs":
|
||||
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 {
|
||||
_, 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 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 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 {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eventName := fmt.Sprintf("Network %s", networkName)
|
||||
w.Event(progress.RemovingEvent(eventName))
|
||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
||||
createOpts.IPAM = &network.IPAM{}
|
||||
}
|
||||
|
||||
if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
|
||||
w.Event(progress.ErrorEvent(eventName))
|
||||
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
|
||||
if n.Ipam.Driver != "" {
|
||||
createOpts.IPAM.Driver = n.Ipam.Driver
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 !errdefs.IsNotFound(err) {
|
||||
return err
|
||||
@ -1123,7 +1143,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
Labels: volume.Labels,
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
|
@ -143,15 +143,6 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||
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) {
|
||||
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
|
@ -132,7 +132,7 @@ func getParents(v *Vertex) []*Vertex {
|
||||
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 {
|
||||
var res []*Vertex
|
||||
for _, p := range v.Parents {
|
||||
@ -145,7 +145,7 @@ func getChildren(v *Vertex) []*Vertex {
|
||||
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 {
|
||||
var res []*Vertex
|
||||
for _, p := range v.Children {
|
||||
@ -194,7 +194,7 @@ func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatu
|
||||
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 {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"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 {
|
||||
builtFromResources := options.Project == nil
|
||||
w := progress.ContextWriter(ctx)
|
||||
resourceToRemove := false
|
||||
|
||||
@ -51,8 +51,9 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return err
|
||||
}
|
||||
|
||||
if builtFromResources {
|
||||
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
|
||||
project := options.Project
|
||||
if project == nil {
|
||||
project, err = s.getProjectWithResources(ctx, containers, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -62,7 +63,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
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))
|
||||
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
|
||||
return err
|
||||
@ -71,7 +72,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return err
|
||||
}
|
||||
|
||||
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
|
||||
orphans := containers.filter(isNotService(project.ServiceNames()...))
|
||||
if options.RemoveOrphans && len(orphans) > 0 {
|
||||
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
|
||||
if err != nil {
|
||||
@ -79,21 +80,18 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
}
|
||||
|
||||
ops, err := s.ensureNetworksDown(ctx, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ops := s.ensureNetworksDown(ctx, project, w)
|
||||
|
||||
if options.Images != "" {
|
||||
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
|
||||
ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
@ -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 {
|
||||
var ops []downOp
|
||||
for _, vol := range project.Volumes {
|
||||
if vol.External.External {
|
||||
continue
|
||||
}
|
||||
volumeName := vol.Name
|
||||
ops = append(ops, func() error {
|
||||
return s.removeVolume(ctx, volumeName, w)
|
||||
@ -114,9 +115,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
|
||||
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
|
||||
for image := range s.getServiceImages(options, projectName) {
|
||||
for image := range s.getServiceImages(options, project) {
|
||||
image := image
|
||||
ops = append(ops, func() error {
|
||||
return s.removeImage(ctx, image, w)
|
||||
@ -125,31 +126,74 @@ func (s *composeService) ensureImagesDown(ctx context.Context, projectName strin
|
||||
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
|
||||
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(projectName))})
|
||||
if err != nil {
|
||||
return ops, err
|
||||
}
|
||||
for _, n := range networks {
|
||||
networkID := n.ID
|
||||
for _, n := range project.Networks {
|
||||
if n.External.External {
|
||||
continue
|
||||
}
|
||||
// loop capture variable for op closure
|
||||
networkName := n.Name
|
||||
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{}{}
|
||||
for _, service := range options.Project.Services {
|
||||
for _, service := range project.Services {
|
||||
image := service.Image
|
||||
if options.Images == "local" && image != "" {
|
||||
continue
|
||||
}
|
||||
if image == "" {
|
||||
image = getImageName(service, projectName)
|
||||
image = getImageName(service, project.Name)
|
||||
}
|
||||
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 {
|
||||
id := fmt.Sprintf("Image %s", image)
|
||||
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 {
|
||||
w.Event(progress.NewEvent(id, progress.Done, "Removed"))
|
||||
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 {
|
||||
resource := fmt.Sprintf("Volume %s", id)
|
||||
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 {
|
||||
w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
|
||||
return nil
|
||||
@ -193,7 +237,7 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StoppingEvent(eventName))
|
||||
err := s.apiClient.ContainerStop(ctx, container.ID, timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
||||
return err
|
||||
@ -218,7 +262,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
return err
|
||||
}
|
||||
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,
|
||||
RemoveVolumes: volumes,
|
||||
})
|
||||
@ -233,21 +277,23 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
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)
|
||||
project, _ := s.projectFromName(containers, projectName)
|
||||
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
project, err := s.projectFromName(containers, projectName)
|
||||
if err != nil && !api.IsNotFoundError(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
project.Volumes = types.Volumes{}
|
||||
for _, vol := range volumes.Volumes {
|
||||
project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
|
||||
Name: vol.Name,
|
||||
Driver: vol.Driver,
|
||||
Labels: vol.Labels,
|
||||
}
|
||||
volumes, err := s.actualVolumes(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Volumes = volumes
|
||||
|
||||
networks, err := s.actualNetworks(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project.Networks = networks
|
||||
return project, nil
|
||||
}
|
||||
|
@ -21,21 +21,24 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
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(
|
||||
[]moby.Container{
|
||||
@ -47,6 +50,14 @@ func TestDown(t *testing.T) {
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
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(), "456", 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(), "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"}},
|
||||
nil)
|
||||
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
||||
}).Return([]moby.NetworkResource{
|
||||
{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{})
|
||||
assert.NilError(t, err)
|
||||
@ -67,8 +82,11 @@ func TestDown(t *testing.T) {
|
||||
func TestDownRemoveOrphans(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
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(
|
||||
[]moby.Container{
|
||||
@ -78,6 +96,8 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
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(), "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(), "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"}},
|
||||
nil)
|
||||
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("name", "myProject_default")),
|
||||
}).Return([]moby.NetworkResource{{ID: "abc123", Name: "myProject_default"}}, nil)
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "abc123").Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
|
||||
assert.NilError(t, err)
|
||||
@ -99,8 +119,11 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
func TestDownRemoveVolumes(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
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(
|
||||
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
||||
@ -108,12 +131,12 @@ func TestDownRemoveVolumes(t *testing.T) {
|
||||
Return(volume.VolumeListOKBody{
|
||||
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
||||
}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return(nil, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().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)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
|
||||
|
@ -29,9 +29,10 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
|
||||
events, errors := s.apiClient.Events(ctx, moby.EventsOptions{
|
||||
Filters: filters.NewArgs(projectFilter(project)),
|
||||
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
})
|
||||
for {
|
||||
select {
|
||||
|
@ -18,149 +18,44 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"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) {
|
||||
container, err := s.getExecTarget(ctx, project, opts)
|
||||
func (s *composeService) Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
target, err := s.getExecTarget(ctx, projectName, options)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
exec, err := s.apiClient.ContainerExecCreate(ctx, container.ID, moby.ExecConfig{
|
||||
Cmd: opts.Command,
|
||||
Env: opts.Environment,
|
||||
User: opts.User,
|
||||
Privileged: opts.Privileged,
|
||||
Tty: opts.Tty,
|
||||
Detach: opts.Detach,
|
||||
WorkingDir: opts.WorkingDir,
|
||||
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
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)
|
||||
exec := container.NewExecOptions()
|
||||
exec.Interactive = options.Interactive
|
||||
exec.TTY = options.Tty
|
||||
exec.Detach = options.Detach
|
||||
exec.User = options.User
|
||||
exec.Privileged = options.Privileged
|
||||
exec.Workdir = options.WorkingDir
|
||||
exec.Container = target.ID
|
||||
exec.Command = options.Command
|
||||
for _, v := range options.Environment {
|
||||
err := exec.Env.Set(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.interactiveExec(ctx, opts, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
err = container.RunExec(s.dockerCli, exec)
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
return sterr.StatusCode, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
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
|
||||
return s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, opts.Service, opts.Index)
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ import (
|
||||
)
|
||||
|
||||
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,
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
})
|
||||
@ -83,7 +84,7 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
|
||||
for _, img := range images {
|
||||
img := img
|
||||
eg.Go(func() error {
|
||||
inspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, img)
|
||||
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return nil
|
||||
@ -93,7 +94,6 @@ func (s *composeService) getImages(ctx context.Context, images []string) (map[st
|
||||
tag := ""
|
||||
repository := ""
|
||||
if len(inspect.RepoTags) > 0 {
|
||||
|
||||
repotag := strings.Split(inspect.RepoTags[0], ":")
|
||||
repository = repotag[0]
|
||||
if len(repotag) > 1 {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user