Merge branch 'v2' into 8768-avoid-pulling-same-image-multiple-times

This commit is contained in:
Vedant Koditkar 2022-08-06 17:58:10 +05:30
commit e623b5ca1e
127 changed files with 3001 additions and 2385 deletions

View File

@ -1,63 +0,0 @@
name: Publish Artifacts
on:
issue_comment:
types: [created]
jobs:
publish-artifacts:
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.18.3
id: go
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build cross platform compose-plugin binaries
run: make -f builder.Makefile cross
- name: Upload macos-amd64 binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-darwin-amd64
path: ${{ github.workspace }}/bin/docker-compose-darwin-amd64
- name: Upload macos-arm64 binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-darwin-arm64
path: ${{ github.workspace }}/bin/docker-compose-darwin-arm64
- name: Upload linux-amd64 binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-linux-amd64
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
- name: Upload linux-ppc64le binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-linux-ppc64le
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
- name: Upload windows-amd64 binary
uses: actions/upload-artifact@v2
with:
name: docker-compose-windows-amd64.exe
path: ${{ github.workspace }}/bin/docker-compose-windows-amd64.exe
- name: Update comment
uses: peter-evans/create-or-update-comment@v1
with:
comment-id: ${{ github.event.comment.id }}
body: |
This PR can be tested using [binaries](https://github.com/docker/compose-cli/actions/runs/${{ github.run_id }}).
reactions: eyes

View File

@ -11,22 +11,22 @@ on:
description: 'To run with tmate enter "debug_enabled"' description: 'To run with tmate enter "debug_enabled"'
required: false required: false
default: "false" default: "false"
env:
GO_VERSION: 1.18.5
DOCKER_CLI_VERSION: 20.10.17
jobs: jobs:
lint: lint:
name: Lint name: Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps: steps:
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.18.3
id: go
- name: Checkout code into the Go module directory - name: Checkout code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Validate go-mod, license headers and docs are up-to-date - name: Validate go-mod, license headers and docs are up-to-date
run: make validate run: make validate
@ -34,8 +34,9 @@ jobs:
- name: Run golangci-lint - name: Run golangci-lint
env: env:
BUILD_TAGS: e2e BUILD_TAGS: e2e
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3
with: with:
version: v1.47.3
args: --timeout=180s args: --timeout=180s
# only on main branch, costs too much for the gain on every PR # only on main branch, costs too much for the gain on every PR
@ -43,22 +44,15 @@ jobs:
name: Validate cross build name: Validate cross build
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
env:
GO111MODULE: "on"
steps: steps:
- name: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.18.3
id: go
- name: Checkout code into the Go module directory - name: Checkout code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v3
- uses: actions/cache@v2 - name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with: with:
path: ~/go/pkg/mod go-version: ${{ env.GO_VERSION }}
key: go-${{ hashFiles('**/go.sum') }} cache: true
# Ensure we don't discover cross platform build issues at release time. # Ensure we don't discover cross platform build issues at release time.
# Time used to build linux here is gained back in the build for local E2E step # Time used to build linux here is gained back in the build for local E2E step
@ -68,28 +62,21 @@ jobs:
build-plugin: build-plugin:
name: Build and tests in plugin mode name: Build and tests in plugin mode
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps: steps:
- name: Set up Go 1.18 - name: Checkout code into the Go module directory
uses: actions/setup-go@v2 uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with: with:
go-version: 1.18.3 go-version: ${{ env.GO_VERSION }}
id: go cache: true
- name: Setup docker CLI - name: Setup docker CLI
run: | run: |
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Test - name: Test
run: make -f builder.Makefile test run: make -f builder.Makefile test
@ -104,28 +91,21 @@ jobs:
build-standalone: build-standalone:
name: Build and tests in standalone mode name: Build and tests in standalone mode
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps: steps:
- name: Set up Go 1.18 - name: Checkout code into the Go module directory
uses: actions/setup-go@v2 uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with: with:
go-version: 1.18.3 go-version: ${{ env.GO_VERSION }}
id: go cache: true
- name: Setup docker CLI - name: Setup docker CLI
run: | run: |
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version 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 - name: Build for local E2E
env: env:
BUILD_TAGS: e2e BUILD_TAGS: e2e

View File

@ -40,6 +40,7 @@ jobs:
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4 uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
with: with:
token: ${{ secrets.GHPAT_DOCS_DISPATCH }} token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
push-to-fork: docker-tools-robot/docker.github.io
commit-message: Update Compose reference API to ${{ github.event.release.name }} commit-message: Update Compose reference API to ${{ github.event.release.name }}
signoff: true signoff: true
branch: dispatch/compose-api-reference-${{ github.event.release.name }} branch: dispatch/compose-api-reference-${{ github.event.release.name }}
@ -47,5 +48,4 @@ jobs:
title: Update Compose reference API to ${{ github.event.release.name }} title: Update Compose reference API to ${{ github.event.release.name }}
body: | body: |
Update the Compose reference API documentation to keep in sync with the latest release `${{ github.event.release.name }}` Update the Compose reference API documentation to keep in sync with the latest release `${{ github.event.release.name }}`
labels: area/Compose
draft: false draft: false

View File

@ -1,11 +0,0 @@
name: PR cleanup
on:
pull_request:
types: [closed]
jobs:
delete_pr_artifacts:
runs-on: ubuntu-latest
steps:
- uses: stefanluptak/delete-old-pr-artifacts@v1
with:
workflow_filename: ci.yaml

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo fetch-depth: 0 # otherwise, you will fail to push refs to dest repo

View File

@ -6,32 +6,26 @@ on:
tag: tag:
description: "Release Tag" description: "Release Tag"
required: true required: true
env:
GO_VERSION: 1.18.5
jobs: jobs:
upload-release: upload-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go 1.18 - name: Checkout code into the Go module directory
uses: actions/setup-go@v2 uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with: with:
go-version: 1.18.3 go-version: ${{ env.GO_VERSION }}
id: go cache: true
- name: Setup docker CLI - name: Setup docker CLI
run: | run: |
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.17.tgz | tar xz
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Build - name: Build
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross

View File

@ -7,6 +7,7 @@ linters:
- deadcode - deadcode
- depguard - depguard
- errcheck - errcheck
- gocritic
- gocyclo - gocyclo
- gofmt - gofmt
- goimports - goimports
@ -32,6 +33,17 @@ linters-settings:
# The io/ioutil package has been deprecated. # The io/ioutil package has been deprecated.
# https://go.dev/doc/go1.16#ioutil # https://go.dev/doc/go1.16#ioutil
- io/ioutil - io/ioutil
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- diagnostic
- opinionated
- style
disabled-checks:
- paramTypeCombine
- unnamedResult
- whyNoLint
gocyclo: gocyclo:
min-complexity: 16 min-complexity: 16
lll: lll:

View File

@ -34,15 +34,51 @@ make test
If you need to update a golden file simply do `go test ./... -test.update-golden`. If you need to update a golden file simply do `go test ./... -test.update-golden`.
### End to end tests ### End-to-end tests
To run e2e tests, the Compose CLI binary need to be build. All the commands to run e2e tests propose a version
with the prefix `build-and-e2e` to first build the CLI before executing tests.
To run the end to end tests, run:
Note that this requires a local Docker Engine to be running.
#### Whole end-to-end tests suite
To execute both CLI and standalone e2e tests, run :
```console
make e2e
```
Or if you need to build the CLI, run:
```console
make build-and-e2e
```
#### Plugin end-to-end tests suite
To execute CLI plugin e2e tests, run :
```console ```console
make e2e-compose make e2e-compose
``` ```
Note that this requires a local Docker Engine to be running. Or if you need to build the CLI, run:
```console
make build-and-e2e-compose
```
#### Standalone end-to-end tests suite
To execute the standalone CLI e2e tests, run :
```console
make e2e-compose-standalone
```
Or if you need to build the CLI, run:
```console
make build-and-e2e-compose-standalone
```
## Releases ## Releases

View File

@ -124,9 +124,10 @@ Fork the repository and make changes on your fork in a feature branch:
issue. issue.
Submit unit tests for your changes. Go has a great test framework built in; use Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. [Run the full test it! Take a look at existing tests for inspiration. Also end-to-end tests are
suite](README.md) on your branch before available. Run the full test suite, both unit tests and e2e tests on your
submitting a pull request. branch before submitting a pull request. See [BUILDING.md](BUILDING.md) for
instructions to build and run tests.
Write clean code. Universally formatted code promotes ease of writing, reading, Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before and maintenance. Always run `gofmt -s -w file.go` on each changed file before

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.2 # syntax=docker/dockerfile:1
# Copyright 2020 Docker Compose CLI authors # Copyright 2020 Docker Compose CLI authors
@ -15,10 +15,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG GO_VERSION=1.18.3-alpine ARG GO_VERSION=1.18.5-alpine
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine ARG GOLANGCI_LINT_VERSION=v1.47.3-alpine
ARG PROTOC_GEN_GO_VERSION=v1.4.3 ARG PROTOC_GEN_GO_VERSION=v1.4.3
FROM --platform=${BUILDPLATFORM} golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS local-golangci-lint
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS base FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION} AS base
WORKDIR /compose-cli WORKDIR /compose-cli
RUN apk add --no-cache -vv \ RUN apk add --no-cache -vv \
@ -34,7 +36,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
FROM base AS lint FROM base AS lint
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
COPY --from=golangci/golangci-lint /usr/bin/golangci-lint /usr/bin/golangci-lint COPY --from=local-golangci-lint /usr/bin/golangci-lint /usr/bin/golangci-lint
ARG BUILD_TAGS ARG BUILD_TAGS
ARG GIT_TAG ARG GIT_TAG
RUN --mount=target=. \ RUN --mount=target=. \
@ -88,7 +90,7 @@ RUN --mount=target=. \
make -f builder.Makefile test make -f builder.Makefile test
FROM base AS check-license-headers FROM base AS check-license-headers
RUN go install github.com/kunalkushwaha/ltag@latest RUN go install github.com/google/addlicense@latest
RUN --mount=target=. \ RUN --mount=target=. \
make -f builder.Makefile check-license-headers make -f builder.Makefile check-license-headers

View File

