mirror of
https://github.com/docker/compose.git
synced 2026-02-10 10:39:23 +08:00
Compare commits
1 Commits
v2.5.0
...
config_no_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d52363cd4 |
@@ -1,2 +1,3 @@
|
||||
.git/
|
||||
bin/
|
||||
dist/
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
6
.github/workflows/artifacts.yml
vendored
6
.github/workflows/artifacts.yml
vendored
@@ -42,12 +42,6 @@ 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:
|
||||
|
||||
53
.github/workflows/ci.yml
vendored
53
.github/workflows/ci.yml
vendored
@@ -22,15 +22,15 @@ jobs:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Validate go-mod, license headers and docs are up-to-date
|
||||
- name: Validate go-mod is up-to-date and license headers
|
||||
run: make validate
|
||||
|
||||
- name: Run golangci-lint
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
args: --timeout=180s
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/bin/ v1.39.0
|
||||
make -f builder.Makefile lint
|
||||
|
||||
# only on main branch, costs too much for the gain on every PR
|
||||
validate-cross-build:
|
||||
@@ -59,8 +59,8 @@ jobs:
|
||||
- name: Build packages
|
||||
run: make -f builder.Makefile cross
|
||||
|
||||
build-plugin:
|
||||
name: Build and tests in plugin mode
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
@@ -71,6 +71,10 @@ jobs:
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Set up gosum
|
||||
run: |
|
||||
go get -u gotest.tools/gotestsum
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
@@ -85,6 +89,8 @@ jobs:
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
BUILD_TAGS: kube
|
||||
run: make -f builder.Makefile test
|
||||
|
||||
- name: Build for local E2E
|
||||
@@ -92,38 +98,5 @@ jobs:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
|
||||
- name: E2E Test in plugin mode
|
||||
- name: E2E Test
|
||||
run: make e2e-compose
|
||||
|
||||
build-standalone:
|
||||
name: Build and tests in standalone mode
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
|
||||
- name: E2E Test in standalone mode
|
||||
run: make e2e-compose-standalone
|
||||
|
||||
5
.github/workflows/release.yaml
vendored
5
.github/workflows/release.yaml
vendored
@@ -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 --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
|
||||
run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
|
||||
|
||||
- name: License
|
||||
run: cp packaging/* bin/
|
||||
@@ -44,8 +44,7 @@ jobs:
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "bin/*"
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
prerelease: true
|
||||
commit: "v2"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag }}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
|
||||
* make
|
||||
* Linux:
|
||||
* [Docker 20.10 or later](https://docs.docker.com/engine/install/)
|
||||
* [Docker 19.03 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://www.docker.com/docker-community" 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://dockercommunity.slack.com/ssb/redirect" target="_blank">with this link</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
35
Makefile
35
Makefile
@@ -42,20 +42,8 @@ compose-plugin: ## Compile the compose cli-plugin
|
||||
--output ./bin
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go test $(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
|
||||
|
||||
.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
|
||||
|
||||
.PHONY: e2e
|
||||
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
e2e-compose: ## Run End to end local tests. Set E2E_TEST=TestName to run a single test
|
||||
gotestsum $(TEST_FLAGS) ./pkg/e2e -- -count=1
|
||||
|
||||
.PHONY: cross
|
||||
cross: ## Compile the CLI for linux, darwin and windows
|
||||
@@ -82,23 +70,6 @@ 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
|
||||
@@ -115,7 +86,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-docs ## Validate sources
|
||||
validate: validate-go-mod validate-headers ## Validate sources
|
||||
|
||||
pre-commit: validate check-dependencies lint compose-plugin test e2e-compose
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ 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
|
||||
@@ -71,3 +70,7 @@ 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
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
func getBoolFlags() []string {
|
||||
return []string{
|
||||
"--debug", "-D",
|
||||
"--verbose",
|
||||
"--tls",
|
||||
"--tlsverify",
|
||||
}
|
||||
}
|
||||
|
||||
func getStringFlags() []string {
|
||||
return []string{
|
||||
"--tlscacert",
|
||||
"--tlscert",
|
||||
"--tlskey",
|
||||
"--host", "-H",
|
||||
"--context",
|
||||
"--log-level",
|
||||
}
|
||||
}
|
||||
|
||||
// Convert transforms standalone docker-compose args into CLI plugin compliant ones
|
||||
func Convert(args []string) []string {
|
||||
var rootFlags []string
|
||||
command := []string{compose.PluginName}
|
||||
l := len(args)
|
||||
for i := 0; i < l; i++ {
|
||||
arg := args[i]
|
||||
if arg[0] != '-' {
|
||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||
if arg == compose.PluginName {
|
||||
i++
|
||||
}
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
if arg == "--verbose" {
|
||||
arg = "--debug"
|
||||
}
|
||||
if arg == "-h" {
|
||||
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
||||
arg = "--help"
|
||||
}
|
||||
if arg == "--version" || arg == "-v" {
|
||||
// redirect --version pseudo-command to actual command
|
||||
arg = "version"
|
||||
}
|
||||
if contains(getBoolFlags(), arg) {
|
||||
rootFlags = append(rootFlags, arg)
|
||||
continue
|
||||
}
|
||||
if contains(getStringFlags(), arg) {
|
||||
i++
|
||||
if i >= l {
|
||||
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
rootFlags = append(rootFlags, arg, args[i])
|
||||
continue
|
||||
}
|
||||
command = append(command, arg)
|
||||
}
|
||||
return append(rootFlags, command...)
|
||||
}
|
||||
|
||||
func contains(array []string, needle string) bool {
|
||||
for _, val := range array {
|
||||
if val == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func Test_convert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "compose only",
|
||||
args: []string{"up"},
|
||||
want: []string{"compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "with context",
|
||||
args: []string{"--context", "foo", "-f", "compose.yaml", "up"},
|
||||
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
|
||||
},
|
||||
{
|
||||
name: "with host",
|
||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "compose --version",
|
||||
args: []string{"--version"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
args: []string{"-h"},
|
||||
want: []string{"compose", "--help"},
|
||||
},
|
||||
{
|
||||
name: "issues/1962",
|
||||
args: []string{"psql", "-h", "postgres"},
|
||||
want: []string{"compose", "psql", "-h", "postgres"}, // -h should not be converted to --help
|
||||
},
|
||||
{
|
||||
name: "issues/8648",
|
||||
args: []string{"exec", "mongo", "mongo", "--host", "mongo"},
|
||||
want: []string{"compose", "exec", "mongo", "mongo", "--host", "mongo"}, // --host is passed to exec
|
||||
},
|
||||
{
|
||||
name: "issues/12",
|
||||
args: []string{"--log-level", "INFO", "up"},
|
||||
want: []string{"--log-level", "INFO", "compose", "up"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Convert(tt.args)
|
||||
assert.DeepEqual(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ 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"
|
||||
@@ -41,28 +40,6 @@ 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{
|
||||
@@ -96,10 +73,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
|
||||
opts.ssh = "default"
|
||||
}
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
@@ -108,7 +82,6 @@ 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")
|
||||
@@ -130,9 +103,12 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,18 +29,15 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"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
|
||||
@@ -89,6 +86,9 @@ 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
|
||||
@@ -120,6 +120,24 @@ func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
|
||||
return err
|
||||
}
|
||||
|
||||
if o.EnvFile != "" {
|
||||
var services types.Services
|
||||
for _, s := range project.Services {
|
||||
ef := o.EnvFile
|
||||
if ef != "" {
|
||||
if !filepath.IsAbs(ef) {
|
||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
||||
}
|
||||
if s.Labels == nil {
|
||||
s.Labels = make(map[string]string)
|
||||
}
|
||||
s.Labels[api.EnvironmentFileLabel] = ef
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
project.Services = services
|
||||
}
|
||||
|
||||
return fn(ctx, project, args)
|
||||
})
|
||||
}
|
||||
@@ -140,11 +158,6 @@ 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
|
||||
@@ -163,29 +176,10 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
|
||||
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
|
||||
ef := o.EnvFile
|
||||
if ef != "" && !filepath.IsAbs(ef) {
|
||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
||||
}
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
api.ServiceLabel: s.Name,
|
||||
api.VersionLabel: api.ComposeVersion,
|
||||
api.WorkingDirLabel: project.WorkingDir,
|
||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||
api.OneoffLabel: "False", // default, will be overridden by `run` command
|
||||
}
|
||||
if ef != "" {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = ef
|
||||
}
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
s, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
@@ -218,16 +212,15 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
|
||||
cli.WithName(o.ProjectName))...)
|
||||
}
|
||||
|
||||
// PluginName is the name of the plugin
|
||||
const PluginName = "compose"
|
||||
const pluginName = "compose"
|
||||
|
||||
// RunningAsStandalone detects when running as a standalone program
|
||||
func RunningAsStandalone() bool {
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != pluginName
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func RootCommand(backend api.Service) *cobra.Command {
|
||||
opts := projectOptions{}
|
||||
var (
|
||||
ansi string
|
||||
@@ -237,7 +230,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
Use: PluginName,
|
||||
Use: pluginName,
|
||||
TraverseChildren: true,
|
||||
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -275,12 +268,6 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
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`)
|
||||
@@ -303,9 +290,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
logsCommand(&opts, backend),
|
||||
convertCommand(&opts, backend),
|
||||
killCommand(&opts, backend),
|
||||
runCommand(&opts, dockerCli, backend),
|
||||
runCommand(&opts, backend),
|
||||
removeCommand(&opts, backend),
|
||||
execCommand(&opts, dockerCli, backend),
|
||||
execCommand(&opts, backend),
|
||||
pauseCommand(&opts, backend),
|
||||
unpauseCommand(&opts, backend),
|
||||
topCommand(&opts, backend),
|
||||
|
||||
@@ -48,7 +48,6 @@ type convertOptions struct {
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
}
|
||||
|
||||
@@ -86,9 +85,6 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if opts.profiles {
|
||||
return runProfiles(opts, args)
|
||||
}
|
||||
if opts.images {
|
||||
return runConfigImages(opts, args)
|
||||
}
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
@@ -104,7 +100,6 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
@@ -116,9 +111,7 @@ func runConvert(ctx context.Context, backend api.Service, opts convertOptions, s
|
||||
project, err := opts.toProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithNormalization(!opts.noNormalize),
|
||||
cli.WithDiscardEnvFile)
|
||||
|
||||
cli.WithNormalization(!opts.noNormalize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -222,18 +215,3 @@ func runProfiles(opts convertOptions, services []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigImages(opts convertOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if s.Image != "" {
|
||||
fmt.Println(s.Image)
|
||||
} else {
|
||||
fmt.Printf("%s_%s\n", project.Name, s.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
|
||||
name, err := opts.toProjectName()
|
||||
projects, err := opts.toProject(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Copy(ctx, name, api.CopyOptions{
|
||||
return backend.Copy(ctx, projects, api.CopyOptions{
|
||||
Source: opts.source,
|
||||
Destination: opts.destination,
|
||||
All: opts.all,
|
||||
|
||||
@@ -19,14 +19,10 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"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"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
@@ -62,19 +58,10 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS "))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
switch name {
|
||||
case "volume":
|
||||
name = "volumes"
|
||||
logrus.Warn("--volume is deprecated, please use --volumes")
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return downCmd
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ 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"
|
||||
@@ -35,15 +37,14 @@ type execOpts struct {
|
||||
environment []string
|
||||
workingDir string
|
||||
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
privileged bool
|
||||
interactive bool
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
privileged bool
|
||||
}
|
||||
|
||||
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := execOpts{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
@@ -69,14 +70,9 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "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
|
||||
}
|
||||
@@ -104,9 +100,27 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
Index: opts.index,
|
||||
Detach: opts.detach,
|
||||
WorkingDir: opts.workingDir,
|
||||
Interactive: opts.interactive,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
|
||||
if execOpts.Tty {
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := con.Reset(); err != nil {
|
||||
fmt.Println("Unable to close the console")
|
||||
}
|
||||
}()
|
||||
|
||||
execOpts.Stdin = con
|
||||
execOpts.Stdout = con
|
||||
execOpts.Stderr = con
|
||||
}
|
||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
|
||||
@@ -19,44 +19,25 @@ 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 {
|
||||
opts := killOptions{
|
||||
projectOptions: p,
|
||||
}
|
||||
var opts api.KillOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [options] [SERVICE...]",
|
||||
Short: "Force stop service containers.",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, backend, opts, args)
|
||||
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
|
||||
return backend.Kill(ctx, project, opts)
|
||||
}),
|
||||
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,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -92,24 +92,22 @@ func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
|
||||
view := viewFromStackList(stackList)
|
||||
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
|
||||
for _, stack := range view {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
|
||||
}
|
||||
}, "NAME", "STATUS", "CONFIG FILES")
|
||||
}, "NAME", "STATUS")
|
||||
}
|
||||
|
||||
type stackView struct {
|
||||
Name string
|
||||
Status string
|
||||
ConfigFiles string
|
||||
Name string
|
||||
Status string
|
||||
}
|
||||
|
||||
func viewFromStackList(stackList []api.Stack) []stackView {
|
||||
retList := make([]stackView, len(stackList))
|
||||
for i, s := range stackList {
|
||||
retList[i] = stackView{
|
||||
Name: s.Name,
|
||||
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
|
||||
ConfigFiles: s.ConfigFiles,
|
||||
Name: s.Name,
|
||||
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
|
||||
}
|
||||
}
|
||||
return retList
|
||||
|
||||
@@ -59,7 +59,7 @@ func (p *psOptions) parseFilter() error {
|
||||
case "source":
|
||||
return api.ErrNotImplemented
|
||||
default:
|
||||
return fmt.Errorf("unknown filter %s", parts[0])
|
||||
return fmt.Errorf("unknow filter %s", parts[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -81,11 +81,12 @@ 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 (supported filters: status).")
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property. Deprecated, use --status instead")
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Any data which is not in a volume will be lost.`,
|
||||
}
|
||||
|
||||
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, err := opts.toProjectName()
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.Duration(opts.timeout) * time.Second
|
||||
return backend.Restart(ctx, projectName, api.RestartOptions{
|
||||
return backend.Restart(ctx, project, api.RestartOptions{
|
||||
Timeout: &timeout,
|
||||
Services: services,
|
||||
})
|
||||
|
||||
@@ -19,12 +19,13 @@ 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-isatty"
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -42,7 +43,6 @@ type runOptions struct {
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
interactive bool
|
||||
user string
|
||||
workdir string
|
||||
entrypoint string
|
||||
@@ -54,8 +54,6 @@ type runOptions struct {
|
||||
servicePorts bool
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func (opts runOptions) apply(project *types.Project) error {
|
||||
@@ -63,9 +61,6 @@ 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{}
|
||||
}
|
||||
@@ -107,7 +102,7 @@ func (opts runOptions) apply(project *types.Project) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts := runOptions{
|
||||
composeOptions: &composeOptions{
|
||||
projectOptions: p,
|
||||
@@ -139,8 +134,6 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
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),
|
||||
@@ -150,7 +143,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
|
||||
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")
|
||||
@@ -160,11 +153,6 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
|
||||
cmd.Flags().BoolVarP(&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)
|
||||
@@ -181,6 +169,11 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
func notAtTTY() bool {
|
||||
b := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
|
||||
return !b
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
|
||||
err := opts.apply(project)
|
||||
if err != nil {
|
||||
@@ -188,7 +181,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, opts.ignoreOrphans)
|
||||
return startDependencies(ctx, backend, *project, opts.Service)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -210,8 +203,10 @@ 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,
|
||||
@@ -220,16 +215,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
UseNetworkAliases: opts.useAliases,
|
||||
NoDeps: opts.noDeps,
|
||||
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 := ""
|
||||
@@ -241,7 +227,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, ignoreOrphans bool) error {
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for _, service := range project.Services {
|
||||
@@ -254,15 +240,8 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices = append(project.DisabledServices, requestedService)
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
IgnoreOrphans: ignoreOrphans,
|
||||
})
|
||||
if err != nil {
|
||||
if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{})
|
||||
}
|
||||
return nil
|
||||
return backend.Start(ctx, &project, api.StartOptions{})
|
||||
}
|
||||
|
||||
@@ -43,12 +43,10 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Start(ctx, projectName, api.StartOptions{
|
||||
AttachTo: services,
|
||||
})
|
||||
return backend.Start(ctx, project, api.StartOptions{})
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName()
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return backend.Stop(ctx, projectName, api.StopOptions{
|
||||
return backend.Stop(ctx, project, api.StopOptions{
|
||||
Timeout: timeout,
|
||||
Services: services,
|
||||
})
|
||||
|
||||
@@ -103,7 +103,8 @@ 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 {
|
||||
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
create.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
if create.ignoreOrphans && create.removeOrphans {
|
||||
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
|
||||
}
|
||||
@@ -204,7 +205,6 @@ 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,
|
||||
|
||||
@@ -18,7 +18,6 @@ package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
@@ -53,11 +52,11 @@ func versionCommand() *cobra.Command {
|
||||
|
||||
func runVersion(opts versionOptions) {
|
||||
if opts.short {
|
||||
fmt.Println(strings.TrimPrefix(internal.Version, "v"))
|
||||
fmt.Println(internal.Version)
|
||||
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)
|
||||
|
||||
15
cmd/main.go
15
cmd/main.go
@@ -23,25 +23,30 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose-switch/redirect"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compatibility"
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
func 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(dockerCli, lazyInit)
|
||||
cmd := commands.RootCommand(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))
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli.Client(), dockerCli.ConfigFile()))
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
}
|
||||
@@ -63,8 +68,8 @@ func pluginMain() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if plugin.RunningStandalone() {
|
||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||
if commands.RunningAsStandalone() {
|
||||
os.Args = append([]string{"docker"}, redirect.Convert(os.Args[1:])...)
|
||||
}
|
||||
pluginMain()
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# syntax=docker/dockerfile:1.3-labs
|
||||
|
||||
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.17
|
||||
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,54 +1,4 @@
|
||||
# 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 Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
@@ -59,8 +9,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:
|
||||
@@ -80,7 +30,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
|
||||
@@ -91,22 +41,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
|
||||
@@ -114,17 +64,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
|
||||
@@ -134,8 +84,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.
|
||||
@@ -149,6 +99,3 @@ 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,30 +1,12 @@
|
||||
# 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,35 +1,9 @@
|
||||
# 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`
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# docker compose cp
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Copy files/folders between a service container and the local filesystem
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--all` | | | Copy to all the containers of the service. |
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `1` | Index of the container if there are multiple instances of a service [default: 1]. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# 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,19 +1,4 @@
|
||||
# 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,16 +1,3 @@
|
||||
# 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,26 +1,7 @@
|
||||
# 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.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# 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,16 +1,3 @@
|
||||
# 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,22 +1,3 @@
|
||||
# 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,19 +1,3 @@
|
||||
# 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,10 +1,3 @@
|
||||
# docker compose pause
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Pause services
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
# 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,117 +1,11 @@
|
||||
# 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 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
|
||||
NAME SERVICE STATUS PORTS
|
||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
||||
example_bar_1 bar exited (1)
|
||||
```
|
||||
|
||||
## 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,26 +1,11 @@
|
||||
# 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`:
|
||||
|
||||
@@ -39,8 +24,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
|
||||
@@ -61,4 +46,4 @@ $ docker compose pull db
|
||||
⠹ f63c47038e66 Waiting 9.3s
|
||||
⠹ 77a0c198cde5 Waiting 9.3s
|
||||
⠹ c8752d5b785c Waiting 9.3s
|
||||
``̀`
|
||||
``̀
|
||||
@@ -1,16 +1,3 @@
|
||||
# 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,24 +1,8 @@
|
||||
# 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,23 +1,3 @@
|
||||
# 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,35 +1,7 @@
|
||||
# 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:
|
||||
|
||||
@@ -40,12 +12,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
|
||||
@@ -58,8 +30,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
|
||||
@@ -80,5 +52,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,10 +1,3 @@
|
||||
# docker compose start
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Start services
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
# 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,10 +1,3 @@
|
||||
# docker compose top
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Display the running processes
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
@@ -16,5 +9,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,10 +1,3 @@
|
||||
# docker compose unpause
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Unpause services
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
# 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
|
||||
|
||||
@@ -37,12 +5,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 (liked `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.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# 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,9 +98,6 @@ 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
|
||||
@@ -129,7 +126,6 @@ cname:
|
||||
- docker compose top
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
clink:
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_convert.yaml
|
||||
@@ -155,7 +151,6 @@ clink:
|
||||
- docker_compose_top.yaml
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
options:
|
||||
- option: ansi
|
||||
value_type: string
|
||||
@@ -163,17 +158,6 @@ 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
|
||||
@@ -182,7 +166,6 @@ options:
|
||||
value_type: string
|
||||
description: Specify an alternate environment file.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -193,7 +176,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Compose configuration files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -203,7 +185,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Do not print ANSI control characters (DEPRECATED)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -213,7 +194,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Specify a profile to enable
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -224,7 +204,6 @@ options:
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -234,7 +213,6 @@ options:
|
||||
value_type: string
|
||||
description: Project name
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -244,18 +222,6 @@ 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
|
||||
@@ -267,7 +233,6 @@ options:
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -19,7 +19,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set build-time variables for services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -29,7 +28,6 @@ options:
|
||||
default_value: "true"
|
||||
description: Compress the build context using gzip. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -39,7 +37,6 @@ options:
|
||||
default_value: "true"
|
||||
description: Always remove intermediate containers. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -50,7 +47,6 @@ options:
|
||||
description: |
|
||||
Set memory limit for the build container. Not supported on buildkit yet.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -60,7 +56,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Do not use cache when building the image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -71,7 +66,6 @@ options:
|
||||
description: |
|
||||
Do not remove intermediate containers after a successful build. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -81,7 +75,6 @@ options:
|
||||
default_value: "true"
|
||||
description: Build images in parallel. DEPRECATED
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -89,9 +82,8 @@ options:
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of progress output (auto, tty, plain, quiet)
|
||||
description: Set type of progress output ("auto", "plain", "noTty")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -101,7 +93,6 @@ 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
|
||||
@@ -112,17 +103,6 @@ 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,7 +16,6 @@ options:
|
||||
default_value: yaml
|
||||
description: 'Format the output. Values: [yaml | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -25,17 +24,6 @@ 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
|
||||
@@ -45,27 +33,6 @@ 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
|
||||
@@ -75,7 +42,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the profile names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -86,7 +52,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Only validate the configuration, don't print anything.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -96,7 +61,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Pin image tags to digests.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -106,7 +70,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the service names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -116,7 +79,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Print the volume names, one per line.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -11,7 +11,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Copy to all the containers of the service.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -22,7 +21,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Archive mode (copy all uid/gid information)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -33,7 +31,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Always follow symbol link in SRC_PATH
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -44,7 +41,6 @@ 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
|
||||
|
||||
@@ -10,7 +10,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Build images before starting containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -21,7 +20,6 @@ options:
|
||||
description: |
|
||||
Recreate containers even if their configuration and image haven't changed.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -31,7 +29,6 @@ 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
|
||||
@@ -42,7 +39,6 @@ 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,7 +23,6 @@ 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
|
||||
@@ -33,7 +32,6 @@ 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
|
||||
@@ -44,7 +42,6 @@ options:
|
||||
default_value: "10"
|
||||
description: Specify a shutdown timeout in seconds
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -56,7 +53,6 @@ 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,7 +29,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Output events as a stream of json objects
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -15,7 +15,6 @@ options:
|
||||
default_value: "false"
|
||||
description: 'Detached mode: Run command in the background.'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -26,7 +25,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set environment variables
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -37,18 +35,6 @@ 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
|
||||
@@ -56,11 +42,10 @@ options:
|
||||
- option: no-TTY
|
||||
shorthand: T
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
default_value: "false"
|
||||
description: |
|
||||
Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -70,18 +55,6 @@ 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
|
||||
@@ -91,7 +64,6 @@ options:
|
||||
value_type: string
|
||||
description: Run the command as this user.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -101,7 +73,6 @@ options:
|
||||
value_type: string
|
||||
description: Path to workdir directory for this command.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -11,7 +11,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Only display IDs
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -16,7 +16,6 @@ options:
|
||||
default_value: SIGKILL
|
||||
description: SIGNAL to send to the container.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -11,7 +11,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Follow log output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -21,7 +20,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Produce monochrome output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -31,7 +29,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't print prefix in logs.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -41,7 +38,6 @@ 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
|
||||
@@ -52,7 +48,6 @@ 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
|
||||
@@ -63,7 +58,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Show timestamps.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -73,7 +67,6 @@ 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,7 +11,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Show all stopped Compose projects
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -20,7 +19,6 @@ options:
|
||||
value_type: filter
|
||||
description: Filter output based on conditions provided.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -30,7 +28,6 @@ options:
|
||||
default_value: pretty
|
||||
description: 'Format the output. Values: [pretty | json].'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -41,7 +38,6 @@ 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,7 +10,6 @@ options:
|
||||
default_value: "1"
|
||||
description: index of the container if service has multiple replicas
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -20,7 +19,6 @@ options:
|
||||
default_value: tcp
|
||||
description: tcp or udp
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -2,13 +2,12 @@ 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 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
|
||||
NAME SERVICE STATUS PORTS
|
||||
example_foo_1 foo running (healthy) 0.0.0.0:8000->80/tcp
|
||||
example_bar_1 bar exited (1)
|
||||
```
|
||||
usage: docker compose ps [SERVICE...]
|
||||
pname: docker compose
|
||||
@@ -21,17 +20,14 @@ 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 (supported filters: status).'
|
||||
details_url: '#filter'
|
||||
description: Filter services by a property
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -40,9 +36,7 @@ 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
|
||||
@@ -53,7 +47,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Only display IDs
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -63,108 +56,18 @@ options:
|
||||
default_value: "false"
|
||||
description: Display services
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: status
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: |
|
||||
Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]
|
||||
details_url: '#status'
|
||||
value_type: string
|
||||
description: Filter services by 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,7 +12,6 @@ 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
|
||||
@@ -22,7 +21,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Also pull services declared as dependencies
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -32,7 +30,6 @@ options:
|
||||
default_value: "true"
|
||||
description: DEPRECATED disable parallel pulling.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -42,7 +39,6 @@ options:
|
||||
default_value: "true"
|
||||
description: DEPRECATED pull multiple images in parallel.
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -53,52 +49,10 @@ 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,7 +28,6 @@ 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,16 +1,6 @@
|
||||
command: docker compose restart
|
||||
short: 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).
|
||||
long: Restart containers
|
||||
usage: docker compose restart
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
@@ -21,7 +11,6 @@ options:
|
||||
default_value: "10"
|
||||
description: Specify a shutdown timeout in seconds
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -26,7 +26,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Deprecated - no effect
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -37,7 +36,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't ask to confirm removal
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -48,7 +46,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Stop the containers, if required, before removing
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -59,7 +56,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Remove any anonymous volumes attached to containers
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -65,7 +65,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Run container in background and print container ID
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -74,7 +73,6 @@ options:
|
||||
value_type: string
|
||||
description: Override the entrypoint of the image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -85,29 +83,16 @@ options:
|
||||
default_value: '[]'
|
||||
description: Set environment variables
|
||||
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: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: label
|
||||
- option: labels
|
||||
shorthand: l
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Add or override a label
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -116,7 +101,6 @@ options:
|
||||
value_type: string
|
||||
description: Assign a name to the container
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -124,10 +108,10 @@ options:
|
||||
- option: no-TTY
|
||||
shorthand: T
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
description: 'Disable pseudo-TTY allocation (default: auto-detected).'
|
||||
default_value: "false"
|
||||
description: |
|
||||
Disable pseudo-noTty allocation. By default docker compose run allocates a TTY
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -137,7 +121,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start linked services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -148,17 +131,6 @@ 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
|
||||
@@ -168,7 +140,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Automatically remove the container when it exits
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -179,18 +150,6 @@ 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
|
||||
@@ -201,7 +160,6 @@ 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
|
||||
@@ -211,7 +169,6 @@ options:
|
||||
value_type: string
|
||||
description: Run as specified username or uid
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -222,7 +179,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Bind mount a volume.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -232,7 +188,6 @@ options:
|
||||
value_type: string
|
||||
description: Working directory inside the container
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -12,7 +12,6 @@ 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
|
||||
|
||||
@@ -5,7 +5,7 @@ long: |-
|
||||
|
||||
Unless they are already running, this command also starts any linked services.
|
||||
|
||||
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
|
||||
The `docker compose up` command aggregates the output of each container (liked `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.
|
||||
|
||||
@@ -27,7 +27,6 @@ options:
|
||||
description: |
|
||||
Stops all containers if any container was stopped. Incompatible with -d
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -37,7 +36,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Recreate dependent containers. Incompatible with --no-recreate.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -47,7 +45,6 @@ options:
|
||||
default_value: '[]'
|
||||
description: Attach to service output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -57,7 +54,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Attach to dependent containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -67,7 +63,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Build images before starting containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -78,7 +73,16 @@ options:
|
||||
default_value: "false"
|
||||
description: 'Detached mode: Run containers in the background'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: environment
|
||||
shorthand: e
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Environment variables
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -88,7 +92,6 @@ 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
|
||||
@@ -99,7 +102,6 @@ options:
|
||||
description: |
|
||||
Recreate containers even if their configuration and image haven't changed.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -109,7 +111,6 @@ 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
|
||||
@@ -119,7 +120,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Produce monochrome output.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -129,7 +129,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start linked services.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -139,7 +138,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't print prefix in logs.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -150,7 +148,6 @@ options:
|
||||
description: |
|
||||
If containers already exist, don't recreate them. Incompatible with --force-recreate.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -160,7 +157,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Don't start the services after creating them.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -170,7 +166,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Pull without printing progress information.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -180,7 +175,6 @@ 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
|
||||
@@ -192,7 +186,6 @@ options:
|
||||
description: |
|
||||
Recreate anonymous volumes instead of retrieving data from the previous containers.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -203,7 +196,6 @@ 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
|
||||
@@ -215,17 +207,6 @@ 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,7 +10,6 @@ options:
|
||||
value_type: string
|
||||
description: 'Format the output. Values: [pretty | json]. (Default: pretty)'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -20,7 +19,6 @@ options:
|
||||
default_value: "false"
|
||||
description: Shows only Compose's version number.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
|
||||
@@ -22,33 +22,17 @@ 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 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))
|
||||
func generateCliYaml(opts *options) error {
|
||||
cmd := &cobra.Command{Use: "docker"}
|
||||
cmd.AddCommand(compose.RootCommand(nil))
|
||||
disableFlagsInUseLine(cmd)
|
||||
|
||||
tool, err := clidocstool.New(clidocstool.Options{
|
||||
Root: cmd,
|
||||
SourceDir: opts.source,
|
||||
TargetDir: opts.target,
|
||||
Plugin: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tool.GenAllTree()
|
||||
cmd.DisableAutoGenTag = true
|
||||
return clidocstool.GenYamlTree(cmd, opts.target)
|
||||
}
|
||||
|
||||
func disableFlagsInUseLine(cmd *cobra.Command) {
|
||||
@@ -76,12 +60,12 @@ type options struct {
|
||||
func main() {
|
||||
cwd, _ := os.Getwd()
|
||||
opts := &options{
|
||||
source: filepath.Join(cwd, "docs", "reference"),
|
||||
source: cwd,
|
||||
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 := generateDocs(opts); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Failed to generate documentation: %s\n", err.Error())
|
||||
if err := generateCliYaml(opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
148
go.mod
148
go.mod
@@ -3,145 +3,129 @@ module github.com/docker/compose/v2
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3
|
||||
github.com/buger/goterm v1.0.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v1.2.4
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.2
|
||||
github.com/compose-spec/compose-go v1.0.6-0.20211119110428-ae73362e0b78
|
||||
github.com/containerd/console v1.0.2
|
||||
github.com/containerd/containerd v1.5.8
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||
github.com/docker/buildx v0.8.1 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.12+incompatible
|
||||
github.com/docker/cli-docs-tool v0.4.0
|
||||
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
|
||||
github.com/docker/cli v20.10.7+incompatible
|
||||
github.com/docker/cli-docs-tool v0.1.1
|
||||
github.com/docker/compose-switch v1.0.2
|
||||
github.com/docker/docker v20.10.7+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/golang/mock v1.5.0
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b
|
||||
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf
|
||||
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.1
|
||||
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.4.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/theupdateframework/notary v0.6.1
|
||||
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.0.3
|
||||
)
|
||||
|
||||
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/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.23 // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
|
||||
github.com/containerd/continuity v0.2.2 // indirect
|
||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||
github.com/compose-spec/godotenv v1.1.0 // indirect
|
||||
github.com/containerd/cgroups v1.0.1 // indirect
|
||||
github.com/containerd/continuity v0.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/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // 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/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/logr v0.4.0 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/googleapis v1.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/gofuzz v1.1.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/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/klauspost/compress v1.11.13 // indirect
|
||||
github.com/kr/pty v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // 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/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/signal v0.6.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/moby/sys/mount v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.4.1 // indirect
|
||||
github.com/moby/sys/symlink v0.1.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/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/common v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // 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/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // 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/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-20211202192323-5770296d904e // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.44.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.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.23.4 // indirect; see replace for the actual version used
|
||||
k8s.io/client-go v0.23.4 // indirect; see replace for the actual version used
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
k8s.io/apimachinery v0.21.0 // indirect
|
||||
k8s.io/client-go v0.21.0 // indirect
|
||||
k8s.io/klog/v2 v2.8.0 // indirect
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
|
||||
sigs.k8s.io/yaml v1.2.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
|
||||
|
||||
// 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
|
||||
)
|
||||
// (for buildx)
|
||||
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||
|
||||
@@ -19,6 +19,7 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -32,15 +33,15 @@ 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, options PullOptions) error
|
||||
Pull(ctx context.Context, project *types.Project, opts PullOptions) error
|
||||
// Create executes the equivalent to a `compose create`
|
||||
Create(ctx context.Context, project *types.Project, options CreateOptions) error
|
||||
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
// Start executes the equivalent to a `compose start`
|
||||
Start(ctx context.Context, projectName string, options StartOptions) error
|
||||
Start(ctx context.Context, project *types.Project, options StartOptions) error
|
||||
// Restart restarts containers
|
||||
Restart(ctx context.Context, projectName string, options RestartOptions) error
|
||||
Restart(ctx context.Context, project *types.Project, options RestartOptions) error
|
||||
// Stop executes the equivalent to a `compose stop`
|
||||
Stop(ctx context.Context, projectName string, options StopOptions) error
|
||||
Stop(ctx context.Context, project *types.Project, options StopOptions) error
|
||||
// Up executes the equivalent to a `compose up`
|
||||
Up(ctx context.Context, project *types.Project, options UpOptions) error
|
||||
// Down executes the equivalent to a `compose down`
|
||||
@@ -54,25 +55,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, projectName string, options KillOptions) error
|
||||
Kill(ctx context.Context, project *types.Project, 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, projectName string, options RemoveOptions) error
|
||||
Remove(ctx context.Context, project *types.Project, options RemoveOptions) error
|
||||
// Exec executes a command in a running service container
|
||||
Exec(ctx context.Context, projectName string, options RunOptions) (int, error)
|
||||
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||
// Copy copies a file/folder between a service container and the local filesystem
|
||||
Copy(ctx context.Context, projectName string, options CopyOptions) error
|
||||
Copy(ctx context.Context, project *types.Project, opts CopyOptions) error
|
||||
// Pause executes the equivalent to a `compose pause`
|
||||
Pause(ctx context.Context, projectName string, options PauseOptions) error
|
||||
Pause(ctx context.Context, project string, options PauseOptions) error
|
||||
// UnPause executes the equivalent to a `compose unpause`
|
||||
UnPause(ctx context.Context, projectName string, options PauseOptions) error
|
||||
UnPause(ctx context.Context, project 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, projectName string, options EventsOptions) error
|
||||
Events(ctx context.Context, project string, options EventsOptions) error
|
||||
// Port executes the equivalent to a `compose port`
|
||||
Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
|
||||
Port(ctx context.Context, project 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)
|
||||
}
|
||||
@@ -91,8 +92,6 @@ 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
|
||||
@@ -117,8 +116,6 @@ 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
|
||||
@@ -219,8 +216,10 @@ 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
|
||||
@@ -228,8 +227,6 @@ type RunOptions struct {
|
||||
Privileged bool
|
||||
UseNetworkAliases bool
|
||||
NoDeps bool
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
@@ -405,11 +402,10 @@ const (
|
||||
|
||||
// Stack holds the name and state of a compose application/stack
|
||||
type Stack struct {
|
||||
ID string
|
||||
Name string
|
||||
Status string
|
||||
ConfigFiles string
|
||||
Reason string
|
||||
ID string
|
||||
Name string
|
||||
Status string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// LogConsumer is a callback to process log messages from services
|
||||
@@ -438,8 +434,6 @@ const (
|
||||
ContainerEventLog = iota
|
||||
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
|
||||
ContainerEventAttach
|
||||
// ContainerEventStopped is a ContainerEvent of type stopped.
|
||||
ContainerEventStopped
|
||||
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
|
||||
ContainerEventExit
|
||||
// UserCancel user cancelled compose up, we are stopping containers
|
||||
|
||||
@@ -28,20 +28,20 @@ type ServiceProxy struct {
|
||||
PushFn func(ctx context.Context, project *types.Project, options PushOptions) error
|
||||
PullFn func(ctx context.Context, project *types.Project, opts PullOptions) error
|
||||
CreateFn func(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
StartFn func(ctx context.Context, projectName string, options StartOptions) error
|
||||
RestartFn func(ctx context.Context, projectName string, options RestartOptions) error
|
||||
StopFn func(ctx context.Context, projectName string, options StopOptions) error
|
||||
StartFn func(ctx context.Context, project *types.Project, options StartOptions) error
|
||||
RestartFn func(ctx context.Context, project *types.Project, options RestartOptions) error
|
||||
StopFn func(ctx context.Context, project *types.Project, options StopOptions) error
|
||||
UpFn func(ctx context.Context, project *types.Project, options UpOptions) error
|
||||
DownFn func(ctx context.Context, projectName string, options DownOptions) error
|
||||
LogsFn func(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
|
||||
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 string, options KillOptions) error
|
||||
KillFn func(ctx context.Context, project *types.Project, options KillOptions) error
|
||||
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||
RemoveFn func(ctx context.Context, project string, options RemoveOptions) error
|
||||
RemoveFn func(ctx context.Context, project *types.Project, options RemoveOptions) error
|
||||
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||
CopyFn func(ctx context.Context, project string, options CopyOptions) error
|
||||
CopyFn func(ctx context.Context, project *types.Project, opts CopyOptions) error
|
||||
PauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||
UnPauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||
TopFn func(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||
@@ -141,27 +141,36 @@ func (s *ServiceProxy) Create(ctx context.Context, project *types.Project, optio
|
||||
}
|
||||
|
||||
// Start implements Service interface
|
||||
func (s *ServiceProxy) Start(ctx context.Context, projectName string, options StartOptions) error {
|
||||
func (s *ServiceProxy) Start(ctx context.Context, project *types.Project, options StartOptions) error {
|
||||
if s.StartFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.StartFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.StartFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Restart implements Service interface
|
||||
func (s *ServiceProxy) Restart(ctx context.Context, projectName string, options RestartOptions) error {
|
||||
func (s *ServiceProxy) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
|
||||
if s.RestartFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.RestartFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.RestartFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Stop implements Service interface
|
||||
func (s *ServiceProxy) Stop(ctx context.Context, projectName string, options StopOptions) error {
|
||||
func (s *ServiceProxy) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
|
||||
if s.StopFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.StopFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.StopFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Up implements Service interface
|
||||
@@ -219,11 +228,14 @@ func (s *ServiceProxy) Convert(ctx context.Context, project *types.Project, opti
|
||||
}
|
||||
|
||||
// Kill implements Service interface
|
||||
func (s *ServiceProxy) Kill(ctx context.Context, projectName string, options KillOptions) error {
|
||||
func (s *ServiceProxy) Kill(ctx context.Context, project *types.Project, options KillOptions) error {
|
||||
if s.KillFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.KillFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.KillFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// RunOneOffContainer implements Service interface
|
||||
@@ -238,43 +250,49 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
|
||||
}
|
||||
|
||||
// Remove implements Service interface
|
||||
func (s *ServiceProxy) Remove(ctx context.Context, projectName string, options RemoveOptions) error {
|
||||
func (s *ServiceProxy) Remove(ctx context.Context, project *types.Project, options RemoveOptions) error {
|
||||
if s.RemoveFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.RemoveFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.RemoveFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Exec implements Service interface
|
||||
func (s *ServiceProxy) Exec(ctx context.Context, projectName string, options RunOptions) (int, error) {
|
||||
func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOptions) (int, error) {
|
||||
if s.ExecFn == nil {
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
return s.ExecFn(ctx, projectName, options)
|
||||
return s.ExecFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Copy implements Service interface
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, projectName string, options CopyOptions) error {
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, project *types.Project, options CopyOptions) error {
|
||||
if s.CopyFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.CopyFn(ctx, projectName, options)
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.CopyFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Pause implements Service interface
|
||||
func (s *ServiceProxy) Pause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||
func (s *ServiceProxy) Pause(ctx context.Context, project string, options PauseOptions) error {
|
||||
if s.PauseFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.PauseFn(ctx, projectName, options)
|
||||
return s.PauseFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// UnPause implements Service interface
|
||||
func (s *ServiceProxy) UnPause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||
func (s *ServiceProxy) UnPause(ctx context.Context, project string, options PauseOptions) error {
|
||||
if s.UnPauseFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.UnPauseFn(ctx, projectName, options)
|
||||
return s.UnPauseFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Top implements Service interface
|
||||
@@ -286,19 +304,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
|
||||
}
|
||||
|
||||
// Events implements Service interface
|
||||
func (s *ServiceProxy) Events(ctx context.Context, projectName string, options EventsOptions) error {
|
||||
func (s *ServiceProxy) Events(ctx context.Context, project string, options EventsOptions) error {
|
||||
if s.EventsFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.EventsFn(ctx, projectName, options)
|
||||
return s.EventsFn(ctx, project, options)
|
||||
}
|
||||
|
||||
// Port implements Service interface
|
||||
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
|
||||
func (s *ServiceProxy) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
|
||||
if s.PortFn == nil {
|
||||
return "", 0, ErrNotImplemented
|
||||
}
|
||||
return s.PortFn(ctx, projectName, service, port, options)
|
||||
return s.PortFn(ctx, project, 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)
|
||||
err := s.attachContainer(ctx, container, listener, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,9 +56,13 @@ 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) error {
|
||||
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener, project *types.Project) 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,
|
||||
@@ -74,13 +78,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w, w)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -139,7 +137,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,
|
||||
@@ -153,7 +151,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,6 +19,8 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
@@ -27,12 +29,11 @@ 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 +65,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, options.SSHs)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,6 +83,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
|
||||
opts[imageName] = buildOptions
|
||||
}
|
||||
}
|
||||
@@ -140,7 +142,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
if project.Services[i].Labels == nil {
|
||||
project.Services[i].Labels = types.Labels{}
|
||||
}
|
||||
project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
|
||||
project.Services[i].Labels[api.ImageDigestLabel] = digest
|
||||
project.Services[i].Image = image
|
||||
}
|
||||
}
|
||||
@@ -160,7 +162,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, []types.SSHKey{})
|
||||
opt, err := s.toBuildOptions(project, service, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -188,29 +190,37 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
||||
for name, info := range imgs {
|
||||
images[name] = info.ID
|
||||
}
|
||||
|
||||
for _, s := range project.Services {
|
||||
imgName := getImageName(s, project.Name)
|
||||
digest, ok := images[imgName]
|
||||
if ok {
|
||||
s.CustomLabels[api.ImageDigestLabel] = digest
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
serverInfo := command.ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
BuildkitVersion: ping.BuilderVersion,
|
||||
}
|
||||
return serverInfo, err
|
||||
}
|
||||
|
||||
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
if len(opts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
|
||||
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)
|
||||
}
|
||||
return s.doBuildBuildkit(ctx, project, opts, mode)
|
||||
}
|
||||
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
|
||||
var tags []string
|
||||
tags = append(tags, imageTag)
|
||||
|
||||
@@ -235,55 +245,11 @@ 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)
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -292,7 +258,9 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
Labels: service.Build.Labels,
|
||||
NetworkMode: service.Build.Network,
|
||||
ExtraHosts: service.Build.ExtraHosts,
|
||||
Session: sessionConfig,
|
||||
Session: []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(os.Stderr),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -321,19 +289,8 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
|
||||
}
|
||||
|
||||
func dockerFilePath(context string, dockerfile string) string {
|
||||
if urlutil.IsGitURL(context) || filepath.IsAbs(dockerfile) {
|
||||
if urlutil.IsGitURL(context) || path.IsAbs(dockerfile) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(context, dockerfile)
|
||||
}
|
||||
|
||||
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
|
||||
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
|
||||
for _, sshKey := range sshKeys {
|
||||
sshConfig = append(sshConfig, sshprovider.AgentConfig{
|
||||
ID: sshKey.ID,
|
||||
Paths: []string{sshKey.Path},
|
||||
})
|
||||
}
|
||||
return sshprovider.NewSSHAgentProvider(sshConfig)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/buildx/build"
|
||||
@@ -29,7 +28,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, project.WorkingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -45,10 +44,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, s.stdout(), os.Stdout, mode)
|
||||
w := xprogress.NewPrinter(progressCtx, 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, nil, w)
|
||||
errW := w.Wait()
|
||||
if err == nil {
|
||||
err = errW
|
||||
|
||||
@@ -69,8 +69,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
|
||||
dockerfileName := options.Inputs.DockerfilePath
|
||||
specifiedContext := options.Inputs.ContextPath
|
||||
progBuff := s.stdout()
|
||||
buildBuff := s.stdout()
|
||||
progBuff := os.Stdout
|
||||
buildBuff := os.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,10 +143,19 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
return "", err
|
||||
}
|
||||
|
||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
// 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
|
||||
}
|
||||
|
||||
configFile := s.configFile()
|
||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||
var body io.Reader
|
||||
if buildCtx != nil {
|
||||
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
}
|
||||
|
||||
configFile := s.configFile
|
||||
creds, err := configFile.GetAllCredentials()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -162,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
|
||||
}
|
||||
@@ -172,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(s.stderr(), "Failed to parse aux message: %s", err)
|
||||
fmt.Fprintf(os.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
|
||||
@@ -194,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(s.stdout(), "SECURITY WARNING: You are building a Docker "+
|
||||
fmt.Fprintln(os.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 "+
|
||||
@@ -222,7 +231,6 @@ func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
|
||||
return dockertypes.ImageBuildOptions{
|
||||
Tags: options.Tags,
|
||||
NoCache: options.NoCache,
|
||||
Remove: true,
|
||||
PullParent: options.Pull,
|
||||
BuildArgs: toMapStringStringPtr(options.BuildArgs),
|
||||
Labels: options.Labels,
|
||||
|
||||
@@ -21,18 +21,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -40,41 +36,19 @@ import (
|
||||
var Separator = "-"
|
||||
|
||||
// NewComposeService create a local implementation of the compose.Service API
|
||||
func NewComposeService(dockerCli command.Cli) api.Service {
|
||||
func NewComposeService(apiClient client.APIClient, configFile *configfile.ConfigFile) api.Service {
|
||||
return &composeService{
|
||||
dockerCli: dockerCli,
|
||||
apiClient: apiClient,
|
||||
configFile: configFile,
|
||||
}
|
||||
}
|
||||
|
||||
type composeService struct {
|
||||
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()
|
||||
apiClient client.APIClient
|
||||
configFile *configfile.ConfigFile
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -118,115 +92,3 @@ func escapeDollarSign(marshal []byte) []byte {
|
||||
escDollar := []byte{'$', '$'}
|
||||
return bytes.ReplaceAll(marshal, dollar, escDollar)
|
||||
}
|
||||
|
||||
// projectFromName builds a types.Project based on actual resources with compose labels set
|
||||
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
|
||||
}
|
||||
set := map[string]*types.ServiceConfig{}
|
||||
for _, c := range containers {
|
||||
serviceLabel := c.Labels[api.ServiceLabel]
|
||||
_, ok := set[serviceLabel]
|
||||
if !ok {
|
||||
set[serviceLabel] = &types.ServiceConfig{
|
||||
Name: serviceLabel,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
}
|
||||
set[serviceLabel].Scale++
|
||||
}
|
||||
for _, service := range set {
|
||||
dependencies := service.Labels[api.DependenciesLabel]
|
||||
if len(dependencies) > 0 {
|
||||
service.DependsOn = types.DependsOnConfig{}
|
||||
for _, dc := range strings.Split(dependencies, ",") {
|
||||
dcArr := strings.Split(dc, ":")
|
||||
condition := ServiceConditionRunningOrHealthy
|
||||
dependency := dcArr[0]
|
||||
|
||||
// backward compatibility
|
||||
if len(dcArr) > 1 {
|
||||
condition = dcArr[1]
|
||||
}
|
||||
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition}
|
||||
}
|
||||
}
|
||||
project.Services = append(project.Services, *service)
|
||||
}
|
||||
SERVICES:
|
||||
for _, qs := range services {
|
||||
for _, es := range project.Services {
|
||||
if es.Name == qs {
|
||||
continue SERVICES
|
||||
}
|
||||
}
|
||||
return project, errors.Wrapf(api.ErrNotFound, "no such service: %q", qs)
|
||||
}
|
||||
err := project.ForServices(services)
|
||||
if err != nil {
|
||||
return project, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -52,7 +51,7 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
|
||||
f = append(f, oneOffFilter(false))
|
||||
case oneOffInclude:
|
||||
}
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(f...),
|
||||
All: stopped,
|
||||
})
|
||||
@@ -87,14 +86,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,20 +180,26 @@ 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
|
||||
}
|
||||
|
||||
mustRecreate, err := mustRecreate(service, container, recreate)
|
||||
if recreate == api.RecreateNever {
|
||||
continue
|
||||
}
|
||||
// Re-create diverged containers
|
||||
configHash, err := ServiceHash(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mustRecreate {
|
||||
name := getContainerProgressName(container)
|
||||
diverged := container.Labels[api.ConfigHashLabel] != configHash
|
||||
if diverged || recreate == api.RecreateForce || service.Extensions[extLifecycle] == forceRecreate {
|
||||
i, container := i, container
|
||||
eg.Go(func() error {
|
||||
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
|
||||
@@ -205,7 +211,6 @@ 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))
|
||||
@@ -244,22 +249,6 @@ 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 != "" {
|
||||
@@ -272,33 +261,11 @@ func getContainerProgressName(container moby.Container) string {
|
||||
return "Container " + getCanonicalContainerName(container)
|
||||
}
|
||||
|
||||
func containerEvents(containers Containers, eventFunc func(string) progress.Event) []progress.Event {
|
||||
events := []progress.Event{}
|
||||
for _, container := range containers {
|
||||
events = append(events, eventFunc(getContainerProgressName(container)))
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// ServiceConditionRunningOrHealthy is a service condition on statys running or healthy
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
w := progress.ContextWriter(ctx)
|
||||
for dep, config := range dependencies {
|
||||
if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil {
|
||||
return err
|
||||
} else if !shouldWait {
|
||||
continue
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Events(containerEvents(containers, progress.Waiting))
|
||||
|
||||
dep, config := dep, config
|
||||
eg.Go(func() error {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
@@ -312,7 +279,6 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
w.Events(containerEvents(containers, progress.Healthy))
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionHealthy:
|
||||
@@ -321,7 +287,6 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
w.Events(containerEvents(containers, progress.Healthy))
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionCompletedSuccessfully:
|
||||
@@ -330,12 +295,14 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if exited {
|
||||
w.Events(containerEvents(containers, progress.Exited))
|
||||
if code != 0 {
|
||||
return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, code)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionStarted:
|
||||
// already managed by InDependencyOrder
|
||||
return nil
|
||||
default:
|
||||
logrus.Warnf("unsupported depends_on condition: %s", config.Condition)
|
||||
return nil
|
||||
@@ -346,20 +313,6 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
|
||||
if dependencyConfig.Condition == types.ServiceConditionStarted {
|
||||
// already managed by InDependencyOrder
|
||||
return false, nil
|
||||
}
|
||||
if service, err := project.GetService(serviceName); err != nil {
|
||||
return false, err
|
||||
} else if service.Scale == 0 {
|
||||
// don't wait for the dependency which configured to have 0 containers running
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func nextContainerNumber(containers []moby.Container) (int, error) {
|
||||
max := 0
|
||||
for _, c := range containers {
|
||||
@@ -406,13 +359,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
|
||||
}
|
||||
@@ -430,7 +383,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
|
||||
}
|
||||
@@ -455,7 +408,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
|
||||
}
|
||||
@@ -479,11 +432,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
|
||||
}
|
||||
@@ -495,10 +448,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||
},
|
||||
}
|
||||
links, err := s.getLinks(ctx, project.Name, service, number)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
links := append(service.Links, service.ExternalLinks...)
|
||||
for _, netName := range service.NetworksByPriority() {
|
||||
netwrk := project.Networks[netName]
|
||||
cfg := service.Networks[netName]
|
||||
@@ -513,7 +463,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
|
||||
}
|
||||
@@ -526,64 +476,6 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
return created, err
|
||||
}
|
||||
|
||||
// getLinks mimics V1 compose/service.py::Service::_get_links()
|
||||
func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) {
|
||||
var links []string
|
||||
format := func(k, v string) string {
|
||||
return fmt.Sprintf("%s:%s", k, v)
|
||||
}
|
||||
getServiceContainers := func(serviceName string) (Containers, error) {
|
||||
return s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
}
|
||||
|
||||
for _, rawLink := range service.Links {
|
||||
linkSplit := strings.Split(rawLink, ":")
|
||||
linkServiceName := linkSplit[0]
|
||||
linkName := linkServiceName
|
||||
if len(linkSplit) == 2 {
|
||||
linkName = linkSplit[1] // linkName if informed like in: "serviceName:linkName"
|
||||
}
|
||||
cnts, err := getServiceContainers(linkServiceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, linkName),
|
||||
format(containerName, strings.Join([]string{linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if service.Labels[api.OneoffLabel] == "True" {
|
||||
cnts, err := getServiceContainers(service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, service.Name),
|
||||
format(containerName, strings.TrimPrefix(containerName, projectName+Separator)),
|
||||
format(containerName, containerName),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rawExtLink := range service.ExternalLinks {
|
||||
extLinkSplit := strings.Split(rawExtLink, ":")
|
||||
externalLink := extLinkSplit[0]
|
||||
linkName := externalLink
|
||||
if len(extLinkSplit) == 2 {
|
||||
linkName = extLinkSplit[1]
|
||||
}
|
||||
links = append(links, format(externalLink, linkName))
|
||||
}
|
||||
return links, nil
|
||||
}
|
||||
|
||||
func shortIDAliasExists(containerID string, aliases ...string) bool {
|
||||
for _, alias := range aliases {
|
||||
if alias == containerID[:12] {
|
||||
@@ -607,7 +499,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,
|
||||
@@ -630,7 +522,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
|
||||
}
|
||||
@@ -642,15 +534,8 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||
if container.State == nil || container.State.Health == nil {
|
||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||
}
|
||||
switch container.State.Health.Status {
|
||||
case moby.Healthy:
|
||||
// Continue by checking the next container.
|
||||
case moby.Unhealthy:
|
||||
return false, fmt.Errorf("container for service %q is unhealthy", service)
|
||||
case moby.Starting:
|
||||
if container.State.Health.Status != moby.Healthy {
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("container for service %q had unexpected health status %q", service, container.State.Health.Status)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
@@ -662,7 +547,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
|
||||
}
|
||||
@@ -674,15 +559,11 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.waitDependencies(ctx, project, service.DependsOn)
|
||||
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),
|
||||
@@ -698,7 +579,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
if scale, err := getScale(service); err != nil && scale == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("service %q has no container to start", service.Name)
|
||||
return fmt.Errorf("no containers to start")
|
||||
}
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
@@ -711,7 +592,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))
|
||||
}
|
||||
|
||||
@@ -17,17 +17,10 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
@@ -53,177 +46,3 @@ func TestContainerName(t *testing.T) {
|
||||
_, err = getScale(s)
|
||||
assert.Error(t, err, fmt.Sprintf(doubledContainerNameWarning, s.Name, s.ContainerName))
|
||||
}
|
||||
|
||||
func TestServiceLinks(t *testing.T) {
|
||||
const dbContainerName = "/" + testProject + "-db-1"
|
||||
const webContainerName = "/" + testProject + "-web-1"
|
||||
s := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Scale: 1,
|
||||
}
|
||||
|
||||
containerListOptions := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("db"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
|
||||
t.Run("service links default", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links name", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links external links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
s.ExternalLinks = []string{"db1:db2"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 4)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
|
||||
// ExternalLink
|
||||
assert.Equal(t, links[3], "db1:db2")
|
||||
})
|
||||
|
||||
t.Run("service links itself oneoff", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(apiClient).AnyTimes()
|
||||
|
||||
s.Links = []string{}
|
||||
s.ExternalLinks = []string{}
|
||||
s.Labels = s.Labels.Add(api.OneoffLabel, "True")
|
||||
|
||||
c := testContainer("web", webContainerName, true)
|
||||
containerListOptionsOneOff := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("web"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-web-1:web")
|
||||
assert.Equal(t, links[1], "testProject-web-1:web-1")
|
||||
assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWaitDependencies(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
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}
|
||||
redisService := types.ServiceConfig{Name: "redis", Scale: 0}
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
|
||||
dependencies := types.DependsOnConfig{
|
||||
"db": {Condition: ServiceConditionRunningOrHealthy},
|
||||
"redis": {Condition: ServiceConditionRunningOrHealthy},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
|
||||
})
|
||||
t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
|
||||
dbService := types.ServiceConfig{Name: "db", Scale: 1}
|
||||
redisService := types.ServiceConfig{Name: "redis", Scale: 1}
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
|
||||
dependencies := types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted},
|
||||
"redis": {Condition: types.ServiceConditionStarted},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ import (
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/pkg/archive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/pkg/errors"
|
||||
@@ -42,10 +44,9 @@ const (
|
||||
acrossServices = fromService | toService
|
||||
)
|
||||
|
||||
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)
|
||||
func (s *composeService) Copy(ctx context.Context, project *types.Project, opts api.CopyOptions) error {
|
||||
srcService, srcPath := splitCpArg(opts.Source)
|
||||
destService, dstPath := splitCpArg(opts.Destination)
|
||||
|
||||
var direction copyDirection
|
||||
var serviceName string
|
||||
@@ -54,7 +55,7 @@ func (s *composeService) Copy(ctx context.Context, projectName string, options a
|
||||
serviceName = srcService
|
||||
|
||||
// copying from multiple containers of a services doesn't make sense.
|
||||
if options.All {
|
||||
if opts.All {
|
||||
return errors.New("cannot use the --all flag when copying from a service")
|
||||
}
|
||||
}
|
||||
@@ -63,17 +64,20 @@ func (s *composeService) Copy(ctx context.Context, projectName string, options a
|
||||
serviceName = destService
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
f := filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
serviceFilter(serviceName),
|
||||
)
|
||||
if !opts.All {
|
||||
f.Add("label", fmt.Sprintf("%s=%d", api.ContainerNumberLabel, opts.Index))
|
||||
}
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{Filters: f})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(containers) < 1 {
|
||||
return fmt.Errorf("no container found for service %q", serviceName)
|
||||
}
|
||||
|
||||
if !options.All {
|
||||
containers = containers.filter(indexed(options.Index))
|
||||
return fmt.Errorf("service %s not running", serviceName)
|
||||
}
|
||||
|
||||
g := errgroup.Group{}
|
||||
@@ -82,9 +86,9 @@ func (s *composeService) Copy(ctx context.Context, projectName string, options a
|
||||
g.Go(func() error {
|
||||
switch direction {
|
||||
case fromService:
|
||||
return s.copyFromContainer(ctx, containerID, srcPath, dstPath, options)
|
||||
return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts)
|
||||
case toService:
|
||||
return s.copyToContainer(ctx, containerID, srcPath, dstPath, options)
|
||||
return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts)
|
||||
case acrossServices:
|
||||
return errors.New("copying between services is not supported")
|
||||
default:
|
||||
@@ -108,7 +112,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 {
|
||||
@@ -120,7 +124,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
|
||||
@@ -144,7 +148,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
|
||||
)
|
||||
|
||||
if srcPath == "-" {
|
||||
content = s.stdin()
|
||||
content = os.Stdin
|
||||
resolvedDstPath = dstInfo.Path
|
||||
if !dstInfo.IsDir {
|
||||
return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
|
||||
@@ -188,7 +192,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 {
|
||||
@@ -208,7 +212,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 {
|
||||
@@ -224,14 +228,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(s.stdout(), content)
|
||||
_, err = io.Copy(os.Stdout, content)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -173,10 +173,6 @@ 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
|
||||
}
|
||||
@@ -233,7 +229,7 @@ func getImageName(service types.ServiceConfig, projectName string) string {
|
||||
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig,
|
||||
number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
||||
|
||||
labels, err := s.prepareLabels(service, number)
|
||||
labels, err := s.prepareLabels(p, service, number)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -259,7 +255,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{
|
||||
@@ -384,7 +380,6 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
Isolation: container.Isolation(service.Isolation),
|
||||
Runtime: service.Runtime,
|
||||
LogConfig: logConfig,
|
||||
GroupAdd: service.GroupAdd,
|
||||
}
|
||||
|
||||
return &containerConfig, &hostConfig, networkConfig, nil
|
||||
@@ -418,42 +413,45 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
|
||||
return securityOpts, nil
|
||||
}
|
||||
|
||||
func (s *composeService) prepareLabels(service types.ServiceConfig, number int) (map[string]string, error) {
|
||||
func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
|
||||
labels := map[string]string{}
|
||||
for k, v := range service.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
for k, v := range service.CustomLabels {
|
||||
labels[k] = v
|
||||
|
||||
labels[api.ProjectLabel] = p.Name
|
||||
labels[api.ServiceLabel] = service.Name
|
||||
labels[api.VersionLabel] = api.ComposeVersion
|
||||
if _, ok := service.Labels[api.OneoffLabel]; !ok {
|
||||
labels[api.OneoffLabel] = "False"
|
||||
}
|
||||
|
||||
hash, err := ServiceHash(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels[api.ConfigHashLabel] = hash
|
||||
|
||||
labels[api.WorkingDirLabel] = p.WorkingDir
|
||||
labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
|
||||
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
|
||||
|
||||
var dependencies []string
|
||||
for s, d := range service.DependsOn {
|
||||
dependencies = append(dependencies, s+":"+d.Condition)
|
||||
for s := range service.DependsOn {
|
||||
dependencies = append(dependencies, s)
|
||||
}
|
||||
labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
|
||||
if len(project.Networks) == 0 {
|
||||
return "none"
|
||||
mode := "none"
|
||||
if len(project.Networks) > 0 {
|
||||
for name := range getNetworksForService(service) {
|
||||
mode = project.Networks[name].Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(service.Networks) > 0 {
|
||||
name := service.NetworksByPriority()[0]
|
||||
return project.Networks[name].Name
|
||||
}
|
||||
|
||||
return project.Networks["default"].Name
|
||||
return mode
|
||||
}
|
||||
|
||||
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
|
||||
@@ -504,7 +502,6 @@ 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 {
|
||||
@@ -643,11 +640,15 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
|
||||
bindings := nat.PortMap{}
|
||||
for _, port := range s.Ports {
|
||||
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
|
||||
bind := bindings[p]
|
||||
binding := nat.PortBinding{
|
||||
HostIP: port.HostIP,
|
||||
HostPort: port.Published,
|
||||
HostIP: port.HostIP,
|
||||
}
|
||||
bindings[p] = append(bindings[p], binding)
|
||||
if port.Published > 0 {
|
||||
binding.HostPort = fmt.Sprint(port.Published)
|
||||
}
|
||||
bind = append(bind, binding)
|
||||
bindings[p] = bind
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
@@ -698,7 +699,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
|
||||
}
|
||||
@@ -713,13 +714,15 @@ 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, v.String())
|
||||
mode := "rw"
|
||||
if m.ReadOnly {
|
||||
mode = "ro"
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
|
||||
continue MOUNTS
|
||||
}
|
||||
}
|
||||
@@ -996,19 +999,21 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||
return aliases
|
||||
}
|
||||
|
||||
func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
|
||||
if len(s.Networks) > 0 {
|
||||
return s.Networks
|
||||
}
|
||||
if s.NetworkMode != "" {
|
||||
return nil
|
||||
}
|
||||
return map[string]*types.ServiceNetworkConfig{"default": nil}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
||||
_, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
||||
_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
||||
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
|
||||
@@ -1048,17 +1053,14 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
|
||||
|
||||
for _, ipamConfig := range n.Ipam.Config {
|
||||
config := network.IPAMConfig{
|
||||
Subnet: ipamConfig.Subnet,
|
||||
IPRange: ipamConfig.IPRange,
|
||||
Gateway: ipamConfig.Gateway,
|
||||
AuxAddress: ipamConfig.AuxiliaryAddresses,
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
@@ -1070,13 +1072,14 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) removeNetwork(ctx context.Context, network string, w progress.Writer) error {
|
||||
eventName := fmt.Sprintf("Network %s", network)
|
||||
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 err := s.apiClient().NetworkRemove(ctx, network); err != nil {
|
||||
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", network))
|
||||
return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", networkID))
|
||||
}
|
||||
|
||||
w.Event(progress.RemovedEvent(eventName))
|
||||
@@ -1084,29 +1087,23 @@ func (s *composeService) removeNetwork(ctx context.Context, network string, w pr
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if volume.External.External {
|
||||
return fmt.Errorf("external volume %q not found", volume.Name)
|
||||
}
|
||||
err := s.createVolume(ctx, volume)
|
||||
return err
|
||||
}
|
||||
|
||||
if volume.External.External {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Volume exists with name, but let's double-check this is the expected one
|
||||
// Volume exists with name, but let's double check this is the expected one
|
||||
// (better safe than sorry when it comes to user's data)
|
||||
p, ok := inspected.Labels[api.ProjectLabel]
|
||||
if !ok {
|
||||
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
return fmt.Errorf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
}
|
||||
if ok && p != project {
|
||||
logrus.Warnf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
|
||||
if p != project {
|
||||
return fmt.Errorf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1115,7 +1112,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,
|
||||
|
||||
@@ -142,74 +142,3 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
|
||||
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
||||
}
|
||||
|
||||
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{
|
||||
Name: "myService",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"myNetwork1": {
|
||||
Priority: 10,
|
||||
},
|
||||
"myNetwork2": {
|
||||
Priority: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
|
||||
"myNetwork1": {
|
||||
Name: "myProject_myNetwork1",
|
||||
},
|
||||
"myNetwork2": {
|
||||
Name: "myProject_myNetwork2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_myNetwork2")
|
||||
})
|
||||
|
||||
t.Run("returns default network when service has no networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
|
||||
"myNetwork1": {
|
||||
Name: "myProject_myNetwork1",
|
||||
},
|
||||
"myNetwork2": {
|
||||
Name: "myProject_myNetwork2",
|
||||
},
|
||||
"default": {
|
||||
Name: "myProject_default",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_default")
|
||||
})
|
||||
|
||||
t.Run("returns none if project has no networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "none")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
for _, node := range nodes {
|
||||
// Don't start this service yet if all of its children have
|
||||
// not been started yet.
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
return err
|
||||
}
|
||||
|
||||
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
|
||||
graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
|
||||
|
||||
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
|
||||
})
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/registry/client"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -50,19 +50,15 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return err
|
||||
}
|
||||
|
||||
project := options.Project
|
||||
if project == nil {
|
||||
project, err = s.getProjectWithResources(ctx, containers, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if options.Project == nil {
|
||||
options.Project = s.projectFromContainerLabels(containers.filter(isNotOneOff), projectName)
|
||||
}
|
||||
|
||||
if len(containers) > 0 {
|
||||
resourceToRemove = true
|
||||
}
|
||||
|
||||
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
err = InReverseDependencyOrder(ctx, options.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 +67,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return err
|
||||
}
|
||||
|
||||
orphans := containers.filter(isNotService(project.ServiceNames()...))
|
||||
orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
|
||||
if options.RemoveOrphans && len(orphans) > 0 {
|
||||
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
|
||||
if err != nil {
|
||||
@@ -79,14 +75,21 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
}
|
||||
|
||||
ops := s.ensureNetworksDown(ctx, project, w)
|
||||
ops, err := s.ensureNetworksDown(ctx, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Images != "" {
|
||||
ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
|
||||
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
|
||||
}
|
||||
|
||||
if options.Volumes {
|
||||
ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
|
||||
rm, err := s.ensureVolumesDown(ctx, projectName, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ops = append(ops, rm...)
|
||||
}
|
||||
|
||||
if !resourceToRemove && len(ops) == 0 {
|
||||
@@ -100,23 +103,24 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||
func (s *composeService) ensureVolumesDown(ctx context.Context, projectName string, w progress.Writer) ([]downOp, error) {
|
||||
var ops []downOp
|
||||
for _, vol := range project.Volumes {
|
||||
if vol.External.External {
|
||||
continue
|
||||
}
|
||||
volumeName := vol.Name
|
||||
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
return ops, err
|
||||
}
|
||||
for _, vol := range volumes.Volumes {
|
||||
id := vol.Name
|
||||
ops = append(ops, func() error {
|
||||
return s.removeVolume(ctx, volumeName, w)
|
||||
return s.removeVolume(ctx, id, w)
|
||||
})
|
||||
}
|
||||
return ops
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
|
||||
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
|
||||
var ops []downOp
|
||||
for image := range s.getServiceImages(options, project) {
|
||||
for image := range s.getServiceImages(options, projectName) {
|
||||
image := image
|
||||
ops = append(ops, func() error {
|
||||
return s.removeImage(ctx, image, w)
|
||||
@@ -125,34 +129,31 @@ func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Pr
|
||||
return ops
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||
func (s *composeService) ensureNetworksDown(ctx context.Context, projectName string) ([]downOp, error) {
|
||||
var ops []downOp
|
||||
for _, n := range project.Networks {
|
||||
if n.External.External {
|
||||
continue
|
||||
}
|
||||
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
|
||||
networkName := n.Name
|
||||
_, err := s.apiClient().NetworkInspect(ctx, networkName, moby.NetworkInspectOptions{})
|
||||
if client.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ops = append(ops, func() error {
|
||||
return s.removeNetwork(ctx, networkName, w)
|
||||
return s.removeNetwork(ctx, networkID, networkName)
|
||||
})
|
||||
}
|
||||
return ops
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
|
||||
func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
|
||||
images := map[string]struct{}{}
|
||||
for _, service := range project.Services {
|
||||
for _, service := range options.Project.Services {
|
||||
image := service.Image
|
||||
if options.Images == "local" && image != "" {
|
||||
continue
|
||||
}
|
||||
if image == "" {
|
||||
image = getImageName(service, project.Name)
|
||||
image = getImageName(service, projectName)
|
||||
}
|
||||
images[image] = struct{}{}
|
||||
}
|
||||
@@ -162,7 +163,7 @@ func (s *composeService) getServiceImages(options api.DownOptions, project *type
|
||||
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
|
||||
@@ -177,7 +178,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
|
||||
@@ -196,7 +197,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
|
||||
@@ -221,7 +222,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,
|
||||
})
|
||||
@@ -236,23 +237,31 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
|
||||
containers = containers.filter(isNotOneOff)
|
||||
project, err := s.projectFromName(containers, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) *types.Project {
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
}
|
||||
|
||||
volumes, err := s.actualVolumes(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(containers) == 0 {
|
||||
return project
|
||||
}
|
||||
project.Volumes = volumes
|
||||
|
||||
networks, err := s.actualNetworks(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
set := map[string]moby.Container{}
|
||||
for _, c := range containers {
|
||||
set[c.Labels[api.ServiceLabel]] = c
|
||||
}
|
||||
project.Networks = networks
|
||||
return project, nil
|
||||
for s, c := range set {
|
||||
service := types.ServiceConfig{
|
||||
Name: s,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
dependencies := c.Labels[api.DependenciesLabel]
|
||||
if len(dependencies) > 0 {
|
||||
service.DependsOn = types.DependsOnConfig{}
|
||||
for _, d := range strings.Split(dependencies, ",") {
|
||||
service.DependsOn[d] = types.ServiceDependency{}
|
||||
}
|
||||
}
|
||||
project.Services = append(project.Services, service)
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
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"
|
||||
@@ -33,11 +34,8 @@ import (
|
||||
func TestDown(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||
tested.apiClient = api
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||
[]moby.Container{
|
||||
@@ -46,10 +44,6 @@ func TestDown(t *testing.T) {
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, 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(), "456", nil).Return(nil)
|
||||
@@ -59,7 +53,9 @@ 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().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, 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)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
|
||||
@@ -69,11 +65,8 @@ func TestDown(t *testing.T) {
|
||||
func TestDownRemoveOrphans(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||
tested.apiClient = api
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||
[]moby.Container{
|
||||
@@ -81,10 +74,6 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, 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)
|
||||
@@ -94,7 +83,9 @@ 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().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, 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)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
|
||||
@@ -104,24 +95,18 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
func TestDownRemoveVolumes(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
cli := mocks.NewMockCli(mockCtrl)
|
||||
tested.dockerCli = cli
|
||||
cli.EXPECT().Client().Return(api).AnyTimes()
|
||||
tested.apiClient = api
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
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().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).Return(volume.VolumeListOKBody{Volumes: []*moby.Volume{{Name: "myProject_volume"}}}, nil)
|
||||
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
|
||||
|
||||
@@ -29,10 +29,9 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
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)),
|
||||
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)),
|
||||
})
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -19,47 +19,128 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"io"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/moby/term"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
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)
|
||||
func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) {
|
||||
container, err := s.getExecTarget(ctx, project, opts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
err = container.RunExec(s.dockerCli, exec)
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
return sterr.StatusCode, nil
|
||||
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() {
|
||||
state, err := term.SetRawTerminal(in.FD())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck
|
||||
}
|
||||
|
||||
go func() {
|
||||
if opts.Tty {
|
||||
_, err := io.Copy(opts.Stdout, stdout)
|
||||
outputDone <- err
|
||||
} else {
|
||||
_, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout)
|
||||
outputDone <- err
|
||||
}
|
||||
stdout.Close() //nolint:errcheck
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(stdin, r)
|
||||
inputDone <- err
|
||||
stdin.Close() //nolint:errcheck
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-outputDone:
|
||||
return err
|
||||
case err := <-inputDone:
|
||||
if _, ok := err.(term.EscapeError); ok {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Wait for output to complete streaming
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
serviceFilter(opts.Service),
|
||||
@@ -75,3 +156,11 @@ func (s *composeService) getExecTarget(ctx context.Context, projectName string,
|
||||
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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user