@ -51,6 +51,12 @@ e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2
docker-compose version docker-compose version
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
.PHONY: build-and-e2e-compose
build-and-e2e-compose: compose-plugin e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
.PHONY: build-and-e2e-compose-standalone
build-and-e2e-compose-standalone: compose-plugin e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
.PHONY: mocks .PHONY: mocks
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_cli.go -package mocks github.com/docker/cli/cli/command Cli
@ -60,6 +66,9 @@ mocks:
.PHONY: e2e .PHONY: e2e
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
.PHONY: build-and-e2e
build-and-e2e: compose-plugin e2e-compose e2e-compose-standalone ## Compile the compose cli-plugin and run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
.PHONY: cross .PHONY: cross
cross: ## Compile the CLI for linux, darwin and windows cross: ## Compile the CLI for linux, darwin and windows
@docker build . --target cross \ @docker build . --target cross \
@ -90,7 +99,7 @@ docs: ## generate documentation
$(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX)) $(eval $@_TMP_OUT := $(shell mktemp -d -t dockercli-output.XXXXXXXXXX))
docker build . \ docker build . \
--output type=local,dest=$($@_TMP_OUT) \ --output type=local,dest=$($@_TMP_OUT) \
-f ./docs/docs.Dockerfile \ -f ./docs/Dockerfile \
--target update --target update
rm -rf ./docs/internal rm -rf ./docs/internal
cp -R "$($@_TMP_OUT)"/out/* ./docs/ cp -R "$($@_TMP_OUT)"/out/* ./docs/
@ -99,7 +108,7 @@ docs: ## generate documentation
.PHONY: validate-docs .PHONY: validate-docs
validate-docs: ## validate the doc does not change validate-docs: ## validate the doc does not change
@docker build . \ @docker build . \
-f ./docs/docs.Dockerfile \ -f ./docs/Dockerfile \
--target validate --target validate
.PHONY: check-dependencies .PHONY: check-dependencies

View File

@ -77,7 +77,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "build [SERVICE...]", Use: "build [OPTIONS] [SERVICE...]",
Short: "Build or rebuild services", Short: "Build or rebuild services",
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.memory != "" { if opts.memory != "" {

View File

@ -136,6 +136,24 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
_ = f.MarkHidden("workdir") _ = f.MarkHidden("workdir")
} }
func (o *projectOptions) projectOrName() (*types.Project, string, error) {
name := o.ProjectName
var project *types.Project
if o.ProjectName == "" {
p, err := o.toProject(nil)
if err != nil {
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
if envProjectName != "" {
return nil, envProjectName, nil
}
return nil, "", err
}
project = p
name = p.Name
}
return project, name, nil
}
func (o *projectOptions) toProjectName() (string, error) { func (o *projectOptions) toProjectName() (string, error) {
if o.ProjectName != "" { if o.ProjectName != "" {
return o.ProjectName, nil return o.ProjectName, nil
@ -159,15 +177,15 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
return nil, compose.WrapComposeError(err) return nil, compose.WrapComposeError(err)
} }
if o.Compatibility || utils.StringToBool(options.Environment["COMPOSE_COMPATIBILITY"]) {
api.Separator = "_"
}
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(options)
if err != nil { if err != nil {
return nil, compose.WrapComposeError(err) return nil, compose.WrapComposeError(err)
} }
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
compose.Separator = "_"
}
ef := o.EnvFile ef := o.EnvFile
if ef != "" && !filepath.IsAbs(ef) { if ef != "" && !filepath.IsAbs(ef) {
ef, err = filepath.Abs(ef) ef, err = filepath.Abs(ef)
@ -239,11 +257,11 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
verbose bool verbose bool
version bool version bool
) )
command := &cobra.Command{ c := &cobra.Command{
Short: "Docker Compose", Short: "Docker Compose",
Use: PluginName, Use: PluginName,
TraverseChildren: true, TraverseChildren: true,
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) ! // By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return cmd.Help() return cmd.Help()
@ -300,7 +318,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
}, },
} }
command.AddCommand( c.AddCommand(
upCommand(&opts, backend), upCommand(&opts, backend),
downCommand(&opts, backend), downCommand(&opts, backend),
startCommand(&opts, backend), startCommand(&opts, backend),
@ -327,16 +345,16 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
createCommand(&opts, backend), createCommand(&opts, backend),
copyCommand(&opts, backend), copyCommand(&opts, backend),
) )
command.Flags().SetInterspersed(false) c.Flags().SetInterspersed(false)
opts.addProjectFlags(command.Flags()) opts.addProjectFlags(c.Flags())
command.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`) c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
command.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information") c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
command.Flags().MarkHidden("version") //nolint:errcheck c.Flags().MarkHidden("version") //nolint:errcheck
command.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`) c.Flags().BoolVar(&noAnsi, "no-ansi", false, `Do not print ANSI control characters (DEPRECATED)`)
command.Flags().MarkHidden("no-ansi") //nolint:errcheck c.Flags().MarkHidden("no-ansi") //nolint:errcheck
command.Flags().BoolVar(&verbose, "verbose", false, "Show more output") c.Flags().BoolVar(&verbose, "verbose", false, "Show more output")
command.Flags().MarkHidden("verbose") //nolint:errcheck c.Flags().MarkHidden("verbose") //nolint:errcheck
return command return c
} }
func setEnvWithDotEnv(prjOpts *projectOptions) error { func setEnvWithDotEnv(prjOpts *projectOptions) error {
@ -354,11 +372,9 @@ func setEnvWithDotEnv(prjOpts *projectOptions) error {
return err return err
} }
for k, v := range envFromFile { for k, v := range envFromFile {
if _, ok := os.LookupEnv(k); !ok { if err := os.Setenv(k, v); err != nil { // overwrite the process env with merged OS + env file results
if err = os.Setenv(k, v); err != nil {
return err return err
} }
} }
}
return nil return nil
} }

View File

@ -58,7 +58,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Aliases: []string{"config"}, Aliases: []string{"config"},
Use: "convert SERVICES", Use: "convert [OPTIONS] [SERVICE...]",
Short: "Converts the compose file to platform's canonical format", Short: "Converts the compose file to platform's canonical format",
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.quiet { if opts.quiet {

View File

@ -30,6 +30,8 @@ import (
type createOptions struct { type createOptions struct {
Build bool Build bool
noBuild bool noBuild bool
Pull string
pullChanged bool
removeOrphans bool removeOrphans bool
ignoreOrphans bool ignoreOrphans bool
forceRecreate bool forceRecreate bool
@ -44,9 +46,10 @@ type createOptions struct {
func createCommand(p *projectOptions, backend api.Service) *cobra.Command { func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
opts := createOptions{} opts := createOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create [SERVICE...]", Use: "create [OPTIONS] [SERVICE...]",
Short: "Creates containers for a service.", Short: "Creates containers for a service.",
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.pullChanged = cmd.Flags().Changed("pull")
if opts.Build && opts.noBuild { if opts.Build && opts.noBuild {
return fmt.Errorf("--build and --no-build are incompatible") return fmt.Errorf("--build and --no-build are incompatible")
} }
@ -56,6 +59,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
return nil return nil
}), }),
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error { RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
opts.Apply(project)
return backend.Create(ctx, project, api.CreateOptions{ return backend.Create(ctx, project, api.CreateOptions{
RemoveOrphans: opts.removeOrphans, RemoveOrphans: opts.removeOrphans,
IgnoreOrphans: opts.ignoreOrphans, IgnoreOrphans: opts.ignoreOrphans,
@ -71,6 +75,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's missing.") flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
flags.StringVar(&opts.Pull, "pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.") flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
return cmd return cmd
@ -105,6 +110,12 @@ func (opts createOptions) GetTimeout() *time.Duration {
} }
func (opts createOptions) Apply(project *types.Project) { func (opts createOptions) Apply(project *types.Project) {
if opts.pullChanged {
for i, service := range project.Services {
service.PullPolicy = opts.Pull
project.Services[i] = service
}
}
if opts.Build { if opts.Build {
for i, service := range project.Services { for i, service := range project.Services {
if service.Build == nil { if service.Build == nil {
@ -117,6 +128,9 @@ func (opts createOptions) Apply(project *types.Project) {
if opts.noBuild { if opts.noBuild {
for i, service := range project.Services { for i, service := range project.Services {
service.Build = nil service.Build = nil
if service.Image == "" {
service.Image = api.GetImageNameOrDefault(service, project.Name)
}
project.Services[i] = service project.Services[i] = service
} }
} }

View File

@ -22,7 +22,6 @@ import (
"os" "os"
"time" "time"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/utils" "github.com/docker/compose/v2/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -45,7 +44,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
downCmd := &cobra.Command{ downCmd := &cobra.Command{
Use: "down", Use: "down [OPTIONS]",
Short: "Stop and remove containers, networks", Short: "Stop and remove containers, networks",
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.timeChanged = cmd.Flags().Changed("timeout") opts.timeChanged = cmd.Flags().Changed("timeout")
@ -69,8 +68,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.") flags.BoolVarP(&opts.volumes, "volumes", "v", false, "Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`) flags.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 { flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name { if name == "volume" {
case "volume":
name = "volumes" name = "volumes"
logrus.Warn("--volume is deprecated, please use --volumes") logrus.Warn("--volume is deprecated, please use --volumes")
} }
@ -80,16 +78,10 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runDown(ctx context.Context, backend api.Service, opts downOptions) error { func runDown(ctx context.Context, backend api.Service, opts downOptions) error {
name := opts.ProjectName project, name, err := opts.projectOrName()
var project *types.Project
if opts.ProjectName == "" {
p, err := opts.toProject(nil)
if err != nil { if err != nil {
return err return err
} }
project = p
name = p.Name
}
var timeout *time.Duration var timeout *time.Duration
if opts.timeChanged { if opts.timeChanged {

View File

@ -38,7 +38,7 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
}, },
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "events [options] [--] [SERVICE...]", Use: "events [OPTIONS] [SERVICE...]",
Short: "Receive real time events from containers.", Short: "Receive real time events from containers.",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, backend, opts, args) return runEvents(ctx, backend, opts, args)
@ -51,12 +51,12 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error { func runEvents(ctx context.Context, backend api.Service, opts eventsOpts, services []string) error {
project, err := opts.toProjectName() name, err := opts.toProjectName()
if err != nil { if err != nil {
return err return err
} }
return backend.Events(ctx, project, api.EventsOptions{ return backend.Events(ctx, name, api.EventsOptions{
Services: services, Services: services,
Consumer: func(event api.Event) error { Consumer: func(event api.Event) error {
if opts.json { if opts.json {

View File

@ -50,7 +50,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
}, },
} }
runCmd := &cobra.Command{ runCmd := &cobra.Command{
Use: "exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...]", Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
Short: "Execute a command in a running container.", Short: "Execute a command in a running container.",
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2),
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {

View File

@ -43,7 +43,7 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
imgCmd := &cobra.Command{ imgCmd := &cobra.Command{
Use: "images [SERVICE...]", Use: "images [OPTIONS] [SERVICE...]",
Short: "List images used by the created containers", Short: "List images used by the created containers",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runImages(ctx, backend, opts, args) return runImages(ctx, backend, opts, args)

View File

@ -34,7 +34,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "kill [options] [SERVICE...]", Use: "kill [OPTIONS] [SERVICE...]",
Short: "Force stop service containers.", Short: "Force stop service containers.",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, backend, opts, args) return runKill(ctx, backend, opts, args)
@ -49,12 +49,12 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error { func runKill(ctx context.Context, backend api.Service, opts killOptions, services []string) error {
projectName, err := opts.toProjectName() name, err := opts.toProjectName()
if err != nil { if err != nil {
return err return err
} }
return backend.Kill(ctx, projectName, api.KillOptions{ return backend.Kill(ctx, name, api.KillOptions{
Services: services, Services: services,
Signal: opts.signal, Signal: opts.signal,
}) })

View File

@ -39,19 +39,20 @@ type lsOptions struct {
} }
func listCommand(backend api.Service) *cobra.Command { func listCommand(backend api.Service) *cobra.Command {
opts := lsOptions{Filter: opts.NewFilterOpt()} lsOpts := lsOptions{Filter: opts.NewFilterOpt()}
lsCmd := &cobra.Command{ lsCmd := &cobra.Command{
Use: "ls", Use: "ls [OPTIONS]",
Short: "List running compose projects", Short: "List running compose projects",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runList(ctx, backend, opts) return runList(ctx, backend, lsOpts)
}), }),
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion(), ValidArgsFunction: noCompletion(),
} }
lsCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].") lsCmd.Flags().StringVar(&lsOpts.Format, "format", "pretty", "Format the output. Values: [pretty | json].")
lsCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs.") lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.")
lsCmd.Flags().Var(&opts.Filter, "filter", "Filter output based on conditions provided.") lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.")
lsCmd.Flags().BoolVarP(&opts.All, "all", "a", false, "Show all stopped Compose projects") lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects")
return lsCmd return lsCmd
} }
@ -60,18 +61,18 @@ var acceptedListFilters = map[string]bool{
"name": true, "name": true,
} }
func runList(ctx context.Context, backend api.Service, opts lsOptions) error { func runList(ctx context.Context, backend api.Service, lsOpts lsOptions) error {
filters := opts.Filter.Value() filters := lsOpts.Filter.Value()
err := filters.Validate(acceptedListFilters) err := filters.Validate(acceptedListFilters)
if err != nil { if err != nil {
return err return err
} }
stackList, err := backend.List(ctx, api.ListOptions{All: opts.All}) stackList, err := backend.List(ctx, api.ListOptions{All: lsOpts.All})
if err != nil { if err != nil {
return err return err
} }
if opts.Quiet { if lsOpts.Quiet {
for _, s := range stackList { for _, s := range stackList {
fmt.Println(s.Name) fmt.Println(s.Name)
} }
@ -90,7 +91,7 @@ func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
} }
view := viewFromStackList(stackList) view := viewFromStackList(stackList)
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) { return formatter.Print(view, lsOpts.Format, os.Stdout, func(w io.Writer) {
for _, stack := range view { 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\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
} }

View File

@ -44,7 +44,7 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
logsCmd := &cobra.Command{ logsCmd := &cobra.Command{
Use: "logs [SERVICE...]", Use: "logs [OPTIONS] [SERVICE...]",
Short: "View output from containers", Short: "View output from containers",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runLogs(ctx, backend, opts, args) return runLogs(ctx, backend, opts, args)

View File

@ -44,13 +44,14 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error { func runPause(ctx context.Context, backend api.Service, opts pauseOptions, services []string) error {
project, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
return backend.Pause(ctx, project, api.PauseOptions{ return backend.Pause(ctx, name, api.PauseOptions{
Services: services, Services: services,
Project: project,
}) })
} }
@ -74,12 +75,13 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error { func runUnPause(ctx context.Context, backend api.Service, opts unpauseOptions, services []string) error {
project, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
return backend.UnPause(ctx, project, api.PauseOptions{ return backend.UnPause(ctx, name, api.PauseOptions{
Services: services, Services: services,
Project: project,
}) })
} }

View File

@ -38,7 +38,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "port [options] [--] SERVICE PRIVATE_PORT", Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
Short: "Print the public port for a port binding.", Short: "Print the public port for a port binding.",
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2),
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {

View File

@ -70,7 +70,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
psCmd := &cobra.Command{ psCmd := &cobra.Command{
Use: "ps [SERVICE...]", Use: "ps [OPTIONS] [SERVICE...]",
Short: "List containers", Short: "List containers",
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.parseFilter() return opts.parseFilter()
@ -91,11 +91,12 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error { func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error {
projectName, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
containers, err := backend.Ps(ctx, projectName, api.PsOptions{ containers, err := backend.Ps(ctx, name, api.PsOptions{
Project: project,
All: opts.All, All: opts.All,
Services: services, Services: services,
}) })

View File

@ -43,7 +43,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "pull [SERVICE...]", Use: "pull [OPTIONS] [SERVICE...]",
Short: "Pull service images", Short: "Pull service images",
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.noParallel { if opts.noParallel {

View File

@ -36,7 +36,7 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
pushCmd := &cobra.Command{ pushCmd := &cobra.Command{
Use: "push [SERVICE...]", Use: "push [OPTIONS] [SERVICE...]",
Short: "Push service images", Short: "Push service images",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runPush(ctx, backend, opts, args) return runPush(ctx, backend, opts, args)

View File

@ -35,7 +35,7 @@ func removeCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rm [SERVICE...]", Use: "rm [OPTIONS] [SERVICE...]",
Short: "Removes stopped service containers", Short: "Removes stopped service containers",
Long: `Removes stopped service containers Long: `Removes stopped service containers
@ -59,23 +59,25 @@ Any data which is not in a volume will be lost.`,
} }
func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error { func runRemove(ctx context.Context, backend api.Service, opts removeOptions, services []string) error {
project, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
if opts.stop { if opts.stop {
err := backend.Stop(ctx, project, api.StopOptions{ err := backend.Stop(ctx, name, api.StopOptions{
Services: services, Services: services,
Project: project,
}) })
if err != nil { if err != nil {
return err return err
} }
} }
return backend.Remove(ctx, project, api.RemoveOptions{ return backend.Remove(ctx, name, api.RemoveOptions{
Services: services, Services: services,
Force: opts.force, Force: opts.force,
Volumes: opts.volumes, Volumes: opts.volumes,
Project: project,
}) })
} }

View File

@ -35,8 +35,8 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
restartCmd := &cobra.Command{ restartCmd := &cobra.Command{
Use: "restart", Use: "restart [OPTIONS] [SERVICE...]",
Short: "Restart containers", Short: "Restart service containers",
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runRestart(ctx, backend, opts, args) return runRestart(ctx, backend, opts, args)
}), }),
@ -49,14 +49,15 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error { func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
projectName, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
timeout := time.Duration(opts.timeout) * time.Second timeout := time.Duration(opts.timeout) * time.Second
return backend.Restart(ctx, projectName, api.RestartOptions{ return backend.Restart(ctx, name, api.RestartOptions{
Timeout: &timeout, Timeout: &timeout,
Services: services, Services: services,
Project: project,
}) })
} }

View File

@ -114,7 +114,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
}, },
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]", Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
Short: "Run a one-off command on a service.", Short: "Run a one-off command on a service.",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {

View File

@ -43,12 +43,13 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
} }
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error { func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
projectName, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
return backend.Start(ctx, projectName, api.StartOptions{ return backend.Start(ctx, name, api.StartOptions{
AttachTo: services, AttachTo: services,
Project: project,
}) })
} }

View File

@ -36,7 +36,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
projectOptions: p, projectOptions: p,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "stop [SERVICE...]", Use: "stop [OPTIONS] [SERVICE...]",
Short: "Stop services", Short: "Stop services",
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
opts.timeChanged = cmd.Flags().Changed("timeout") opts.timeChanged = cmd.Flags().Changed("timeout")
@ -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 { func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
projectName, err := opts.toProjectName() project, name, err := opts.projectOrName()
if err != nil { if err != nil {
return err return err
} }
@ -63,8 +63,9 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
timeoutValue := time.Duration(opts.timeout) * time.Second timeoutValue := time.Duration(opts.timeout) * time.Second
timeout = &timeoutValue timeout = &timeoutValue
} }
return backend.Stop(ctx, projectName, api.StopOptions{ return backend.Stop(ctx, name, api.StopOptions{
Timeout: timeout, Timeout: timeout,
Services: services, Services: services,
Project: project,
}) })
} }

View File

@ -96,7 +96,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
up := upOptions{} up := upOptions{}
create := createOptions{} create := createOptions{}
upCmd := &cobra.Command{ upCmd := &cobra.Command{
Use: "up [SERVICE...]", Use: "up [OPTIONS] [SERVICE...]",
Short: "Create and start containers", Short: "Create and start containers",
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
create.timeChanged = cmd.Flags().Changed("timeout") create.timeChanged = cmd.Flags().Changed("timeout")
@ -115,6 +115,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background") flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.") flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.") flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
flags.StringVar(&create.Pull, "pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.") flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
@ -219,7 +220,10 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
func setServiceScale(project *types.Project, name string, replicas uint64) error { func setServiceScale(project *types.Project, name string, replicas uint64) error {
for i, s := range project.Services { for i, s := range project.Services {
if s.Name == name { if s.Name != name {
continue
}
service, err := project.GetService(name) service, err := project.GetService(name)
if err != nil { if err != nil {
return err return err
@ -231,6 +235,5 @@ func setServiceScale(project *types.Project, name string, replicas uint64) error
project.Services[i] = service project.Services[i] = service
return nil return nil
} }
}
return fmt.Errorf("unknown service %q", name) return fmt.Errorf("unknown service %q", name)
} }

View File

@ -35,13 +35,18 @@ type versionOptions struct {
func versionCommand() *cobra.Command { func versionCommand() *cobra.Command {
opts := versionOptions{} opts := versionOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "version", Use: "version [OPTIONS]",
Short: "Show the Docker Compose version information", Short: "Show the Docker Compose version information",
Args: cobra.MaximumNArgs(0), Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
runVersion(opts) runVersion(opts)
return nil return nil
}, },
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// overwrite parent PersistentPreRunE to avoid trying to load
// compose file on version command if COMPOSE_FILE is set
return nil
},
} }
// define flags for backward compatibility with com.docker.cli // define flags for backward compatibility with com.docker.cli
flags := cmd.Flags() flags := cmd.Flags()

View File

@ -27,6 +27,16 @@ import (
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
) )
// LogConsumer consume logs from services and format them
type logConsumer struct {
ctx context.Context
presenters sync.Map // map[string]*presenter
width int
writer io.Writer
color bool
prefix bool
}
// NewLogConsumer creates a new LogConsumer // NewLogConsumer creates a new LogConsumer
func NewLogConsumer(ctx context.Context, w io.Writer, color bool, prefix bool) api.LogConsumer { func NewLogConsumer(ctx context.Context, w io.Writer, color bool, prefix bool) api.LogConsumer {
return &logConsumer{ return &logConsumer{
@ -101,16 +111,6 @@ func (l *logConsumer) computeWidth() {
l.width = width + 1 l.width = width + 1
} }
// LogConsumer consume logs from services and format them
type logConsumer struct {
ctx context.Context
presenters sync.Map // map[string]*presenter
width int
writer io.Writer
color bool
prefix bool
}
type presenter struct { type presenter struct {
colors colorFunc colors colorFunc
name string name string

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.3-labs # syntax=docker/dockerfile:1
# Copyright 2020 Docker Compose CLI authors # Copyright 2020 Docker Compose CLI authors
@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG GO_VERSION=1.18.3 ARG GO_VERSION=1.18.5
ARG FORMATS=md,yaml ARG FORMATS=md,yaml
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen

View File

@ -23,7 +23,7 @@ Docker Compose
| [`ps`](compose_ps.md) | List containers | | [`ps`](compose_ps.md) | List containers |
| [`pull`](compose_pull.md) | Pull service images | | [`pull`](compose_pull.md) | Pull service images |
| [`push`](compose_push.md) | Push service images | | [`push`](compose_push.md) | Push service images |
| [`restart`](compose_restart.md) | Restart containers | | [`restart`](compose_restart.md) | Restart service containers |
| [`rm`](compose_rm.md) | Removes stopped service containers | | [`rm`](compose_rm.md) | Removes stopped service containers |
| [`run`](compose_run.md) | Run a one-off command on a service. | | [`run`](compose_run.md) | Run a one-off command on a service. |
| [`start`](compose_start.md) | Start services | | [`start`](compose_start.md) | Start services |

View File

@ -5,7 +5,7 @@ Converts the compose file to platform's canonical format
### Aliases ### Aliases
`convert`, `config` `docker compose convert`, `docker compose config`
### Options ### Options

View File

@ -11,6 +11,7 @@ Creates containers for a service.
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. | | `--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-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. | | `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--pull` | `string` | `missing` | Pull image before running ("always"\|"missing"\|"never") |
<!---MARKER_GEN_END--> <!---MARKER_GEN_END-->

View File

@ -61,4 +61,4 @@ $ docker compose pull db
⠹ f63c47038e66 Waiting 9.3s ⠹ f63c47038e66 Waiting 9.3s
⠹ 77a0c198cde5 Waiting 9.3s ⠹ 77a0c198cde5 Waiting 9.3s
⠹ c8752d5b785c Waiting 9.3s ⠹ c8752d5b785c Waiting 9.3s
``̀` ```

View File

@ -1,7 +1,7 @@
# docker compose restart # docker compose restart
<!---MARKER_GEN_START--> <!---MARKER_GEN_START-->
Restart containers Restart service containers
### Options ### Options
@ -14,7 +14,7 @@ Restart containers
## Description ## Description
Restarts all stopped and running services. Restarts all stopped and running services, or the specified services only.
If you make changes to your `compose.yml` configuration, these changes are not reflected If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added after running this command. For example, changes to environment variables (which are added

View File

@ -21,6 +21,7 @@ Create and start containers
| `--no-log-prefix` | | | Don't print prefix in logs. | | `--no-log-prefix` | | | Don't print prefix in logs. |
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | | `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--no-start` | | | Don't start the services after creating them. | | `--no-start` | | | Don't start the services after creating them. |
| `--pull` | `string` | `missing` | Pull image before running ("always"\|"missing"\|"never") |
| `--quiet-pull` | | | Pull without printing progress information. | | `--quiet-pull` | | | Pull without printing progress information. |
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | | `--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. | | `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. |

View File

@ -10,7 +10,7 @@ long: |-
If you change a service's `Dockerfile` or the contents of its build directory, If you change a service's `Dockerfile` or the contents of its build directory,
run `docker compose build` to rebuild it. run `docker compose build` to rebuild it.
usage: docker compose build [SERVICE...] usage: docker compose build [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,5 +1,5 @@
command: docker compose convert command: docker compose convert
aliases: config aliases: docker compose convert, docker compose config
short: Converts the compose file to platform's canonical format short: Converts the compose file to platform's canonical format
long: |- long: |-
`docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine, `docker compose convert` render the actual data model to be applied on target platform. When used with Docker engine,
@ -7,7 +7,7 @@ long: |-
fully defined Compose model. fully defined Compose model.
To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config` To allow smooth migration from docker-compose, this subcommand declares alias `docker compose config`
usage: docker compose convert SERVICES usage: docker compose convert [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,8 +1,9 @@
command: docker compose cp command: docker compose cp
short: Copy files/folders between a service container and the local filesystem short: Copy files/folders between a service container and the local filesystem
long: Copy files/folders between a service container and the local filesystem long: Copy files/folders between a service container and the local filesystem
usage: "docker compose cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-\n\tdocker compose usage: |-
cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH" docker compose cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-
docker compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose create command: docker compose create
short: Creates containers for a service. short: Creates containers for a service.
long: Creates containers for a service. long: Creates containers for a service.
usage: docker compose create [SERVICE...] usage: docker compose create [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:
@ -47,6 +47,16 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: pull
value_type: string
default_value: missing
description: Pull image before running ("always"|"missing"|"never")
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false deprecated: false
experimental: false experimental: false
experimentalcli: false experimentalcli: false

View File

@ -14,7 +14,7 @@ long: |-
Anonymous volumes are not removed by default. However, as they dont have a stable name, they will not be automatically Anonymous volumes are not removed by default. However, as they dont have a stable name, they will not be automatically
mounted by a subsequent `up`. For data that needs to persist between updates, use explicit paths as bind mounts or mounted by a subsequent `up`. For data that needs to persist between updates, use explicit paths as bind mounts or
named volumes. named volumes.
usage: docker compose down usage: docker compose down [OPTIONS]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -20,7 +20,7 @@ long: |-
``` ```
The events that can be received using this can be seen [here](/engine/reference/commandline/events/#object-types). The events that can be received using this can be seen [here](/engine/reference/commandline/events/#object-types).
usage: docker compose events [options] [--] [SERVICE...] usage: docker compose events [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -5,7 +5,7 @@ long: |-
With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so
you can use a command such as `docker compose exec web sh` to get an interactive prompt. you can use a command such as `docker compose exec web sh` to get an interactive prompt.
usage: docker compose exec [options] [-e KEY=VAL...] [--] SERVICE COMMAND [ARGS...] usage: docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose images command: docker compose images
short: List images used by the created containers short: List images used by the created containers
long: List images used by the created containers long: List images used by the created containers
usage: docker compose images [SERVICE...] usage: docker compose images [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -6,7 +6,7 @@ long: |-
```console ```console
$ docker-compose kill -s SIGINT $ docker-compose kill -s SIGINT
``` ```
usage: docker compose kill [options] [SERVICE...] usage: docker compose kill [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose logs command: docker compose logs
short: View output from containers short: View output from containers
long: Displays log output from services. long: Displays log output from services.
usage: docker compose logs [SERVICE...] usage: docker compose logs [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose ls command: docker compose ls
short: List running compose projects short: List running compose projects
long: List Compose projects running on platform. long: List Compose projects running on platform.
usage: docker compose ls usage: docker compose ls [OPTIONS]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose port command: docker compose port
short: Print the public port for a port binding. short: Print the public port for a port binding.
long: Prints the public port for a port binding. long: Prints the public port for a port binding.
usage: docker compose port [options] [--] SERVICE PRIVATE_PORT usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -10,7 +10,7 @@ long: |-
example-bar-1 "/docker-entrypoint.…" bar exited (0) example-bar-1 "/docker-entrypoint.…" bar exited (0)
example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp example-foo-1 "/docker-entrypoint.…" foo running 0.0.0.0:8080->80/tcp
``` ```
usage: docker compose ps [SERVICE...] usage: docker compose ps [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -3,7 +3,7 @@ short: Pull service images
long: |- long: |-
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
those images. those images.
usage: docker compose pull [SERVICE...] usage: docker compose pull [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:
@ -98,7 +98,7 @@ examples: |-
⠹ f63c47038e66 Waiting 9.3s ⠹ f63c47038e66 Waiting 9.3s
⠹ 77a0c198cde5 Waiting 9.3s ⠹ 77a0c198cde5 Waiting 9.3s
⠹ c8752d5b785c Waiting 9.3s ⠹ c8752d5b785c Waiting 9.3s
``̀` ```
deprecated: false deprecated: false
experimental: false experimental: false
experimentalcli: false experimentalcli: false

View File

@ -19,7 +19,7 @@ long: |-
build: . build: .
image: your-dockerid/yourimage ## goes to your repository on Docker Hub image: your-dockerid/yourimage ## goes to your repository on Docker Hub
``` ```
usage: docker compose push [SERVICE...] usage: docker compose push [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -1,7 +1,7 @@
command: docker compose restart command: docker compose restart
short: Restart containers short: Restart service containers
long: |- long: |-
Restarts all stopped and running services. Restarts all stopped and running services, or the specified services only.
If you make changes to your `compose.yml` configuration, these changes are not reflected If you make changes to your `compose.yml` configuration, these changes are not reflected
after running this command. For example, changes to environment variables (which are added after running this command. For example, changes to environment variables (which are added
@ -11,7 +11,7 @@ long: |-
If you are looking to configure a service's restart policy, please refer to If you are looking to configure a service's restart policy, please refer to
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart) [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). or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
usage: docker compose restart usage: docker compose restart [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -16,7 +16,7 @@ long: |-
Are you sure? [yN] y Are you sure? [yN] y
Removing djangoquickstart_web_run_1 ... done Removing djangoquickstart_web_run_1 ... done
``` ```
usage: docker compose rm [SERVICE...] usage: docker compose rm [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -54,8 +54,7 @@ long: |-
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
specified in the service configuration. specified in the service configuration.
usage: docker compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l usage: docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
KEY=VALUE...] SERVICE [COMMAND] [ARGS...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -2,7 +2,7 @@ command: docker compose stop
short: Stop services short: Stop services
long: | long: |
Stops running containers without removing them. They can be started again with `docker compose start`. Stops running containers without removing them. They can be started again with `docker compose start`.
usage: docker compose stop [SERVICE...] usage: docker compose stop [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

View File

@ -17,7 +17,7 @@ long: |-
If the process encounters an error, the exit code for this command is `1`. If the process encounters an error, the exit code for this command is `1`.
If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the containers are stopped, and the exit code is `0`. If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the containers are stopped, and the exit code is `0`.
usage: docker compose up [SERVICE...] usage: docker compose up [OPTIONS] [SERVICE...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:
@ -165,6 +165,16 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: pull
value_type: string
default_value: missing
description: Pull image before running ("always"|"missing"|"never")
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet-pull - option: quiet-pull
value_type: bool value_type: bool
default_value: "false" default_value: "false"

View File

@ -1,7 +1,7 @@
command: docker compose version command: docker compose version
short: Show the Docker Compose version information short: Show the Docker Compose version information
long: Show the Docker Compose version information long: Show the Docker Compose version information
usage: docker compose version usage: docker compose version [OPTIONS]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
options: options:

25
go.mod
View File

@ -5,14 +5,14 @@ go 1.18
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.5 github.com/AlecAivazis/survey/v2 v2.3.5
github.com/buger/goterm v1.0.4 github.com/buger/goterm v1.0.4
github.com/cnabio/cnab-to-oci v0.3.4 github.com/cnabio/cnab-to-oci v0.3.6
github.com/compose-spec/compose-go v1.2.8 github.com/compose-spec/compose-go v1.4.0
github.com/containerd/console v1.0.3 github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.6 github.com/containerd/containerd v1.6.7
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f
github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
github.com/docker/cli v20.10.17+incompatible github.com/docker/cli v20.10.17+incompatible
github.com/docker/cli-docs-tool v0.4.0 github.com/docker/cli-docs-tool v0.5.0
github.com/docker/docker v20.10.17+incompatible github.com/docker/docker v20.10.17+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 github.com/docker/go-units v0.4.0
@ -21,19 +21,19 @@ require (
github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/go-version v1.6.0
github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d github.com/moby/buildkit v0.10.3
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/morikuni/aec v1.0.0 github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b github.com/sirupsen/logrus v1.9.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/theupdateframework/notary v0.7.0 github.com/theupdateframework/notary v0.7.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
gopkg.in/yaml.v2 v2.4.0
gotest.tools v2.2.0+incompatible gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.3.0 gotest.tools/v3 v3.3.0
) )
@ -45,7 +45,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cnabio/cnab-go v0.23.4 // indirect github.com/cnabio/cnab-go v0.23.4 // indirect
github.com/containerd/continuity v0.2.2 // indirect github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8 // indirect
github.com/containerd/ttrpc v1.1.0 // indirect github.com/containerd/ttrpc v1.1.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect github.com/containerd/typeurl v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -61,7 +61,7 @@ require (
github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
@ -111,7 +111,7 @@ require (
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
@ -120,7 +120,6 @@ require (
google.golang.org/grpc v1.45.0 // indirect google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used k8s.io/apimachinery v0.24.1 // indirect; see replace for the actual version used
k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used k8s.io/client-go v0.24.1 // indirect; see replace for the actual version used

44
go.sum
View File

@ -141,7 +141,7 @@ github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwT
github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc=
github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I=
github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
@ -270,8 +270,8 @@ github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e/go.mod h1:yMWuSON
github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw= github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw=
github.com/cnabio/cnab-go v0.23.4 h1:jplQcSnvFyQlD6swiqL3BmqRnhbnS+lc/EKdBLH9E80= github.com/cnabio/cnab-go v0.23.4 h1:jplQcSnvFyQlD6swiqL3BmqRnhbnS+lc/EKdBLH9E80=
github.com/cnabio/cnab-go v0.23.4/go.mod h1:9EmgHR51LFqQStzaC+xHPJlkD4OPsF6Ev5Y8e/YHEns= github.com/cnabio/cnab-go v0.23.4/go.mod h1:9EmgHR51LFqQStzaC+xHPJlkD4OPsF6Ev5Y8e/YHEns=
github.com/cnabio/cnab-to-oci v0.3.4 h1:u1AUUplhKojCMgee17QkjU27yM1tfH9fOTFvxkrEqVw= github.com/cnabio/cnab-to-oci v0.3.6 h1:QVvy4WjQpGyf20xbbeYtRObX+pB8cWNuvvT/e4w1DoQ=
github.com/cnabio/cnab-to-oci v0.3.4/go.mod h1:7f86Z39HUg67wg8dZvxvFpW2pGDjK3RwbJAMJGxTXHQ= github.com/cnabio/cnab-to-oci v0.3.6/go.mod h1:AvVNl0Hh3VBk1zqeLdyE5S3bTQ5EsZPPF4mUUJYyy1Y=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@ -286,8 +286,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8= github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
github.com/compose-spec/compose-go v1.2.8 h1:ImPy82xn+rJKL5xmgEyesZEfqJmrzJ1WuZSHEhxMEFI= github.com/compose-spec/compose-go v1.4.0 h1:zaYVAZ6lIByr7Jffi20AabfeUwcTrdXfH3X1R5HEm+g=
github.com/compose-spec/compose-go v1.2.8/go.mod h1:813WrDd7NtOl9ZVqswlJ5iCQy3lxI3KYxKkY8EeHQ7w= github.com/compose-spec/compose-go v1.4.0/go.mod h1:l7RUULbFFLzlQHuxtJr7SVLyWdqEpbJEGTWCgcu6Eqw=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@ -330,8 +330,8 @@ github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE=
github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.7 h1:IVikHEHEMZ5SXpUa80tNGNIV7fBigjp+sOcrlzAkPCc=
github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/containerd v1.6.7/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@ -339,8 +339,9 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA=
github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk=
github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8 h1:yGFEcFNMhze29DxAAB33v/1OMRYF/cM9iwwgV2P0ZrE=
github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk=
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
@ -447,15 +448,17 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU=
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M=
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f h1:3NCYdjXycNd/Xn/iICZzmxkiDX1e1cjTHjbMAz+wRVk=
github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/buildx v0.8.2 h1:dsd3F0hhmUydFX/KFrvbK81JvlTA4T3Iy0lwDJt4PsU= github.com/docker/buildx v0.8.2 h1:dsd3F0hhmUydFX/KFrvbK81JvlTA4T3Iy0lwDJt4PsU=
github.com/docker/buildx v0.8.2/go.mod h1:5sMOfNwOmO2jy/MxBL4ySk2LoLIG1tQFu2EU8wbKa34= github.com/docker/buildx v0.8.2/go.mod h1:5sMOfNwOmO2jy/MxBL4ySk2LoLIG1tQFu2EU8wbKa34=
github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible h1:RWXvuBczWuSIMjI69AnkNklNNVX2gmS0X+15AttGDVk= github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible h1:RWXvuBczWuSIMjI69AnkNklNNVX2gmS0X+15AttGDVk=
github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.4.0 h1:MdfKoErGEbFqIxQ8an9BsZ+YzKUGd58RBVkV+Q82GPo=
github.com/docker/cli-docs-tool v0.4.0/go.mod h1:rgW5KKdNpLMBIuH4WQ/1RNh38nH+/Ay5jgL4P0ZMPpY= github.com/docker/cli-docs-tool v0.4.0/go.mod h1:rgW5KKdNpLMBIuH4WQ/1RNh38nH+/Ay5jgL4P0ZMPpY=
github.com/docker/cli-docs-tool v0.5.0 h1:EjGwI6EyB7YemHCC7R8mwXszJTbuq0T0pFuDC5bMhcE=
github.com/docker/cli-docs-tool v0.5.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
@ -606,8 +609,8 @@ github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQq
github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
@ -704,8 +707,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE=
github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
@ -1011,14 +1015,14 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ=
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d h1:6pLVBJO3V/lMegbVD5kh2QrpZwqS4ZrxEm/MyifCPaY=
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d/go.mod h1:WvwAZv8aRScHkqc/+X46cRC2CKMKpqcaX+pRvUTtPes= github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d/go.mod h1:WvwAZv8aRScHkqc/+X46cRC2CKMKpqcaX+pRvUTtPes=
github.com/moby/buildkit v0.10.3 h1:/dGykD8FW+H4p++q5+KqKEo6gAkYKyBQHdawdjVwVAU=
github.com/moby/buildkit v0.10.3/go.mod h1:jxeOuly98l9gWHai0Ojrbnczrk/rf+o9/JqNhY+UCSo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74=
github.com/moby/sys/mount v0.3.0 h1:bXZYMmq7DBQPwHRxH/MG+u9+XF90ZOwoXpHTOznMGp0=
github.com/moby/sys/mount v0.3.0/go.mod h1:U2Z3ur2rXPFrFmy4q6WMwWrBOAQGYtYTRVM8BIvzbwk= github.com/moby/sys/mount v0.3.0/go.mod h1:U2Z3ur2rXPFrFmy4q6WMwWrBOAQGYtYTRVM8BIvzbwk=
github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
@ -1218,8 +1222,6 @@ github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUcc
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
@ -1245,8 +1247,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
@ -1673,8 +1676,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1790,8 +1794,9 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1909,7 +1914,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=

View File

@ -117,7 +117,7 @@ type CreateOptions struct {
// StartOptions group options of the Start API // StartOptions group options of the Start API
type StartOptions struct { type StartOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name // Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project Project *types.Project
// Attach to container and forward logs if not nil // Attach to container and forward logs if not nil
Attach LogConsumer Attach LogConsumer
@ -133,6 +133,8 @@ type StartOptions struct {
// RestartOptions group options of the Restart API // RestartOptions group options of the Restart API
type RestartOptions struct { type RestartOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
// Timeout override container restart timeout // Timeout override container restart timeout
Timeout *time.Duration Timeout *time.Duration
// Services passed in the command line to be restarted // Services passed in the command line to be restarted
@ -141,6 +143,8 @@ type RestartOptions struct {
// StopOptions group options of the Stop API // StopOptions group options of the Stop API
type StopOptions struct { type StopOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
// Timeout override container stop timeout // Timeout override container stop timeout
Timeout *time.Duration Timeout *time.Duration
// Services passed in the command line to be stopped // Services passed in the command line to be stopped
@ -201,6 +205,8 @@ type KillOptions struct {
// RemoveOptions group options of the Remove API // RemoveOptions group options of the Remove API
type RemoveOptions struct { type RemoveOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
// DryRun just list removable resources // DryRun just list removable resources
DryRun bool DryRun bool
// Volumes remove anonymous volumes // Volumes remove anonymous volumes
@ -213,6 +219,8 @@ type RemoveOptions struct {
// RunOptions group options of the Run API // RunOptions group options of the Run API
type RunOptions struct { type RunOptions struct {
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
Name string Name string
Service string Service string
Command []string Command []string
@ -272,6 +280,7 @@ type ListOptions struct {
// PsOptions group options of the Ps API // PsOptions group options of the Ps API
type PsOptions struct { type PsOptions struct {
Project *types.Project
All bool All bool
Services []string Services []string
} }
@ -377,6 +386,8 @@ type LogOptions struct {
type PauseOptions struct { type PauseOptions struct {
// Services passed in the command line to be started // Services passed in the command line to be started
Services []string Services []string
// Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project
} }
const ( const (
@ -445,3 +456,15 @@ const (
// UserCancel user cancelled compose up, we are stopping containers // UserCancel user cancelled compose up, we are stopping containers
UserCancel UserCancel
) )
// Separator is used for naming components
var Separator = "-"
// GetImageNameOrDefault computes the default image name for a service, used to tag built images
func GetImageNameOrDefault(service types.ServiceConfig, projectName string) string {
imageName := service.Image
if imageName == "" {
imageName = projectName + Separator + service.Name
}
return imageName
}

View File

@ -48,12 +48,9 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) error { func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
opts := map[string]build.Options{} opts := map[string]build.Options{}
imagesToBuild := []string{} var imagesToBuild []string
args := flatten(options.Args.Resolve(func(s string) (string, bool) { args := flatten(options.Args.Resolve(envResolver(project.Environment)))
s, ok := project.Environment[s]
return s, ok
}))
services, err := project.GetServices(options.Services...) services, err := project.GetServices(options.Services...)
if err != nil { if err != nil {
@ -61,8 +58,10 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
} }
for _, service := range services { for _, service := range services {
if service.Build != nil { if service.Build == nil {
imageName := getImageName(service, project.Name) continue
}
imageName := api.GetImageNameOrDefault(service, project.Name)
imagesToBuild = append(imagesToBuild, imageName) imagesToBuild = append(imagesToBuild, imageName)
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs) buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
if err != nil { if err != nil {
@ -84,7 +83,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
} }
opts[imageName] = buildOptions opts[imageName] = buildOptions
} }
}
_, err = s.doBuild(ctx, project, opts, options.Progress) _, err = s.doBuild(ctx, project, opts, options.Progress)
if err == nil { if err == nil {
@ -134,14 +132,13 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
} }
// set digest as com.docker.compose.image label so we can detect outdated containers // set digest as com.docker.compose.image label so we can detect outdated containers
for i, service := range project.Services { for i, service := range project.Services {
image := getImageName(service, project.Name) image := api.GetImageNameOrDefault(service, project.Name)
digest, ok := images[image] digest, ok := images[image]
if ok { if ok {
if project.Services[i].Labels == nil { if project.Services[i].Labels == nil {
project.Services[i].Labels = types.Labels{} project.Services[i].Labels = types.Labels{}
} }
project.Services[i].CustomLabels[api.ImageDigestLabel] = digest project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
project.Services[i].Image = image
} }
} }
return nil return nil
@ -153,7 +150,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
if service.Image == "" && service.Build == nil { if service.Image == "" && service.Build == nil {
return nil, fmt.Errorf("invalid service %q. Must specify either image or build", service.Name) return nil, fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
} }
imageName := getImageName(service, project.Name) imageName := api.GetImageNameOrDefault(service, project.Name)
_, localImagePresent := images[imageName] _, localImagePresent := images[imageName]
if service.Build != nil { if service.Build != nil {
@ -173,9 +170,9 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
} }
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) { func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]string, error) {
imageNames := []string{} var imageNames []string
for _, s := range project.Services { for _, s := range project.Services {
imgName := getImageName(s, project.Name) imgName := api.GetImageNameOrDefault(s, project.Name)
if !utils.StringContains(imageNames, imgName) { if !utils.StringContains(imageNames, imgName) {
imageNames = append(imageNames, imgName) imageNames = append(imageNames, imgName)
} }
@ -189,11 +186,11 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
images[name] = info.ID images[name] = info.ID
} }
for _, s := range project.Services { for i := range project.Services {
imgName := getImageName(s, project.Name) imgName := api.GetImageNameOrDefault(project.Services[i], project.Name)
digest, ok := images[imgName] digest, ok := images[imgName]
if ok { if ok {
s.CustomLabels[api.ImageDigestLabel] = digest project.Services[i].CustomLabels.Add(api.ImageDigestLabel, digest)
} }
} }
@ -214,10 +211,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
var tags []string var tags []string
tags = append(tags, imageTag) tags = append(tags, imageTag)
buildArgs := flatten(service.Build.Args.Resolve(func(s string) (string, bool) { buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
s, ok := project.Environment[s]
return s, ok
}))
var plats []specs.Platform var plats []specs.Platform
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok { if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
@ -256,23 +250,11 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
} }
if len(service.Build.Secrets) > 0 { if len(service.Build.Secrets) > 0 {
var sources []secretsprovider.Source secretsProvider, err := addSecretsConfig(project, service)
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 { if err != nil {
return build.Options{}, err return build.Options{}, err
} }
p := secretsprovider.NewSecretProvider(store) sessionConfig = append(sessionConfig, secretsProvider)
sessionConfig = append(sessionConfig, p)
} }
if len(service.Build.Tags) > 0 { if len(service.Build.Tags) > 0 {
@ -324,11 +306,11 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
return merged return merged
} }
func dockerFilePath(context string, dockerfile string) string { func dockerFilePath(ctxName string, dockerfile string) string {
if urlutil.IsGitURL(context) || filepath.IsAbs(dockerfile) { if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
return dockerfile return dockerfile
} }
return filepath.Join(context, dockerfile) return filepath.Join(ctxName, dockerfile)
} }
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) { func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
@ -341,3 +323,30 @@ func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
} }
return sshprovider.NewSSHAgentProvider(sshConfig) return sshprovider.NewSSHAgentProvider(sshConfig)
} }
func addSecretsConfig(project *types.Project, service types.ServiceConfig) (session.Attachable, error) {
var sources []secretsprovider.Source
for _, secret := range service.Build.Secrets {
config := project.Secrets[secret.Source]
switch {
case config.File != "":
sources = append(sources, secretsprovider.Source{
ID: secret.Source,
FilePath: config.File,
})
case config.Environment != "":
sources = append(sources, secretsprovider.Source{
ID: secret.Source,
Env: config.Environment,
})
default:
return nil, fmt.Errorf("build.secrets only supports environment or file-based secrets: %q", secret.Source)
}
}
store, err := secretsprovider.NewStore(sources)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}

View File

@ -29,6 +29,7 @@ import (
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
buildx "github.com/docker/buildx/build" buildx "github.com/docker/buildx/build"
"github.com/docker/cli/cli/command/image/build" "github.com/docker/cli/cli/command/image/build"
"github.com/docker/compose/v2/pkg/api"
dockertypes "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
@ -45,7 +46,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
var nameDigests = make(map[string]string) var nameDigests = make(map[string]string)
var errs error var errs error
err := project.WithServices(nil, func(service types.ServiceConfig) error { err := project.WithServices(nil, func(service types.ServiceConfig) error {
imageName := getImageName(service, project.Name) imageName := api.GetImageNameOrDefault(service, project.Name)
o, ok := opts[imageName] o, ok := opts[imageName]
if !ok { if !ok {
return nil return nil
@ -214,7 +215,7 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
if imageID == "" { if imageID == "" {
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile) return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
} }
if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0666); err != nil { if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0o666); err != nil {
return "", err return "", err
} }
} }

View File

@ -24,6 +24,8 @@ import (
"io" "io"
"strings" "strings"
"gopkg.in/yaml.v2"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
@ -33,12 +35,8 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sanathkr/go-yaml"
) )
// Separator is used for naming components
var Separator = "-"
// NewComposeService create a local implementation of the compose.Service API // NewComposeService create a local implementation of the compose.Service API
func NewComposeService(dockerCli command.Cli) api.Service { func NewComposeService(dockerCli command.Cli) api.Service {
return &composeService{ return &composeService{
@ -175,26 +173,6 @@ SERVICES:
return project, nil return project, nil
} }
// actualState list resources labelled by projectName to rebuild compose project model
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
var containers Containers
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return nil, nil, err
}
project, err := s.projectFromName(containers, projectName, services...)
if err != nil && !api.IsNotFoundError(err) {
return nil, nil, err
}
if len(services) > 0 {
containers = containers.filter(isService(services...))
}
return containers, project, nil
}
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) { func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName))) volumes, err := s.apiClient().VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil { if err != nil {

View File

@ -261,7 +261,7 @@ func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy st
} }
func getContainerName(projectName string, service types.ServiceConfig, number int) string { func getContainerName(projectName string, service types.ServiceConfig, number int) string {
name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, Separator) name := strings.Join([]string{projectName, service.Name, strconv.Itoa(number)}, api.Separator)
if service.ContainerName != "" { if service.ContainerName != "" {
name = service.ContainerName name = service.ContainerName
} }
@ -553,8 +553,8 @@ func (s composeService) getLinks(ctx context.Context, projectName string, servic
containerName := getCanonicalContainerName(c) containerName := getCanonicalContainerName(c)
links = append(links, links = append(links,
format(containerName, linkName), format(containerName, linkName),
format(containerName, strings.Join([]string{linkServiceName, strconv.Itoa(number)}, Separator)), format(containerName, linkServiceName+api.Separator+strconv.Itoa(number)),
format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, Separator)), format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, api.Separator)),
) )
} }
} }
@ -568,7 +568,7 @@ func (s composeService) getLinks(ctx context.Context, projectName string, servic
containerName := getCanonicalContainerName(c) containerName := getCanonicalContainerName(c)
links = append(links, links = append(links,
format(containerName, service.Name), format(containerName, service.Name),
format(containerName, strings.TrimPrefix(containerName, projectName+Separator)), format(containerName, strings.TrimPrefix(containerName, projectName+api.Separator)),
format(containerName, containerName), format(containerName, containerName),
) )
} }
@ -607,6 +607,7 @@ func (s *composeService) connectContainerToNetwork(ctx context.Context, id strin
ipam = &network.EndpointIPAMConfig{ ipam = &network.EndpointIPAMConfig{
IPv4Address: ipv4Address, IPv4Address: ipv4Address,
IPv6Address: ipv6Address, IPv6Address: ipv6Address,
LinkLocalIPs: cfg.LinkLocalIPs,
} }
} }
err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{ err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{

View File

@ -263,7 +263,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
} }
preArchive := content preArchive := content
if len(srcInfo.RebaseName) != 0 { if srcInfo.RebaseName != "" {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path) _, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName) preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
} }

View File

@ -227,14 +227,6 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
return nil return nil
} }
func getImageName(service types.ServiceConfig, projectName string) string {
imageName := service.Image
if imageName == "" {
imageName = projectName + "_" + service.Name
}
return imageName
}
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig, 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) { number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
@ -279,7 +271,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
AttachStderr: true, AttachStderr: true,
AttachStdout: true, AttachStdout: true,
Cmd: runCmd, Cmd: runCmd,
Image: getImageName(service, p.Name), Image: api.GetImageNameOrDefault(service, p.Name),
WorkingDir: service.WorkingDir, WorkingDir: service.WorkingDir,
Entrypoint: entrypoint, Entrypoint: entrypoint,
NetworkDisabled: service.NetworkMode == "disabled", NetworkDisabled: service.NetworkMode == "disabled",
@ -316,6 +308,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
ipam = &network.EndpointIPAMConfig{ ipam = &network.EndpointIPAMConfig{
IPv4Address: ipv4Address, IPv4Address: ipv4Address,
IPv6Address: ipv6Address, IPv6Address: ipv6Address,
LinkLocalIPs: config.LinkLocalIPs,
} }
} }
networkConfig = &network.NetworkingConfig{ networkConfig = &network.NetworkingConfig{
@ -680,7 +673,7 @@ func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []s
continue continue
} }
if spec[0] == "container" { if spec[0] == "container" {
volumes = append(volumes, strings.Join(spec[1:], ":")) volumes = append(volumes, vol)
continue continue
} }
serviceName := spec[0] serviceName := spec[0]
@ -712,7 +705,7 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) { inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
var mounts = []mount.Mount{} var mounts = []mount.Mount{}
image := getImageName(service, p.Name) image := api.GetImageNameOrDefault(service, p.Name)
imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image) imgInspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, image)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -727,8 +720,12 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
binds := []string{} binds := []string{}
MOUNTS: MOUNTS:
for _, m := range mountOptions { for _, m := range mountOptions {
if m.Type == mount.TypeNamedPipe {
mounts = append(mounts, m)
continue
}
volumeMounts[m.Target] = struct{}{} volumeMounts[m.Target] = struct{}{}
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe { if m.Type == mount.TypeBind {
// `Mount` is preferred but does not offer option to created host path if missing // `Mount` is preferred but does not offer option to created host path if missing
// so `Bind` API is used here with raw volume string // so `Bind` API is used here with raw volume string
// see https://github.com/moby/moby/issues/43483 // see https://github.com/moby/moby/issues/43483
@ -893,7 +890,7 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
continue continue
} }
mount, err := buildMount(p, types.ServiceVolumeConfig{ mnt, err := buildMount(p, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind, Type: types.VolumeTypeBind,
Source: definedSecret.File, Source: definedSecret.File,
Target: target, Target: target,
@ -902,7 +899,7 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
if err != nil { if err != nil {
return nil, err return nil, err
} }
mounts[target] = mount mounts[target] = mnt
} }
values := make([]mount.Mount, 0, len(mounts)) values := make([]mount.Mount, 0, len(mounts))
for _, v := range mounts { for _, v := range mounts {
@ -911,8 +908,8 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
return values, nil return values, nil
} }
func isUnixAbs(path string) bool { func isUnixAbs(p string) bool {
return strings.HasPrefix(path, "/") return strings.HasPrefix(p, "/")
} }
func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) { func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
@ -1041,7 +1038,14 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
if err != nil { if err != nil {
return err return err
} }
if len(networks) == 0 { networkNotFound := true
for _, net := range networks {
if net.Name == n.Name {
networkNotFound = false
break
}
}
if networkNotFound {
if n.External.External { if n.External.External {
if n.Driver == "overlay" { if n.Driver == "overlay" {
// Swarm nodes do not register overlay networks that were // Swarm nodes do not register overlay networks that were

View File

@ -22,11 +22,12 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/compose-spec/compose-go/types"
composetypes "github.com/compose-spec/compose-go/types"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
composetypes "github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
mountTypes "github.com/docker/docker/api/types/mount" mountTypes "github.com/docker/docker/api/types/mount"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )
@ -45,6 +46,18 @@ func TestBuildBindMount(t *testing.T) {
assert.Equal(t, mount.Type, mountTypes.TypeBind) assert.Equal(t, mount.Type, mountTypes.TypeBind)
} }
func TestBuildNamedPipeMount(t *testing.T) {
project := composetypes.Project{}
volume := composetypes.ServiceVolumeConfig{
Type: composetypes.VolumeTypeNamedPipe,
Source: "\\\\.\\pipe\\docker_engine_windows",
Target: "\\\\.\\pipe\\docker_engine",
}
mount, err := buildMount(project, volume)
assert.NilError(t, err)
assert.Equal(t, mount.Type, mountTypes.TypeNamedPipe)
}
func TestBuildVolumeMount(t *testing.T) { func TestBuildVolumeMount(t *testing.T) {
project := composetypes.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
@ -66,17 +79,17 @@ func TestBuildVolumeMount(t *testing.T) {
} }
func TestServiceImageName(t *testing.T) { func TestServiceImageName(t *testing.T) {
assert.Equal(t, getImageName(types.ServiceConfig{Image: "myImage"}, "myProject"), "myImage") assert.Equal(t, api.GetImageNameOrDefault(composetypes.ServiceConfig{Image: "myImage"}, "myProject"), "myImage")
assert.Equal(t, getImageName(types.ServiceConfig{Name: "aService"}, "myProject"), "myProject_aService") assert.Equal(t, api.GetImageNameOrDefault(composetypes.ServiceConfig{Name: "aService"}, "myProject"), "myProject-aService")
} }
func TestPrepareNetworkLabels(t *testing.T) { func TestPrepareNetworkLabels(t *testing.T) {
project := types.Project{ project := composetypes.Project{
Name: "myProject", Name: "myProject",
Networks: types.Networks(map[string]types.NetworkConfig{"skynet": {}}), Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{"skynet": {}}),
} }
prepareNetworks(&project) prepareNetworks(&project)
assert.DeepEqual(t, project.Networks["skynet"].Labels, types.Labels(map[string]string{ assert.DeepEqual(t, project.Networks["skynet"].Labels, composetypes.Labels(map[string]string{
"com.docker.compose.network": "skynet", "com.docker.compose.network": "skynet",
"com.docker.compose.project": "myProject", "com.docker.compose.project": "myProject",
"com.docker.compose.version": api.ComposeVersion, "com.docker.compose.version": api.ComposeVersion,
@ -98,6 +111,11 @@ func TestBuildContainerMountOptions(t *testing.T) {
Type: composetypes.VolumeTypeVolume, Type: composetypes.VolumeTypeVolume,
Target: "/var/myvolume2", Target: "/var/myvolume2",
}, },
{
Type: composetypes.VolumeTypeNamedPipe,
Source: "\\\\.\\pipe\\docker_engine_windows",
Target: "\\\\.\\pipe\\docker_engine",
},
}, },
}, },
}, },
@ -129,18 +147,20 @@ func TestBuildContainerMountOptions(t *testing.T) {
return mounts[i].Target < mounts[j].Target return mounts[i].Target < mounts[j].Target
}) })
assert.NilError(t, err) assert.NilError(t, err)
assert.Assert(t, len(mounts) == 2) assert.Assert(t, len(mounts) == 3)
assert.Equal(t, mounts[0].Target, "/var/myvolume1") assert.Equal(t, mounts[0].Target, "/var/myvolume1")
assert.Equal(t, mounts[1].Target, "/var/myvolume2") assert.Equal(t, mounts[1].Target, "/var/myvolume2")
assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit) mounts, err = buildContainerMountOptions(project, project.Services[0], moby.ImageInspect{}, inherit)
sort.Slice(mounts, func(i, j int) bool { sort.Slice(mounts, func(i, j int) bool {
return mounts[i].Target < mounts[j].Target return mounts[i].Target < mounts[j].Target
}) })
assert.NilError(t, err) assert.NilError(t, err)
assert.Assert(t, len(mounts) == 2) assert.Assert(t, len(mounts) == 3)
assert.Equal(t, mounts[0].Target, "/var/myvolume1") assert.Equal(t, mounts[0].Target, "/var/myvolume1")
assert.Equal(t, mounts[1].Target, "/var/myvolume2") assert.Equal(t, mounts[1].Target, "/var/myvolume2")
assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
} }
func TestGetDefaultNetworkMode(t *testing.T) { func TestGetDefaultNetworkMode(t *testing.T) {

View File

@ -45,8 +45,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
resourceToRemove := false resourceToRemove := false
var containers Containers include := oneOffExclude
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true) if options.RemoveOrphans {
include = oneOffInclude
}
containers, err := s.getContainers(ctx, projectName, include, true)
if err != nil { if err != nil {
return err return err
} }
@ -163,6 +166,7 @@ func (s *composeService) removeNetwork(ctx context.Context, name string, w progr
var removed int var removed int
for _, net := range networks { for _, net := range networks {
if net.Name == name {
if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil { if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
if errdefs.IsNotFound(err) { if errdefs.IsNotFound(err) {
continue continue
@ -172,6 +176,7 @@ func (s *composeService) removeNetwork(ctx context.Context, name string, w progr
} }
removed++ removed++
} }
}
if removed == 0 { if removed == 0 {
// in practice, it's extremely unlikely for this to ever occur, as it'd // in practice, it's extremely unlikely for this to ever occur, as it'd
@ -193,7 +198,7 @@ func (s *composeService) getServiceImages(options api.DownOptions, project *type
continue continue
} }
if image == "" { if image == "" {
image = getImageName(service, project.Name) image = api.GetImageNameOrDefault(service, project.Name)
} }
images[image] = struct{}{} images[image] = struct{}{}
} }

View File

@ -40,7 +40,7 @@ func TestDown(t *testing.T) {
tested.dockerCli = cli tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes() cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]moby.Container{ []moby.Container{
testContainer("service1", "123", false), testContainer("service1", "123", false),
testContainer("service2", "456", false), testContainer("service2", "456", false),
@ -88,7 +88,7 @@ func TestDownRemoveOrphans(t *testing.T) {
tested.dockerCli = cli tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes() cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(true)).Return(
[]moby.Container{ []moby.Container{
testContainer("service1", "123", false), testContainer("service1", "123", false),
testContainer("service2", "789", false), testContainer("service2", "789", false),
@ -125,7 +125,7 @@ func TestDownRemoveVolumes(t *testing.T) {
tested.dockerCli = cli tested.dockerCli = cli
cli.EXPECT().Client().Return(api).AnyTimes() cli.EXPECT().Client().Return(api).AnyTimes()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]moby.Container{testContainer("service1", "123", false)}, nil) []moby.Container{testContainer("service1", "123", false)}, nil)
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))). api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
Return(volume.VolumeListOKBody{ Return(volume.VolumeListOKBody{

View File

@ -0,0 +1,68 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"runtime"
"strings"
)
var (
// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
)
// envResolver returns resolver for environment variables suitable for the current platform.
// Expected to be used with `MappingWithEquals.Resolve`.
// Updates in `environment` may not be reflected.
func envResolver(environment map[string]string) func(string) (string, bool) {
return envResolverWithCase(environment, isCaseInsensitiveEnvVars)
}
// envResolverWithCase returns resolver for environment variables with the specified case-sensitive condition.
// Expected to be used with `MappingWithEquals.Resolve`.
// Updates in `environment` may not be reflected.
func envResolverWithCase(environment map[string]string, caseInsensitive bool) func(string) (string, bool) {
if environment == nil {
return func(s string) (string, bool) {
return "", false
}
}
if !caseInsensitive {
return func(s string) (string, bool) {
v, ok := environment[s]
return v, ok
}
}
// variable names must be treated case-insensitively.
// Resolves in this way:
// * Return the value if its name matches with the passed name case-sensitively.
// * Otherwise, return the value if its lower-cased name matches lower-cased passed name.
// * The value is indefinite if multiple variable matches.
loweredEnvironment := make(map[string]string, len(environment))
for k, v := range environment {
loweredEnvironment[strings.ToLower(k)] = v
}
return func(s string) (string, bool) {
v, ok := environment[s]
if ok {
return v, ok
}
v, ok = loweredEnvironment[strings.ToLower(s)]
return v, ok
}
}

View File

@ -0,0 +1,115 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"testing"
"gotest.tools/assert"
)
func Test_EnvResolverWithCase(t *testing.T) {
tests := []struct {
name string
environment map[string]string
caseInsensitive bool
search string
expectedValue string
expectedOk bool
}{
{
name: "case sensitive/case match",
environment: map[string]string{
"Env1": "Value1",
"Env2": "Value2",
},
caseInsensitive: false,
search: "Env1",
expectedValue: "Value1",
expectedOk: true,
},
{
name: "case sensitive/case unmatch",
environment: map[string]string{
"Env1": "Value1",
"Env2": "Value2",
},
caseInsensitive: false,
search: "ENV1",
expectedValue: "",
expectedOk: false,
},
{
name: "case sensitive/nil environment",
environment: nil,
caseInsensitive: false,
search: "Env1",
expectedValue: "",
expectedOk: false,
},
{
name: "case insensitive/case match",
environment: map[string]string{
"Env1": "Value1",
"Env2": "Value2",
},
caseInsensitive: true,
search: "Env1",
expectedValue: "Value1",
expectedOk: true,
},
{
name: "case insensitive/case unmatch",
environment: map[string]string{
"Env1": "Value1",
"Env2": "Value2",
},
caseInsensitive: true,
search: "ENV1",
expectedValue: "Value1",
expectedOk: true,
},
{
name: "case insensitive/unmatch",
environment: map[string]string{
"Env1": "Value1",
"Env2": "Value2",
},
caseInsensitive: true,
search: "Env3",
expectedValue: "",
expectedOk: false,
},
{
name: "case insensitive/nil environment",
environment: nil,
caseInsensitive: true,
search: "Env1",
expectedValue: "",
expectedOk: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
f := envResolverWithCase(test.environment, test.caseInsensitive)
v, ok := f(test.search)
assert.Equal(t, v, test.expectedValue)
assert.Equal(t, ok, test.expectedOk)
})
}
}

View File

@ -23,7 +23,7 @@ import (
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
) )
// ServiceHash compute configuration has for a service // ServiceHash computes the configuration hash for a service.
func ServiceHash(o types.ServiceConfig) (string, error) { func ServiceHash(o types.ServiceConfig) (string, error) {
// remove the Build config when generating the service hash // remove the Build config when generating the service hash
o.Build = nil o.Build = nil

View File

@ -18,6 +18,7 @@ package compose
import ( import (
"context" "context"
"fmt"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -110,9 +111,15 @@ func anyCancellableContext() gomock.Matcher {
return gomock.AssignableToTypeOf(ctxWithCancel) return gomock.AssignableToTypeOf(ctxWithCancel)
} }
func projectFilterListOpt() moby.ContainerListOptions { func projectFilterListOpt(withOneOff bool) moby.ContainerListOptions {
filter := filters.NewArgs(
projectFilter(strings.ToLower(testProject)),
)
if !withOneOff {
filter.Add("label", fmt.Sprintf("%s=False", compose.OneoffLabel))
}
return moby.ContainerListOptions{ return moby.ContainerListOptions{
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))), Filters: filter,
All: true, All: true,
} }
} }

View File

@ -106,9 +106,9 @@ func combinedStatus(statuses []string) string {
for _, status := range keys { for _, status := range keys {
nb := nbByStatus[status] nb := nbByStatus[status]
if result != "" { if result != "" {
result = result + ", " result += ", "
} }
result = result + fmt.Sprintf("%s(%d)", status, nb) result += fmt.Sprintf("%s(%d)", status, nb)
} }
return result return result
} }

View File

@ -33,12 +33,16 @@ func (s *composeService) Pause(ctx context.Context, projectName string, options
}) })
} }
func (s *composeService) pause(ctx context.Context, project string, options api.PauseOptions) error { func (s *composeService) pause(ctx context.Context, projectName string, options api.PauseOptions) error {
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...) containers, err := s.getContainers(ctx, projectName, oneOffExclude, false, options.Services...)
if err != nil { if err != nil {
return err return err
} }
if options.Project != nil {
containers = containers.filter(isService(options.Project.ServiceNames()...))
}
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
containers.forEach(func(container moby.Container) { containers.forEach(func(container moby.Container) {
@ -67,6 +71,10 @@ func (s *composeService) unPause(ctx context.Context, projectName string, option
return err return err
} }
if options.Project != nil {
containers = containers.filter(isService(options.Project.ServiceNames()...))
}
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
containers.forEach(func(container moby.Container) { containers.forEach(func(container moby.Container) {

View File

@ -32,6 +32,11 @@ type logPrinter interface {
Cancel() Cancel()
} }
type printer struct {
queue chan api.ContainerEvent
consumer api.LogConsumer
}
// newLogPrinter builds a LogPrinter passing containers logs to LogConsumer // newLogPrinter builds a LogPrinter passing containers logs to LogConsumer
func newLogPrinter(consumer api.LogConsumer) logPrinter { func newLogPrinter(consumer api.LogConsumer) logPrinter {
queue := make(chan api.ContainerEvent) queue := make(chan api.ContainerEvent)
@ -48,11 +53,6 @@ func (p *printer) Cancel() {
} }
} }
type printer struct {
queue chan api.ContainerEvent
consumer api.LogConsumer
}
func (p *printer) HandleEvent(event api.ContainerEvent) { func (p *printer) HandleEvent(event api.ContainerEvent) {
p.queue <- event p.queue <- event
} }

View File

@ -37,6 +37,19 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
return nil, err return nil, err
} }
project := options.Project
if project == nil {
project, err = s.getProjectWithResources(ctx, containers, projectName)
if err != nil {
return nil, err
}
}
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
}
containers = containers.filter(isService(options.Services...))
summary := make([]api.ContainerSummary, len(containers)) summary := make([]api.ContainerSummary, len(containers))
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for i, container := range containers { for i, container := range containers {

View File

@ -26,6 +26,7 @@ import (
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
compose "github.com/docker/compose/v2/pkg/api" compose "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks" "github.com/docker/compose/v2/pkg/mocks"
@ -48,6 +49,8 @@ func TestPs(t *testing.T) {
c2, inspect2 := containerDetails("service1", "456", "running", "", 0) c2, inspect2 := containerDetails("service1", "456", "running", "", 0)
c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}} c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c3, inspect3 := containerDetails("service2", "789", "exited", "", 130) c3, inspect3 := containerDetails("service2", "789", "exited", "", 130)
api.EXPECT().VolumeList(ctx, gomock.Any()).Return(volume.VolumeListOKBody{}, nil)
api.EXPECT().NetworkList(ctx, gomock.Any()).Return([]moby.NetworkResource{}, nil)
api.EXPECT().ContainerList(ctx, listOpts).Return([]moby.Container{c1, c2, c3}, nil) api.EXPECT().ContainerList(ctx, listOpts).Return([]moby.Container{c1, c2, c3}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil)

View File

@ -31,7 +31,7 @@ import (
func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error { func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
projectName = strings.ToLower(projectName) projectName = strings.ToLower(projectName)
containers, _, err := s.actualState(ctx, projectName, options.Services) containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...)
if err != nil { if err != nil {
if api.IsNotFoundError(err) { if api.IsNotFoundError(err) {
fmt.Fprintln(s.stderr(), "No stopped containers") fmt.Fprintln(s.stderr(), "No stopped containers")
@ -40,6 +40,10 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
return err return err
} }
if options.Project != nil {
containers = containers.filter(isService(options.Project.ServiceNames()...))
}
stoppedContainers := containers.filter(func(c moby.Container) bool { stoppedContainers := containers.filter(func(c moby.Container) bool {
return c.State != ContainerRunning return c.State != ContainerRunning
}) })

View File

@ -34,28 +34,30 @@ func (s *composeService) Restart(ctx context.Context, projectName string, option
} }
func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error { func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
observedState, err := s.getContainers(ctx, projectName, oneOffExclude, true)
if err != nil { if err != nil {
return err return err
} }
project, err := s.projectFromName(observedState, projectName, options.Services...) project := options.Project
if project == nil {
project, err = s.getProjectWithResources(ctx, containers, projectName)
if err != nil { if err != nil {
return err return err
} }
}
if len(options.Services) == 0 { if len(options.Services) == 0 {
options.Services = project.ServiceNames() options.Services = project.ServiceNames()
} }
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
err = InDependencyOrder(ctx, project, func(c context.Context, service string) error { return InDependencyOrder(ctx, project, func(c context.Context, service string) error {
if !utils.StringContains(options.Services, service) { if !utils.StringContains(options.Services, service) {
return nil return nil
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, container := range observedState.filter(isService(service)) { for _, container := range containers.filter(isService(service)) {
container := container container := container
eg.Go(func() error { eg.Go(func() error {
eventName := getContainerProgressName(container) eventName := getContainerProgressName(container)
@ -69,8 +71,4 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
} }
return eg.Wait() return eg.Wait()
}) })
if err != nil {
return err
}
return nil
} }

View File

@ -114,15 +114,12 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
service.Entrypoint = opts.Entrypoint service.Entrypoint = opts.Entrypoint
} }
if len(opts.Environment) > 0 { if len(opts.Environment) > 0 {
env := types.NewMappingWithEquals(opts.Environment) cmdEnv := types.NewMappingWithEquals(opts.Environment)
projectEnv := env.Resolve(func(s string) (string, bool) { serviceOverrideEnv := cmdEnv.Resolve(func(s string) (string, bool) {
if _, ok := service.Environment[s]; ok { v, ok := envResolver(project.Environment)(s)
return "", false
}
v, ok := project.Environment[s]
return v, ok return v, ok
}).RemoveEmpty() }).RemoveEmpty()
service.Environment.OverrideBy(projectEnv) service.Environment.OverrideBy(serviceOverrideEnv)
} }
for k, v := range opts.Labels { for k, v := range opts.Labels {
service.Labels = service.Labels.Add(k, v) service.Labels = service.Labels.Add(k, v)

View File

@ -57,7 +57,7 @@ func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, erro
value := []byte(env) value := []byte(env)
b := bytes.Buffer{} b := bytes.Buffer{}
tarWriter := tar.NewWriter(&b) tarWriter := tar.NewWriter(&b)
mode := uint32(0400) mode := uint32(0o400)
if config.Mode != nil { if config.Mode != nil {
mode = *config.Mode mode = *config.Mode
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
) )
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error { func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
@ -31,15 +32,28 @@ func (s *composeService) Stop(ctx context.Context, projectName string, options a
} }
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error { func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
w := progress.ContextWriter(ctx) containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
containers, project, err := s.actualState(ctx, projectName, options.Services)
if err != nil { if err != nil {
return err return err
} }
project := options.Project
if project == nil {
project, err = s.getProjectWithResources(ctx, containers, projectName)
if err != nil {
return err
}
}
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
}
w := progress.ContextWriter(ctx)
return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error { return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
containersToStop := containers.filter(isService(service)).filter(isNotOneOff) if !utils.StringContains(options.Services, service) {
return s.stopContainers(ctx, w, containersToStop, options.Timeout) return nil
}
return s.stopContainers(ctx, w, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
}) })
} }

View File

@ -26,6 +26,8 @@ import (
"github.com/docker/compose/v2/pkg/mocks" "github.com/docker/compose/v2/pkg/mocks"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
) )
@ -40,12 +42,16 @@ func TestStopTimeout(t *testing.T) {
cli.EXPECT().Client().Return(api).AnyTimes() cli.EXPECT().Client().Return(api).AnyTimes()
ctx := context.Background() ctx := context.Background()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
[]moby.Container{ []moby.Container{
testContainer("service1", "123", false), testContainer("service1", "123", false),
testContainer("service1", "456", false), testContainer("service1", "456", false),
testContainer("service2", "789", false), testContainer("service2", "789", false),
}, nil) }, 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{}, nil)
timeout := time.Duration(2) * time.Second timeout := time.Duration(2) * time.Second
api.EXPECT().ContainerStop(gomock.Any(), "123", &timeout).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "123", &timeout).Return(nil)

View File

@ -61,12 +61,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
go func() { go func() {
<-signalChan <-signalChan
s.Kill(ctx, project.Name, api.KillOptions{ //nolint:errcheck s.Kill(ctx, project.Name, api.KillOptions{ //nolint:errcheck
Services: project.ServiceNames(), Services: options.Create.Services,
}) })
}() }()
return s.Stop(ctx, project.Name, api.StopOptions{ return s.Stop(ctx, project.Name, api.StopOptions{
Services: project.ServiceNames(), Services: options.Create.Services,
}) })
}) })
} }

46
pkg/e2e/assert.go Normal file
View File

@ -0,0 +1,46 @@
/*
Copyright 2022 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// RequireServiceState ensures that the container is in the expected state
// (running or exited).
func RequireServiceState(t testing.TB, cli *CLI, service string, state string) {
t.Helper()
psRes := cli.RunDockerComposeCmd(t, "ps", "--format=json", service)
var psOut []map[string]interface{}
require.NoError(t, json.Unmarshal([]byte(psRes.Stdout()), &psOut),
"Invalid `compose ps` JSON output")
for _, svc := range psOut {
require.Equal(t, service, svc["Service"],
"Found ps output for unexpected service")
require.Equalf(t,
strings.ToLower(state),
strings.ToLower(svc["State"].(string)),
"Service %q (%s) not in expected state",
service, svc["Name"],
)
}
}

66
pkg/e2e/buffer.go Normal file
View File

@ -0,0 +1,66 @@
/*
Copyright 2022 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"bytes"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type lockedBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (l *lockedBuffer) Read(p []byte) (n int, err error) {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.Read(p)
}
func (l *lockedBuffer) Write(p []byte) (n int, err error) {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.Write(p)
}
func (l *lockedBuffer) String() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.buf.String()
}
func (l *lockedBuffer) RequireEventuallyContains(t testing.TB, v string) {
t.Helper()
var bufContents strings.Builder
require.Eventuallyf(t, func() bool {
l.mu.Lock()
defer l.mu.Unlock()
if _, err := l.buf.WriteTo(&bufContents); err != nil {
require.FailNowf(t, "Failed to copy from buffer",
"Error: %v", err)
}
return strings.Contains(bufContents.String(), v)
}, 2*time.Second, 20*time.Millisecond,
"Buffer did not contain %q\n============\n%s\n============",
v, &bufContents)
}

View File

@ -31,30 +31,30 @@ func TestLocalComposeBuild(t *testing.T) {
t.Run("build named and unnamed images", func(t *testing.T) { t.Run("build named and unnamed images", func(t *testing.T) {
// ensure local test run does not reuse previously build image // ensure local test run does not reuse previously build image
c.RunDockerOrExitError(t, "rmi", "build-test_nginx") c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx") c.RunDockerOrExitError(t, "rmi", "custom-nginx")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build") res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"}) res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
c.RunDockerCmd(t, "image", "inspect", "build-test_nginx") c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
c.RunDockerCmd(t, "image", "inspect", "custom-nginx") c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
}) })
t.Run("build with build-arg", func(t *testing.T) { t.Run("build with build-arg", func(t *testing.T) {
// ensure local test run does not reuse previously build image // ensure local test run does not reuse previously build image
c.RunDockerOrExitError(t, "rmi", "build-test_nginx") c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx") c.RunDockerOrExitError(t, "rmi", "custom-nginx")
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR") c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
res := c.RunDockerCmd(t, "image", "inspect", "build-test_nginx") res := c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`}) res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
}) })
t.Run("build with build-arg set by env", func(t *testing.T) { t.Run("build with build-arg set by env", func(t *testing.T) {
// ensure local test run does not reuse previously build image // ensure local test run does not reuse previously build image
c.RunDockerOrExitError(t, "rmi", "build-test_nginx") c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx") c.RunDockerOrExitError(t, "rmi", "custom-nginx")
icmd.RunCmd(c.NewDockerComposeCmd(t, icmd.RunCmd(c.NewDockerComposeCmd(t,
@ -67,20 +67,20 @@ func TestLocalComposeBuild(t *testing.T) {
cmd.Env = append(cmd.Env, "FOO=BAR") cmd.Env = append(cmd.Env, "FOO=BAR")
}) })
res := c.RunDockerCmd(t, "image", "inspect", "build-test_nginx") res := c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`}) res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
}) })
t.Run("build with multiple build-args ", func(t *testing.T) { t.Run("build with multiple build-args ", func(t *testing.T) {
// ensure local test run does not reuse previously build image // ensure local test run does not reuse previously build image
c.RunDockerOrExitError(t, "rmi", "-f", "multi-args_multiargs") c.RunDockerOrExitError(t, "rmi", "-f", "multi-args-multiargs")
cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/multi-args", "build") cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/multi-args", "build")
icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0") cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
}) })
res := c.RunDockerCmd(t, "image", "inspect", "multi-args_multiargs") res := c.RunDockerCmd(t, "image", "inspect", "multi-args-multiargs")
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`}) res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
}) })
@ -131,7 +131,7 @@ func TestLocalComposeBuild(t *testing.T) {
}) })
t.Run("build as part of up", func(t *testing.T) { t.Run("build as part of up", func(t *testing.T) {
c.RunDockerOrExitError(t, "rmi", "build-test_nginx") c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
c.RunDockerOrExitError(t, "rmi", "custom-nginx") c.RunDockerOrExitError(t, "rmi", "custom-nginx")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d") res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
@ -145,7 +145,7 @@ func TestLocalComposeBuild(t *testing.T) {
output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second) output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second)
assert.Assert(t, strings.Contains(output, "Hello from Nginx container")) assert.Assert(t, strings.Contains(output, "Hello from Nginx container"))
c.RunDockerCmd(t, "image", "inspect", "build-test_nginx") c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
c.RunDockerCmd(t, "image", "inspect", "custom-nginx") c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
}) })
@ -164,7 +164,7 @@ func TestLocalComposeBuild(t *testing.T) {
t.Run("cleanup build project", func(t *testing.T) { t.Run("cleanup build project", func(t *testing.T) {
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down") c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
c.RunDockerCmd(t, "rmi", "build-test_nginx") c.RunDockerCmd(t, "rmi", "build-test-nginx")
c.RunDockerCmd(t, "rmi", "custom-nginx") c.RunDockerCmd(t, "rmi", "custom-nginx")
}) })
} }
@ -176,7 +176,12 @@ func TestBuildSecrets(t *testing.T) {
// ensure local test run does not reuse previously build image // ensure local test run does not reuse previously build image
c.RunDockerOrExitError(t, "rmi", "build-test-secret") c.RunDockerOrExitError(t, "rmi", "build-test-secret")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/secrets", "build") cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/secrets", "build")
res := icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
cmd.Env = append(cmd.Env, "SOME_SECRET=bar")
})
res.Assert(t, icmd.Success) res.Assert(t, icmd.Success)
}) })
} }
@ -211,10 +216,10 @@ func TestBuildImageDependencies(t *testing.T) {
t.Cleanup(resetState) t.Cleanup(resetState)
// the image should NOT exist now // the image should NOT exist now
res := cli.RunDockerOrExitError(t, "image", "inspect", "build-dependencies_service") res := cli.RunDockerOrExitError(t, "image", "inspect", "build-dependencies-service")
res.Assert(t, icmd.Expected{ res.Assert(t, icmd.Expected{
ExitCode: 1, ExitCode: 1,
Err: "Error: No such image: build-dependencies_service", Err: "Error: No such image: build-dependencies-service",
}) })
res = cli.RunDockerComposeCmd(t, "build") res = cli.RunDockerComposeCmd(t, "build")
@ -222,8 +227,8 @@ func TestBuildImageDependencies(t *testing.T) {
res = cli.RunDockerCmd(t, res = cli.RunDockerCmd(t,
"image", "inspect", "--format={{ index .RepoTags 0 }}", "image", "inspect", "--format={{ index .RepoTags 0 }}",
"build-dependencies_service") "build-dependencies-service")
res.Assert(t, icmd.Expected{Out: "build-dependencies_service:latest"}) res.Assert(t, icmd.Expected{Out: "build-dependencies-service:latest"})
} }
t.Run("ClassicBuilder", func(t *testing.T) { t.Run("ClassicBuilder", func(t *testing.T) {

View File

@ -27,137 +27,196 @@ import (
func TestEnvPriority(t *testing.T) { func TestEnvPriority(t *testing.T) {
c := NewParallelCLI(t) c := NewParallelCLI(t)
projectDir := "./fixtures/environment/env-priority"
t.Run("up", func(t *testing.T) { t.Run("up", func(t *testing.T) {
c.RunDockerOrExitError(t, "rmi", "env-compose-priority") c.RunDockerOrExitError(t, "rmi", "env-compose-priority")
c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml", c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
"--project-directory", projectDir, "up", "-d", "--build") "up", "-d", "--build")
}) })
// Full options activated // Full options activated
// 1. Compose file <-- Result expected // 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment patched by --env-file)
// 2. Shell environment variables // 2. Compose File (service::environment section)
// 3. Environment file // 3. Compose File (service::env_file section file)
// 4. Dockerfile // 4. Container Image ENV directive
// 5. Variable is not defined // 5. Variable is not defined
t.Run("compose file priority", func(t *testing.T) { t.Run("compose file priority", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml", cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--env-file", "./fixtures/environment/env-priority/.env.override",
"--rm", "-e", "WHEREAMI", "env-compose-priority") "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
cmd.Env = append(cmd.Env, "WHEREAMI=shell") cmd.Env = append(cmd.Env, "WHEREAMI=shell")
res := icmd.RunCmd(cmd) res := icmd.RunCmd(cmd)
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Compose File") assert.Equal(t, strings.TrimSpace(res.Stdout()), "override")
})
// Full options activated
// 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected
// 2. Compose File (service::environment section)
// 3. Compose File (service::env_file section file)
// 4. Container Image ENV directive
// 5. Variable is not defined
t.Run("compose file priority", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
"--env-file", "./fixtures/environment/env-priority/.env.override",
"run", "--rm", "-e", "WHEREAMI=shell", "env-compose-priority")
res := icmd.RunCmd(cmd)
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell")
}) })
// No Compose file, all other options // No Compose file, all other options
// 1. Compose file // 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment patched by --env-file)
// 2. Shell environment variables <-- Result expected // 2. Compose File (service::environment section)
// 3. Environment file // 3. Compose File (service::env_file section file)
// 4. Dockerfile // 4. Container Image ENV directive
// 5. Variable is not defined // 5. Variable is not defined
t.Run("shell priority", func(t *testing.T) { t.Run("shell priority", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory", cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e", "--env-file", "./fixtures/environment/env-priority/.env.override",
"WHEREAMI", "env-compose-priority") "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
res := icmd.RunCmd(cmd)
assert.Equal(t, strings.TrimSpace(res.Stdout()), "override")
})
// No Compose file, all other options with env variable from OS environment
// 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment)
// 2. Compose File (service::environment section)
// 3. Compose File (service::env_file section file)
// 4. Container Image ENV directive
// 5. Variable is not defined
t.Run("shell priority file with default value", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
"--env-file", "./fixtures/environment/env-priority/.env.override.with.default",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
cmd.Env = append(cmd.Env, "WHEREAMI=shell") cmd.Env = append(cmd.Env, "WHEREAMI=shell")
res := icmd.RunCmd(cmd) res := icmd.RunCmd(cmd)
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell") assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell")
}) })
// No Compose file, all other options with env variable from OS environment
// 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment default value from file in --env-file)
// 2. Compose File (service::environment section)
// 3. Compose File (service::env_file section file)
// 4. Container Image ENV directive
// 5. Variable is not defined
t.Run("shell priority implicitly set", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
"--env-file", "./fixtures/environment/env-priority/.env.override.with.default",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
res := icmd.RunCmd(cmd)
assert.Equal(t, strings.TrimSpace(res.Stdout()), "EnvFileDefaultValue")
})
// No Compose file and env variable pass to the run command // No Compose file and env variable pass to the run command
// 1. Compose file // 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected
// 2. Shell environment variables <-- Result expected // 2. Compose File (service::environment section)
// 3. Environment file // 3. Compose File (service::env_file section file)
// 4. Dockerfile // 4. Container Image ENV directive
// 5. Variable is not defined // 5. Variable is not defined
t.Run("shell priority from run command", func(t *testing.T) { t.Run("shell priority from run command", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory", res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e", "--env-file", "./fixtures/environment/env-priority/.env.override",
"WHEREAMI=shell-run", "env-compose-priority") "run", "--rm", "-e", "WHEREAMI=shell-run", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell-run") assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell-run")
}) })
// No Compose file & no env variable but override env file // No Compose file & no env variable but override env file
// 1. Compose file // 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment patched by .env as a default --env-file value)
// 2. Shell environment variables // 2. Compose File (service::environment section)
// 3. Environment file <-- Result expected // 3. Compose File (service::env_file section file)
// 4. Dockerfile // 4. Container Image ENV directive
// 5. Variable is not defined
t.Run("override env file from compose", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env-file.yaml",
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Env File")
})
// No Compose file & no env variable but override by default env file
// 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment patched by --env-file value)
// 2. Compose File (service::environment section)
// 3. Compose File (service::env_file section file)
// 4. Container Image ENV directive
// 5. Variable is not defined // 5. Variable is not defined
t.Run("override env file", func(t *testing.T) { t.Run("override env file", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory", res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e", "--env-file", "./fixtures/environment/env-priority/.env.override",
"WHEREAMI", "env-compose-priority") "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "override") assert.Equal(t, strings.TrimSpace(res.Stdout()), "override")
}) })
// No Compose file & no env variable but override env file // No Compose file & no env variable but override env file
// 1. Compose file // 1. Command Line (docker compose run --env <KEY[=VAL]>) <-- Result expected (From environment patched by --env-file value)
// 2. Shell environment variables // 2. Compose File (service::environment section)
// 3. Environment file <-- Result expected // 3. Compose File (service::env_file section file)
// 4. Dockerfile // 4. Container Image ENV directive
// 5. Variable is not defined // 5. Variable is not defined
t.Run("env file", func(t *testing.T) { t.Run("env file", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory", res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
projectDir, "run", "--rm", "-e", "WHEREAMI", "env-compose-priority") "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Env File") assert.Equal(t, strings.TrimSpace(res.Stdout()), "Env File")
}) })
// No Compose file & no env variable, using an empty override env file // No Compose file & no env variable, using an empty override env file
// 1. Compose file // 1. Command Line (docker compose run --env <KEY[=VAL]>)
// 2. Shell environment variables // 2. Compose File (service::environment section)
// 3. Environment file // 3. Compose File (service::env_file section file)
// 4. Dockerfile <-- Result expected // 4. Container Image ENV directive <-- Result expected
// 5. Variable is not defined // 5. Variable is not defined
t.Run("use Dockerfile", func(t *testing.T) { t.Run("use Dockerfile", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory", res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.empty", "run", "--rm", "-e", "WHEREAMI", "--env-file", "./fixtures/environment/env-priority/.env.empty",
"env-compose-priority") "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Dockerfile") assert.Equal(t, strings.TrimSpace(res.Stdout()), "Dockerfile")
}) })
t.Run("down", func(t *testing.T) { t.Run("down", func(t *testing.T) {
c.RunDockerComposeCmd(t, "--project-directory", projectDir, "down") c.RunDockerComposeCmd(t, "--project-name", "env-priority", "down")
}) })
} }
func TestEnvInterpolation(t *testing.T) { func TestEnvInterpolation(t *testing.T) {
c := NewParallelCLI(t) c := NewParallelCLI(t)
projectDir := "./fixtures/environment/env-interpolation" // No variable defined in the Compose file and nor env variable pass to the run command
// 1. Command Line (docker compose run --env <KEY[=VAL]>)
// No variable defined in the Compose file and env variable pass to the run command // 2. Compose File (service::environment section) <-- Result expected (From environment patched by .env as a default --env-file value)
// 1. Compose file // 3. Compose File (service::env_file section file)
// 2. Shell environment variables <-- Result expected // 4. Container Image ENV directive
// 3. Environment file
// 4. Dockerfile
// 5. Variable is not defined // 5. Variable is not defined
t.Run("shell priority from run command", func(t *testing.T) { t.Run("shell priority from run command", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation/compose.yaml", cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation/compose.yaml", "config")
"--project-directory", projectDir, "config")
cmd.Env = append(cmd.Env, "WHEREAMI=shell") cmd.Env = append(cmd.Env, "WHEREAMI=shell")
res := icmd.RunCmd(cmd) res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{Out: `IMAGE: default_env:shell`}) res.Assert(t, icmd.Expected{Out: `IMAGE: default_env:EnvFile`})
})
// No variable defined in the Compose file and env variable pass to the run command
// 1. Command Line (docker compose run --env <KEY[=VAL]>)
// 2. Compose File (service::environment section) <-- Result expected (From environment patched by .env as a default --env-file value.
// This variable has a default value in case of an absent variable in the OS environment)
// 3. Compose File (service::env_file section file)
// 4. Container Image ENV directive
// 5. Variable is not defined
t.Run("shell priority from run command using default value fallback", func(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation-default-value/compose.yaml", "config").
Assert(t, icmd.Expected{Out: `IMAGE: default_env:EnvFileDefaultValue`})
}) })
} }
func TestCommentsInEnvFile(t *testing.T) { func TestCommentsInEnvFile(t *testing.T) {
c := NewParallelCLI(t) c := NewParallelCLI(t)
projectDir := "./fixtures/environment/env-file-comments"
t.Run("comments in env files", func(t *testing.T) { t.Run("comments in env files", func(t *testing.T) {
c.RunDockerOrExitError(t, "rmi", "env-file-comments") c.RunDockerOrExitError(t, "rmi", "env-file-comments")
c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml", "--project-directory", c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml", "up", "-d", "--build")
projectDir, "up", "-d", "--build")
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml", res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml",
"--project-directory", projectDir, "run", "--rm", "-e", "COMMENT", "-e", "NO_COMMENT", "env-file-comments") "run", "--rm", "-e", "COMMENT", "-e", "NO_COMMENT", "env-file-comments")
res.Assert(t, icmd.Expected{Out: `COMMENT=1234`}) res.Assert(t, icmd.Expected{Out: `COMMENT=1234`})
res.Assert(t, icmd.Expected{Out: `NO_COMMENT=1234#5`}) res.Assert(t, icmd.Expected{Out: `NO_COMMENT=1234#5`})
c.RunDockerComposeCmd(t, "--project-directory", projectDir, "down", "--rmi", "all") c.RunDockerComposeCmd(t, "--project-name", "env-file-comments", "down", "--rmi", "all")
}) })
} }

View File

@ -79,7 +79,7 @@ func TestLocalComposeRun(t *testing.T) {
}) })
t.Run("down", func(t *testing.T) { t.Run("down", func(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down") c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down", "--remove-orphans")
res := c.RunDockerCmd(t, "ps", "--all") res := c.RunDockerCmd(t, "ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
}) })
@ -121,6 +121,7 @@ func TestLocalComposeRun(t *testing.T) {
c.Env = append(c.Env, "COMPOSE_REMOVE_ORPHANS=True") c.Env = append(c.Env, "COMPOSE_REMOVE_ORPHANS=True")
}) })
res := c.RunDockerCmd(t, "ps", "--all") res := c.RunDockerCmd(t, "ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
}) })

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.2 # syntax=docker/dockerfile:1
# Copyright 2020 Docker Compose CLI authors # Copyright 2020 Docker Compose CLI authors
@ -20,3 +20,7 @@ FROM alpine
RUN echo "foo" > /tmp/expected RUN echo "foo" > /tmp/expected
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /tmp/actual RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /tmp/actual
RUN diff /tmp/expected /tmp/actual RUN diff /tmp/expected /tmp/actual
RUN echo "bar" > /tmp/expected
RUN --mount=type=secret,id=envsecret cat /run/secrets/envsecret > tmp/actual
RUN diff --ignore-all-space /tmp/expected /tmp/actual

View File

@ -5,7 +5,10 @@ services:
context: . context: .
secrets: secrets:
- mysecret - mysecret
- envsecret
secrets: secrets:
mysecret: mysecret:
file: ./secret.txt file: ./secret.txt
envsecret:
environment: SOME_SECRET

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.2 # syntax=docker/dockerfile:1
# Copyright 2020 Docker Compose CLI authors # Copyright 2020 Docker Compose CLI authors

View File

@ -0,0 +1 @@
IMAGE=default_env:${WHEREAMI:-EnvFileDefaultValue}

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