mirror of
https://github.com/docker/compose.git
synced 2026-02-18 14:32:33 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ae036fd0 | ||
|
|
3f0550f884 | ||
|
|
18ce1f41b7 | ||
|
|
3bf29d401c | ||
|
|
c384905d70 | ||
|
|
7424a3d3c1 | ||
|
|
7c0b8a4c96 | ||
|
|
6b7e9466c4 | ||
|
|
a6dd996988 | ||
|
|
91eae4f035 | ||
|
|
8b89721476 | ||
|
|
3892e9cbc4 | ||
|
|
f43a1e3ece | ||
|
|
fa1ae635d1 | ||
|
|
afc0263f5c | ||
|
|
b15df818c7 | ||
|
|
bb002a7688 | ||
|
|
2ccd57e01a | ||
|
|
1c14d30777 | ||
|
|
8bd487ac43 | ||
|
|
1d4cb32001 | ||
|
|
19d1ab77eb | ||
|
|
a01f62f5dc | ||
|
|
045f5ad758 | ||
|
|
b6b58d26c1 | ||
|
|
55b1b9976b | ||
|
|
34441c8e4a | ||
|
|
139a6945cb | ||
|
|
97a9d02dda | ||
|
|
25c4bcef85 | ||
|
|
4607dac19c | ||
|
|
616777eb4a | ||
|
|
c1f475d7bd | ||
|
|
c6109b2e5c | ||
|
|
fffe7fff57 | ||
|
|
0a5f4e62e4 | ||
|
|
d88f6805e7 | ||
|
|
266ab22d53 | ||
|
|
a7476c8eeb | ||
|
|
15ebff00b1 | ||
|
|
f44ca01fcf | ||
|
|
19a1454c2d | ||
|
|
aa297a9969 | ||
|
|
0d0a02cc6b | ||
|
|
3c641ed265 | ||
|
|
f41eec4e09 | ||
|
|
140dc519d3 | ||
|
|
279225896a | ||
|
|
a95cc4074a | ||
|
|
b4420c372b | ||
|
|
ce3700d334 | ||
|
|
e2a3fe9427 | ||
|
|
94465d57cc | ||
|
|
0dc64723c9 | ||
|
|
8891d9e2b5 | ||
|
|
6cd68a4bf2 | ||
|
|
a1984ca1de | ||
|
|
118b4f07e5 | ||
|
|
8714f983ac | ||
|
|
6bc50cb457 | ||
|
|
937fa2dc8f | ||
|
|
71ab6c9eef | ||
|
|
723078c593 |
64
.github/ISSUE_TEMPLATE.md
vendored
64
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,64 +0,0 @@
|
||||
<!--
|
||||
If you are reporting a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
If you suspect your issue is a bug, please edit your issue description to
|
||||
include the BUG REPORT INFORMATION shown below. If you fail to provide this
|
||||
information within 7 days, we cannot debug your issue and will close it. We
|
||||
will, however, reopen it if you later provide the information.
|
||||
|
||||
For more information about reporting issues, see
|
||||
https://github.com/docker/compose-cli/blob/master/CONTRIBUTING.md#reporting-other-issues
|
||||
|
||||
---------------------------------------------------
|
||||
GENERAL SUPPORT INFORMATION
|
||||
---------------------------------------------------
|
||||
|
||||
The GitHub issue tracker is for bug reports and feature requests.
|
||||
General support can be found at the following locations:
|
||||
|
||||
- Docker Support Forums - https://forums.docker.com
|
||||
- Docker Community Slack - https://dockr.ly/slack
|
||||
- Post a question on StackOverflow, using the Docker tag
|
||||
|
||||
---------------------------------------------------
|
||||
BUG REPORT INFORMATION
|
||||
---------------------------------------------------
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
-->
|
||||
|
||||
**Description**
|
||||
|
||||
<!--
|
||||
Briefly describe the problem you are having in a few paragraphs.
|
||||
-->
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
**Additional information you deem important (e.g. issue happens only occasionally):**
|
||||
|
||||
**Output of `docker compose version`:**
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
**Output of `docker info`:**
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
**Additional environment details:**
|
||||
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: 🐞 Bug
|
||||
description: File a bug/issue
|
||||
title: "[BUG] <title>"
|
||||
labels: ['status/0-triage', 'kind/bug']
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Briefly describe the problem you are having.
|
||||
|
||||
Include both the current behavior (what you are seeing) as well as what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Compose Version
|
||||
description: |
|
||||
Paste output of `docker compose version` and `docker-compose version`.
|
||||
render: Text
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Docker Environment
|
||||
description: Paste output of `docker info`.
|
||||
render: Text
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Docker Community Slack
|
||||
url: https://dockr.ly/slack
|
||||
about: 'Use the #docker-compose channel'
|
||||
- name: Docker Support Forums
|
||||
url: https://forums.docker.com/c/open-source-projects/compose/15
|
||||
about: 'Use the "Open Source Projects > Compose" category'
|
||||
- name: 'Ask on Stack Overflow'
|
||||
url: https://stackoverflow.com/questions/tagged/docker-compose
|
||||
about: 'Use the [docker-compose] tag when creating new questions'
|
||||
13
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
13
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Feature request
|
||||
description: Missing functionality? Come tell us about it!
|
||||
labels:
|
||||
- kind/feature
|
||||
- status/0-triage
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What is the feature you want to see?
|
||||
validations:
|
||||
required: true
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -22,6 +22,9 @@ env:
|
||||
DESTDIR: "./bin"
|
||||
DOCKER_CLI_VERSION: "20.10.17"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -49,6 +52,7 @@ jobs:
|
||||
target:
|
||||
- lint
|
||||
- validate-go-mod
|
||||
- validate-modules
|
||||
- validate-headers
|
||||
- validate-docs
|
||||
steps:
|
||||
@@ -173,6 +177,9 @@ jobs:
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
run: |
|
||||
make e2e-compose
|
||||
-
|
||||
name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
-
|
||||
name: Test standalone mode
|
||||
if: ${{ matrix.mode == 'standalone' }}
|
||||
@@ -182,6 +189,9 @@ jobs:
|
||||
make e2e-compose-standalone
|
||||
|
||||
release:
|
||||
permissions:
|
||||
contents: write # to create a release (ncipollo/release-action)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- binary
|
||||
|
||||
7
.github/workflows/docs.yml
vendored
7
.github/workflows/docs.yml
vendored
@@ -4,8 +4,13 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
open-pr:
|
||||
permissions:
|
||||
contents: write # to create branch (peter-evans/create-pull-request)
|
||||
pull-requests: write # to create a PR (peter-evans/create-pull-request)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
@@ -13,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
repository: docker/docker.github.io
|
||||
repository: docker/docs
|
||||
ref: master
|
||||
-
|
||||
name: Prepare
|
||||
|
||||
74
.github/workflows/merge.yml
vendored
Normal file
74
.github/workflows/merge.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: merge
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'v2'
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: Build and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [desktop-windows, desktop-macos, desktop-m1]
|
||||
# mode: [plugin, standalone]
|
||||
mode: [plugin]
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
check-latest: true
|
||||
|
||||
- name: List Docker resources on machine
|
||||
run: |
|
||||
docker ps --all
|
||||
docker volume ls
|
||||
docker network ls
|
||||
docker image ls
|
||||
- name: Remove Docker resources on machine
|
||||
continue-on-error: true
|
||||
run: |
|
||||
docker kill $(docker ps -q)
|
||||
docker rm -f $(docker ps -aq)
|
||||
docker volume rm -f $(docker volume ls -q)
|
||||
docker ps --all
|
||||
|
||||
- name: Unit tests
|
||||
run: make test
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
make
|
||||
- name: Check arch of go compose binary
|
||||
run: |
|
||||
file ./bin/build/docker-compose
|
||||
if: ${{ !contains(matrix.os, 'desktop-windows') }}
|
||||
-
|
||||
name: Test plugin mode
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
run: |
|
||||
make e2e-compose
|
||||
-
|
||||
name: Test standalone mode
|
||||
if: ${{ matrix.mode == 'standalone' }}
|
||||
run: |
|
||||
make e2e-compose-standalone
|
||||
|
||||
19
.github/workflows/rebase.yml
vendored
19
.github/workflows/rebase.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Automatic Rebase
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
bin/
|
||||
/.vscode/
|
||||
coverage.out
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.19.1
|
||||
ARG GO_VERSION=1.19.2
|
||||
ARG XX_VERSION=1.1.2
|
||||
ARG GOLANGCI_LINT_VERSION=v1.49.0
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
@@ -69,6 +69,11 @@ RUN --mount=type=bind,target=.,rw <<EOT
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM vendored AS modules-validate
|
||||
RUN apk add --no-cache bash
|
||||
RUN apk add --no-cache jq
|
||||
RUN --mount=type=bind,target=.,rw ./verify-go-modules.sh e2e
|
||||
|
||||
FROM build-base AS build
|
||||
ARG BUILD_TAGS
|
||||
ARG TARGETPLATFORM
|
||||
@@ -177,7 +182,7 @@ FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
|
||||
# docs-reference is a target used as remote context to update docs on release
|
||||
# with latest changes on docker.github.io.
|
||||
# with latest changes on docs.docker.com.
|
||||
# see open-pr job in .github/workflows/docs.yml for more details
|
||||
FROM scratch AS docs-reference
|
||||
COPY docs/reference/*.yaml .
|
||||
|
||||
26
Makefile
26
Makefile
@@ -18,15 +18,23 @@ VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
|
||||
GO_LDFLAGS ?= -s -w -X ${PKG}/internal.Version=${VERSION}
|
||||
GO_BUILDTAGS ?= e2e,kube
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DETECTED_OS = Windows
|
||||
else
|
||||
DETECTED_OS = $(shell uname -s)
|
||||
endif
|
||||
ifeq ($(DETECTED_OS),Linux)
|
||||
MOBY_DOCKER=/usr/bin/docker
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
ifeq ($(DETECTED_OS),Darwin)
|
||||
MOBY_DOCKER=/Applications/Docker.app/Contents/Resources/bin/docker
|
||||
endif
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
BINARY_EXT=.exe
|
||||
endif
|
||||
|
||||
TEST_FLAGS?=
|
||||
TEST_COVERAGE_FLAGS = -race -coverprofile=coverage.out -covermode=atomic
|
||||
TEST_FLAGS?= -timeout 15m
|
||||
E2E_TEST?=
|
||||
ifeq ($(E2E_TEST),)
|
||||
else
|
||||
@@ -40,7 +48,7 @@ all: build
|
||||
|
||||
.PHONY: build ## Build the compose cli-plugin
|
||||
build:
|
||||
CGO_ENABLED=0 GO111MODULE=on go build -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(DESTDIR)/docker-compose" ./cmd
|
||||
CGO_ENABLED=0 GO111MODULE=on go build -trimpath -tags "$(GO_BUILDTAGS)" -ldflags "$(GO_LDFLAGS)" -o "$(DESTDIR)/docker-compose$(BINARY_EXT)" ./cmd
|
||||
|
||||
.PHONY: binary
|
||||
binary:
|
||||
@@ -54,7 +62,7 @@ install: binary
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
docker compose version
|
||||
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
go test $(TEST_FLAGS) $(TEST_COVERAGE_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
@@ -123,7 +131,11 @@ go-mod-tidy: ## Run go mod tidy in a container and output resulting go.mod and g
|
||||
validate-go-mod: ## Validate go.mod and go.sum are up-to-date
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
|
||||
validate: validate-go-mod validate-headers validate-docs ## Validate sources
|
||||
.PHONY: validate-modules
|
||||
validate-modules: ## Validate root and e2e go.mod are synced
|
||||
$(BUILDX_CMD) bake modules-validate
|
||||
|
||||
validate: validate-go-mod validate-modules validate-headers validate-docs ## Validate sources
|
||||
|
||||
pre-commit: validate check-dependencies lint build test e2e-compose
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[](https://pkg.go.dev/github.com/docker/compose/v2)
|
||||
[](https://github.com/docker/compose/actions?query=workflow%3Aci)
|
||||
[](https://goreportcard.com/report/github.com/docker/compose/v2)
|
||||
[](https://codecov.io/gh/docker/compose)
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ import (
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
func getCompletionCommands() []string {
|
||||
return []string{
|
||||
"__complete",
|
||||
"__completeNoDesc",
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolFlags() []string {
|
||||
return []string{
|
||||
"--debug", "-D",
|
||||
@@ -50,6 +57,10 @@ func Convert(args []string) []string {
|
||||
l := len(args)
|
||||
for i := 0; i < l; i++ {
|
||||
arg := args[i]
|
||||
if contains(getCompletionCommands(), arg) {
|
||||
command = append([]string{arg}, command...)
|
||||
continue
|
||||
}
|
||||
if len(arg) > 0 && arg[0] != '-' {
|
||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||
if arg == compose.PluginName {
|
||||
|
||||
@@ -102,7 +102,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -27,11 +28,11 @@ type validArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]s
|
||||
|
||||
func noCompletion() validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
return []string{}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
}
|
||||
|
||||
func serviceCompletion(p *projectOptions) validArgsFn {
|
||||
func completeServiceNames(p *projectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
project, err := p.toProject(nil)
|
||||
if err != nil {
|
||||
@@ -46,3 +47,21 @@ func serviceCompletion(p *projectOptions) validArgsFn {
|
||||
return serviceNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := backend.List(cmd.Context(), api.ListOptions{
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
var values []string
|
||||
for _, stack := range list {
|
||||
if strings.HasPrefix(stack.Name, toComplete) {
|
||||
values = append(values, stack.Name)
|
||||
}
|
||||
}
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
cnabgodocker "github.com/cnabio/cnab-go/driver/docker"
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||
@@ -33,7 +32,6 @@ import (
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -293,16 +291,6 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Reset DockerCli and APIClient to get possible `DOCKER_HOST` and/or `DOCKER_CONTEXT` loaded from environment file.
|
||||
err = dockerCli.Apply(func(cli *command.DockerCli) error {
|
||||
return cli.Initialize(cnabgodocker.BuildDockerClientOptions(),
|
||||
command.WithInitializeClient(func(_ *command.DockerCli) (client.APIClient, error) {
|
||||
return nil, nil
|
||||
}))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent := cmd.Root()
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
@@ -370,6 +358,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
)
|
||||
c.Flags().SetInterspersed(false)
|
||||
opts.addProjectFlags(c.Flags())
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"project-name",
|
||||
completeProjectNames(backend),
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"file",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt
|
||||
},
|
||||
)
|
||||
|
||||
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information")
|
||||
c.Flags().MarkHidden("version") //nolint:errcheck
|
||||
|
||||
@@ -93,7 +93,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
|
||||
@@ -60,7 +60,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
opts.destination = args[1]
|
||||
return runCopy(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
|
||||
@@ -70,7 +70,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
QuietPull: false,
|
||||
})
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
|
||||
|
||||
@@ -43,7 +43,7 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runEvents(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
|
||||
|
||||
@@ -61,7 +61,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExec(ctx, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
|
||||
|
||||
@@ -48,7 +48,7 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runImages(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
return imgCmd
|
||||
|
||||
@@ -42,7 +42,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@@ -49,7 +49,7 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runLogs(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := logsCmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
|
||||
|
||||
@@ -38,7 +38,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runUnPause(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPort(ctx, backend, opts, args[0])
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
|
||||
|
||||
@@ -78,7 +78,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPs(ctx, backend, args, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]")
|
||||
|
||||
@@ -54,7 +54,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPull(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
|
||||
|
||||
@@ -29,6 +29,7 @@ type pushOptions struct {
|
||||
composeOptions
|
||||
|
||||
Ignorefailures bool
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
@@ -41,9 +42,10 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPush(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures")
|
||||
pushCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Push without printing progress information")
|
||||
|
||||
return pushCmd
|
||||
}
|
||||
@@ -56,5 +58,6 @@ func runPush(ctx context.Context, backend api.Service, opts pushOptions, service
|
||||
|
||||
return backend.Push(ctx, project, api.PushOptions{
|
||||
IgnoreFailures: opts.Ignorefailures,
|
||||
Quiet: opts.Quiet,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ Any data which is not in a volume will be lost.`,
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRemove(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal")
|
||||
|
||||
@@ -40,7 +40,7 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runRestart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := restartCmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
@@ -143,7 +143,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
|
||||
@@ -37,7 +37,7 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStart(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return startCmd
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
|
||||
@@ -44,7 +44,7 @@ func topCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runTop(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
return topCmd
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return runUp(ctx, backend, create, up, project, services)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
ValidArgsFunction: completeServiceNames(p),
|
||||
}
|
||||
flags := upCmd.Flags()
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
|
||||
@@ -34,14 +34,13 @@ import (
|
||||
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
lazyInit := api.NewServiceProxy()
|
||||
cmd := commands.RootCommand(dockerCli, lazyInit)
|
||||
serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli))
|
||||
cmd := commands.RootCommand(dockerCli, serviceProxy)
|
||||
originalPreRun := cmd.PersistentPreRunE
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
lazyInit.WithService(compose.NewComposeService(dockerCli))
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.19.1"
|
||||
default = "1.19.2"
|
||||
}
|
||||
|
||||
variable "BUILD_TAGS" {
|
||||
@@ -71,6 +71,12 @@ target "vendor-validate" {
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "modules-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "modules-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
|
||||
@@ -8,6 +8,7 @@ Push service images
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
|
||||
| `-q`, `--quiet` | | | Push without printing progress information |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -33,6 +33,17 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Push without printing progress information
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
||||
5
e2e/cucumber-features/down.feature
Normal file
5
e2e/cucumber-features/down.feature
Normal file
@@ -0,0 +1,5 @@
|
||||
Feature: Down
|
||||
|
||||
Scenario: No resources to remove
|
||||
When I run "compose down"
|
||||
Then the output contains "Warning: No resource found to remove for project "no_resources_to_remove""
|
||||
15
e2e/cucumber-features/simple.feature
Normal file
15
e2e/cucumber-features/simple.feature
Normal file
@@ -0,0 +1,15 @@
|
||||
Feature: Simple service up
|
||||
|
||||
Background:
|
||||
Given a compose file
|
||||
"""
|
||||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
command: top
|
||||
"""
|
||||
|
||||
Scenario: compose up
|
||||
When I run "compose up -d"
|
||||
Then the output contains "simple-1 Started"
|
||||
And service "simple" is "running"
|
||||
21
e2e/cucumber-features/start.feature
Normal file
21
e2e/cucumber-features/start.feature
Normal file
@@ -0,0 +1,21 @@
|
||||
Feature: Start
|
||||
|
||||
Background:
|
||||
Given a compose file
|
||||
"""
|
||||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
command: top
|
||||
another:
|
||||
image: alpine
|
||||
command: top
|
||||
"""
|
||||
|
||||
Scenario: Start single service
|
||||
When I run "compose create"
|
||||
Then the output contains "simple-1 Created"
|
||||
And the output contains "another-1 Created"
|
||||
Then I run "compose start another"
|
||||
And service "another" is "running"
|
||||
And service "simple" is "created"
|
||||
30
e2e/cucumber-features/stop.feature
Normal file
30
e2e/cucumber-features/stop.feature
Normal file
@@ -0,0 +1,30 @@
|
||||
Feature: Stop
|
||||
|
||||
Background:
|
||||
Given a compose file
|
||||
"""
|
||||
services:
|
||||
should_fail:
|
||||
image: alpine
|
||||
command: ls /does_not_exist
|
||||
sleep: # will be killed
|
||||
image: alpine
|
||||
command: ping localhost
|
||||
"""
|
||||
|
||||
Scenario: Cascade stop
|
||||
When I run "compose up --abort-on-container-exit"
|
||||
Then the output contains "should_fail-1 exited with code 1"
|
||||
And the output contains "Aborting on container exit..."
|
||||
And the exit code is 1
|
||||
|
||||
Scenario: Exit code from
|
||||
When I run "compose up --exit-code-from sleep"
|
||||
Then the output contains "should_fail-1 exited with code 1"
|
||||
And the output contains "Aborting on container exit..."
|
||||
And the exit code is 137
|
||||
|
||||
Scenario: Exit code from unknown service
|
||||
When I run "compose up --exit-code-from unknown"
|
||||
Then the output contains "no such service: unknown"
|
||||
And the exit code is 1
|
||||
129
e2e/cucumber_test.go
Normal file
129
e2e/cucumber_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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 cucumber
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/colors"
|
||||
"github.com/docker/compose/v2/pkg/e2e"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestCucumber(t *testing.T) {
|
||||
testingOptions := godog.Options{
|
||||
TestingT: t,
|
||||
Paths: []string{"./cucumber-features"},
|
||||
Output: colors.Colored(os.Stdout),
|
||||
Format: "pretty",
|
||||
}
|
||||
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
Options: &testingOptions,
|
||||
ScenarioInitializer: setup,
|
||||
}.Run()
|
||||
|
||||
if status == 2 {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
t.Fatalf("zero status code expected, %d received", status)
|
||||
}
|
||||
}
|
||||
|
||||
func setup(s *godog.ScenarioContext) {
|
||||
t := s.TestingT()
|
||||
cli := e2e.NewCLI(t, e2e.WithEnv(
|
||||
fmt.Sprintf("COMPOSE_PROJECT_NAME=%s", strings.Split(t.Name(), "/")[1]),
|
||||
))
|
||||
th := testHelper{
|
||||
T: t,
|
||||
CLI: cli,
|
||||
}
|
||||
|
||||
s.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
|
||||
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
|
||||
return ctx, nil
|
||||
})
|
||||
|
||||
s.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
|
||||
cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0")
|
||||
return ctx, nil
|
||||
})
|
||||
|
||||
s.Step(`^a compose file$`, th.setComposeFile)
|
||||
s.Step(`^I run "compose (.*)"$`, th.runComposeCommand)
|
||||
s.Step(`service "(.*)" is "(.*)"$`, th.serviceIsStatus)
|
||||
s.Step(`output contains "(.*)"$`, th.outputContains)
|
||||
s.Step(`exit code is (\d+)$`, th.exitCodeIs)
|
||||
}
|
||||
|
||||
type testHelper struct {
|
||||
T *testing.T
|
||||
ComposeFile string
|
||||
CommandOutput string
|
||||
CommandExitCode int
|
||||
CLI *e2e.CLI
|
||||
}
|
||||
|
||||
func (th *testHelper) serviceIsStatus(service, status string) error {
|
||||
res := th.CLI.RunDockerComposeCmd(th.T, "ps", "-a")
|
||||
statusRegex := fmt.Sprintf("%s\\s+%s", service, status)
|
||||
r, _ := regexp.Compile(statusRegex)
|
||||
if !r.MatchString(res.Combined()) {
|
||||
return fmt.Errorf("Missing/incorrect ps output:\n%s", res.Combined())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) outputContains(substring string) error {
|
||||
if !strings.Contains(th.CommandOutput, substring) {
|
||||
return fmt.Errorf("Missing output substring: %s\noutput: %s", substring, th.CommandOutput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) exitCodeIs(exitCode int) error {
|
||||
if exitCode != th.CommandExitCode {
|
||||
return fmt.Errorf("Wrong exit code: %d expected: %d", th.CommandExitCode, exitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) runComposeCommand(command string) error {
|
||||
commandArgs := []string{"-f", "-"}
|
||||
commandArgs = append(commandArgs, strings.Split(command, " ")...)
|
||||
cmd := th.CLI.NewDockerComposeCmd(th.T, commandArgs...)
|
||||
cmd.Stdin = strings.NewReader(th.ComposeFile)
|
||||
res := icmd.RunCmd(cmd)
|
||||
th.CommandOutput = res.Combined()
|
||||
th.CommandExitCode = res.ExitCode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *testHelper) setComposeFile(composeString string) error {
|
||||
th.ComposeFile = composeString
|
||||
return nil
|
||||
}
|
||||
171
e2e/go.mod
Normal file
171
e2e/go.mod
Normal file
@@ -0,0 +1,171 @@
|
||||
module github.com/docker/compose/v2/e2e
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/docker/compose/v2 => ../
|
||||
|
||||
replace github.com/cucumber/godog => github.com/laurazard/godog v0.0.0-20220922095256-4c4b17abdae7
|
||||
|
||||
require (
|
||||
github.com/cucumber/godog v0.12.5
|
||||
github.com/docker/compose/v2 v2.11.1
|
||||
gotest.tools/v3 v3.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.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-to-oci v0.3.7 // indirect
|
||||
github.com/compose-spec/compose-go v1.6.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.6.8 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31 // indirect
|
||||
github.com/docker/buildx v0.9.1 // indirect
|
||||
github.com/docker/cli v20.10.19+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.19+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-memdb v1.3.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/buildkit v0.10.4 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 // indirect
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cobra v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.8.0 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/weppos/publicsuffix-go v0.20.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.24.1 // indirect
|
||||
k8s.io/apimachinery v0.24.1 // indirect
|
||||
k8s.io/client-go v0.24.1 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221013203545-33ab36d6b304+incompatible // 22.06 branch
|
||||
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
|
||||
// For k8s dependencies, we use a replace directive, to prevent them being
|
||||
// upgraded to the version specified in containerd, which is not relevant to the
|
||||
// version needed.
|
||||
// See https://github.com/docker/buildx/pull/948 for details.
|
||||
// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
|
||||
k8s.io/api => k8s.io/api v0.22.4
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||
)
|
||||
1041
e2e/go.sum
Normal file
1041
e2e/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
53
go.mod
53
go.mod
@@ -5,16 +5,15 @@ go 1.19
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/cnabio/cnab-go v0.24.1-0.20220907172316-1ca5c8721bf7
|
||||
github.com/cnabio/cnab-to-oci v0.3.7
|
||||
github.com/compose-spec/compose-go v1.5.1
|
||||
github.com/compose-spec/compose-go v1.6.0
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.8
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220902125104-0122d7ddaec0
|
||||
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/distribution/distribution/v3 v3.0.0-20220907155224-78b9c98c5c31
|
||||
github.com/docker/buildx v0.9.1 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.19+incompatible
|
||||
github.com/docker/cli-docs-tool v0.5.0
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/docker/docker v20.10.19+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/golang/mock v1.6.0
|
||||
@@ -26,17 +25,17 @@ require (
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae
|
||||
github.com/morikuni/aec 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.20220303224323-02efb9a75ee1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.3.0
|
||||
gotest.tools/v3 v3.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -45,7 +44,8 @@ require (
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8 // indirect
|
||||
github.com/cnabio/cnab-go v0.23.4 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -69,30 +69,30 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/signal v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220930225714-4638ad635be5 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
@@ -101,14 +101,14 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.10.0
|
||||
go.opentelemetry.io/otel v1.11.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.11.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
@@ -117,7 +117,7 @@ require (
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
@@ -133,9 +133,11 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||
github.com/zmap/zlint v1.1.0 // indirect
|
||||
@@ -146,8 +148,9 @@ require (
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20221013132413-1d6c6e2367e2+incompatible // 22.06 master branch
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20221013203545-33ab36d6b304+incompatible // 22.06 branch
|
||||
github.com/moby/buildkit => github.com/moby/buildkit v0.10.1-0.20220816171719-55ba9d14360a // same as buildx
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ type ConvertOptions struct {
|
||||
|
||||
// PushOptions group options of the Push API
|
||||
type PushOptions struct {
|
||||
Quiet bool
|
||||
IgnoreFailures bool
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
bclient "github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
@@ -81,6 +81,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
buildOptions.Exports = []bclient.ExportEntry{{
|
||||
Type: "docker",
|
||||
Attrs: map[string]string{
|
||||
"load": "true",
|
||||
},
|
||||
}}
|
||||
if len(buildOptions.Platforms) > 1 {
|
||||
buildOptions.Exports = []bclient.ExportEntry{{
|
||||
Type: "image",
|
||||
@@ -173,7 +179,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
|
||||
"load": "true",
|
||||
},
|
||||
}}
|
||||
if opt.Platforms, err = useDockerDefaultPlatform(project, service.Build.Platforms); err != nil {
|
||||
if opt.Platforms, err = useDockerDefaultOrServicePlatform(project, service, true); err != nil {
|
||||
opt.Platforms = []specs.Platform{}
|
||||
}
|
||||
opts[imageName] = opt
|
||||
@@ -243,7 +249,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
}
|
||||
|
||||
sessionConfig := []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(s.stderr()),
|
||||
authprovider.NewDockerAuthProvider(s.configFile()),
|
||||
}
|
||||
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
|
||||
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
|
||||
@@ -357,23 +363,11 @@ func addSecretsConfig(project *types.Project, service types.ServiceConfig) (sess
|
||||
}
|
||||
|
||||
func addPlatforms(project *types.Project, service types.ServiceConfig) ([]specs.Platform, error) {
|
||||
plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
|
||||
plats, err := useDockerDefaultOrServicePlatform(project, service, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
return nil, fmt.Errorf("service.platform should be part of the service.build.platforms: %q", service.Platform)
|
||||
}
|
||||
// User defined a service platform and no build platforms, so we should keep the one define on the service level
|
||||
p, err := platforms.Parse(service.Platform)
|
||||
if !utils.Contains(plats, p) {
|
||||
plats = append(plats, p)
|
||||
}
|
||||
return plats, err
|
||||
}
|
||||
|
||||
for _, buildPlatform := range service.Build.Platforms {
|
||||
p, err := platforms.Parse(buildPlatform)
|
||||
if err != nil {
|
||||
@@ -404,7 +398,7 @@ func useDockerDefaultPlatform(project *types.Project, platformList types.StringL
|
||||
var plats []specs.Platform
|
||||
if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {
|
||||
if len(platformList) > 0 && !utils.StringContains(platformList, platform) {
|
||||
return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: %q", platform)
|
||||
return nil, fmt.Errorf("the DOCKER_DEFAULT_PLATFORM %q value should be part of the service.build.platforms: %q", platform, platformList)
|
||||
}
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
@@ -414,3 +408,23 @@ func useDockerDefaultPlatform(project *types.Project, platformList types.StringL
|
||||
}
|
||||
return plats, nil
|
||||
}
|
||||
|
||||
func useDockerDefaultOrServicePlatform(project *types.Project, service types.ServiceConfig, useOnePlatform bool) ([]specs.Platform, error) {
|
||||
plats, err := useDockerDefaultPlatform(project, service.Build.Platforms)
|
||||
if (len(plats) > 0 && useOnePlatform) || err != nil {
|
||||
return plats, err
|
||||
}
|
||||
|
||||
if service.Platform != "" && !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
return nil, fmt.Errorf("service.platform %q should be part of the service.build.platforms: %q", service.Platform, service.Build.Platforms)
|
||||
}
|
||||
// User defined a service platform and no build platforms, so we should keep the one define on the service level
|
||||
p, err := platforms.Parse(service.Platform)
|
||||
if !utils.Contains(plats, p) {
|
||||
plats = append(plats, p)
|
||||
}
|
||||
return plats, err
|
||||
}
|
||||
return plats, nil
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
|
||||
dis := make([]build.DriverInfo, len(ng.Nodes))
|
||||
var f driver.Factory
|
||||
if ng.Driver != "" {
|
||||
factories := driver.GetFactories()
|
||||
factories := driver.GetFactories(true)
|
||||
for _, fac := range factories {
|
||||
if fac.Name() == ng.Driver {
|
||||
f = fac
|
||||
@@ -103,8 +103,8 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
|
||||
}
|
||||
}
|
||||
if f == nil {
|
||||
if f = driver.GetFactory(ng.Driver, true); f == nil {
|
||||
return nil, fmt.Errorf("failed to find buildx driver %q", ng.Driver)
|
||||
if f, err = driver.GetFactory(ng.Driver, true); f == nil || err != nil {
|
||||
return nil, fmt.Errorf("failed to find buildx driver %q, error: %w", ng.Driver, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -113,7 +113,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
|
||||
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func (s *composeService) getDrivers(ctx context.Context) ([]build.DriverInfo, er
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, "")
|
||||
if err != nil {
|
||||
di.Err = err
|
||||
return nil
|
||||
|
||||
@@ -30,13 +30,13 @@ import (
|
||||
buildx "github.com/docker/buildx/build"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -180,7 +181,10 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
// Scale Down
|
||||
container := container
|
||||
eg.Go(func() error {
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := c.service.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{
|
||||
Timeout: timeoutInSecond,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -412,7 +416,8 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
||||
var created moby.Container
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||
err := s.apiClient().ContainerStop(ctx, replaced.ID, timeout)
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
@@ -1155,7 +1155,7 @@ func (s *composeService) createVolume(ctx context.Context, volume types.VolumeCo
|
||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(eventName))
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volume_api.VolumeCreateBody{
|
||||
_, err := s.apiClient().VolumeCreate(ctx, volume_api.CreateOptions{
|
||||
Labels: volume.Labels,
|
||||
Name: volume.Name,
|
||||
Driver: volume.Driver,
|
||||
|
||||
@@ -37,38 +37,49 @@ const (
|
||||
ServiceStarted
|
||||
)
|
||||
|
||||
type graphTraversalConfig struct {
|
||||
type graphTraversal struct {
|
||||
mu sync.Mutex
|
||||
seen map[string]struct{}
|
||||
|
||||
extremityNodesFn func(*Graph) []*Vertex // leaves or roots
|
||||
adjacentNodesFn func(*Vertex) []*Vertex // getParents or getChildren
|
||||
filterAdjacentByStatusFn func(*Graph, string, ServiceStatus) []*Vertex // filterChildren or filterParents
|
||||
targetServiceStatus ServiceStatus
|
||||
adjacentServiceStatusToSkip ServiceStatus
|
||||
|
||||
visitorFn func(context.Context, string) error
|
||||
}
|
||||
|
||||
var (
|
||||
upDirectionTraversalConfig = graphTraversalConfig{
|
||||
func upDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
|
||||
return &graphTraversal{
|
||||
extremityNodesFn: leaves,
|
||||
adjacentNodesFn: getParents,
|
||||
filterAdjacentByStatusFn: filterChildren,
|
||||
adjacentServiceStatusToSkip: ServiceStopped,
|
||||
targetServiceStatus: ServiceStarted,
|
||||
visitorFn: visitorFn,
|
||||
}
|
||||
downDirectionTraversalConfig = graphTraversalConfig{
|
||||
}
|
||||
|
||||
func downDirectionTraversal(visitorFn func(context.Context, string) error) *graphTraversal {
|
||||
return &graphTraversal{
|
||||
extremityNodesFn: roots,
|
||||
adjacentNodesFn: getChildren,
|
||||
filterAdjacentByStatusFn: filterParents,
|
||||
adjacentServiceStatusToSkip: ServiceStarted,
|
||||
targetServiceStatus: ServiceStopped,
|
||||
visitorFn: visitorFn,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// InDependencyOrder applies the function to the services of the project taking in account the dependency order
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversalConfig)) error {
|
||||
func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, string) error, options ...func(*graphTraversal)) error {
|
||||
graph, err := NewGraph(project.Services, ServiceStopped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return visit(ctx, graph, upDirectionTraversalConfig, fn)
|
||||
t := upDirectionTraversal(fn)
|
||||
return t.visit(ctx, graph)
|
||||
}
|
||||
|
||||
// InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
|
||||
@@ -77,43 +88,59 @@ func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn fu
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return visit(ctx, graph, downDirectionTraversalConfig, fn)
|
||||
t := downDirectionTraversal(fn)
|
||||
return t.visit(ctx, graph)
|
||||
}
|
||||
|
||||
func visit(ctx context.Context, g *Graph, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
|
||||
nodes := traversalConfig.extremityNodesFn(g)
|
||||
func (t *graphTraversal) visit(ctx context.Context, g *Graph) error {
|
||||
nodes := t.extremityNodesFn(g)
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
eg.Go(func() error {
|
||||
return run(ctx, g, eg, nodes, traversalConfig, fn)
|
||||
})
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
t.run(ctx, g, eg, nodes)
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
// Note: this could be `graph.walk` or whatever
|
||||
func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, string) error) error {
|
||||
func (t *graphTraversal) run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex) {
|
||||
for _, node := range nodes {
|
||||
// Don't start this service yet if all of its children have
|
||||
// not been started yet.
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
if len(t.filterAdjacentByStatusFn(graph, node.Key, t.adjacentServiceStatusToSkip)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
node := node
|
||||
if !t.consume(node.Key) {
|
||||
// another worker already visited this node
|
||||
continue
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
err := fn(ctx, node.Service)
|
||||
err := t.visitorFn(ctx, node.Service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
|
||||
graph.UpdateStatus(node.Key, t.targetServiceStatus)
|
||||
|
||||
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
|
||||
t.run(ctx, graph, eg, t.adjacentNodesFn(node))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (t *graphTraversal) consume(nodeKey string) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.seen == nil {
|
||||
t.seen = make(map[string]struct{})
|
||||
}
|
||||
if _, ok := t.seen[nodeKey]; ok {
|
||||
return false
|
||||
}
|
||||
t.seen[nodeKey] = struct{}{}
|
||||
return true
|
||||
}
|
||||
|
||||
// Graph represents project as service dependencies
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
@@ -46,6 +47,51 @@ var project = types.Project{
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraversalWithMultipleParents(t *testing.T) {
|
||||
dependent := types.ServiceConfig{
|
||||
Name: "dependent",
|
||||
DependsOn: make(types.DependsOnConfig),
|
||||
}
|
||||
|
||||
project := types.Project{
|
||||
Services: []types.ServiceConfig{dependent},
|
||||
}
|
||||
|
||||
for i := 1; i <= 100; i++ {
|
||||
name := fmt.Sprintf("svc_%d", i)
|
||||
dependent.DependsOn[name] = types.ServiceDependency{}
|
||||
|
||||
svc := types.ServiceConfig{Name: name}
|
||||
project.Services = append(project.Services, svc)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
svc := make(chan string, 10)
|
||||
seen := make(map[string]int)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for service := range svc {
|
||||
seen[service]++
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
err := InDependencyOrder(ctx, &project, func(ctx context.Context, service string) error {
|
||||
svc <- service
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err, "Error during iteration")
|
||||
close(svc)
|
||||
<-done
|
||||
|
||||
testify.Len(t, seen, 101)
|
||||
for svc, count := range seen {
|
||||
assert.Equal(t, 1, count, "Service: %s", svc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInDependencyUpCommandOrder(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
@@ -22,8 +22,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
@@ -241,7 +244,8 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.StoppingEvent(eventName))
|
||||
err := s.apiClient().ContainerStop(ctx, container.ID, timeout)
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, container.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||
if err != nil {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
|
||||
return err
|
||||
@@ -270,7 +274,7 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
Force: true,
|
||||
RemoveVolumes: volumes,
|
||||
})
|
||||
if err != nil {
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/errdefs"
|
||||
@@ -53,7 +54,7 @@ func TestDown(t *testing.T) {
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
Return(volume.ListResponse{}, nil)
|
||||
|
||||
// network names are not guaranteed to be unique, ensure Compose handles
|
||||
// cleanup properly if duplicates are inadvertently created
|
||||
@@ -63,9 +64,10 @@ func TestDown(t *testing.T) {
|
||||
{ID: "def456", Name: "myProject_default"},
|
||||
}, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
||||
stopOptions := containerType.StopOptions{}
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", stopOptions).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
|
||||
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
@@ -102,13 +104,14 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
Return(volume.ListResponse{}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return([]moby.NetworkResource{{Name: "myProject_default"}}, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "321", nil).Return(nil)
|
||||
stopOptions := containerType.StopOptions{}
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopOptions).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", stopOptions).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "321", stopOptions).Return(nil)
|
||||
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
@@ -137,13 +140,13 @@ func TestDownRemoveVolumes(t *testing.T) {
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt(false)).Return(
|
||||
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{
|
||||
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
||||
Return(volume.ListResponse{
|
||||
Volumes: []*volume.Volume{{Name: "myProject_volume"}},
|
||||
}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return(nil, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", containerType.StopOptions{}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
|
||||
|
||||
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
|
||||
@@ -274,8 +277,8 @@ func TestDownRemoveImages_NoLabel(t *testing.T) {
|
||||
[]moby.Container{container}, nil)
|
||||
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{
|
||||
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
||||
Return(volume.ListResponse{
|
||||
Volumes: []*volume.Volume{{Name: "myProject_volume"}},
|
||||
}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return(nil, nil)
|
||||
@@ -292,7 +295,7 @@ func TestDownRemoveImages_NoLabel(t *testing.T) {
|
||||
api.EXPECT().ImageInspectWithRaw(gomock.Any(), "testproject-service1").
|
||||
Return(moby.ImageInspect{}, nil, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", containerType.StopOptions{}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().ImageRemove(gomock.Any(), "testproject-service1:latest", moby.ImageRemoveOptions{}).Return(nil, nil)
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestKillAll(t *testing.T) {
|
||||
}).Return(
|
||||
[]moby.Container{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
Return(volume.ListResponse{}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return([]moby.NetworkResource{
|
||||
{ID: "abc123", Name: "testProject_default"},
|
||||
@@ -87,7 +87,7 @@ func TestKillSignal(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, listOptions).Return([]moby.Container{testContainer(serviceName, "123", false)}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
Return(volume.ListResponse{}, nil)
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).
|
||||
Return([]moby.NetworkResource{
|
||||
{ID: "abc123", Name: "testProject_default"},
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *composeService) Port(ctx context.Context, projectName string, service s
|
||||
return "", 0, err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return "", 0, fmt.Errorf("no container found for %s_%d", service, options.Index)
|
||||
return "", 0, fmt.Errorf("no container found for %s%s%d", service, api.Separator, options.Index)
|
||||
}
|
||||
container := list[0]
|
||||
for _, p := range container.Ports {
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestPs(t *testing.T) {
|
||||
c2, inspect2 := containerDetails("service1", "456", "running", "", 0)
|
||||
c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
|
||||
c3, inspect3 := containerDetails("service2", "789", "exited", "", 130)
|
||||
api.EXPECT().VolumeList(ctx, gomock.Any()).Return(volume.VolumeListOKBody{}, nil)
|
||||
api.EXPECT().VolumeList(ctx, gomock.Any()).Return(volume.ListResponse{}, 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().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
|
||||
|
||||
@@ -37,6 +37,9 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Push(ctx context.Context, project *types.Project, options api.PushOptions) error {
|
||||
if options.Quiet {
|
||||
return s.push(ctx, project, options)
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.push(ctx, project, options)
|
||||
})
|
||||
@@ -65,7 +68,7 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
|
||||
}
|
||||
service := service
|
||||
eg.Go(func() error {
|
||||
err := s.pushServiceImage(ctx, service, info, s.configFile(), w)
|
||||
err := s.pushServiceImage(ctx, service, info, s.configFile(), w, options.Quiet)
|
||||
if err != nil {
|
||||
if !options.IgnoreFailures {
|
||||
return err
|
||||
@@ -78,7 +81,7 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer) error {
|
||||
func (s *composeService) pushServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
|
||||
ref, err := reference.ParseNormalizedNamed(service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -121,7 +124,10 @@ func (s *composeService) pushServiceImage(ctx context.Context, service types.Ser
|
||||
if jm.Error != nil {
|
||||
return errors.New(jm.Error.Message)
|
||||
}
|
||||
toPushProgressEvent(service.Name, jm, w)
|
||||
|
||||
if !quietPush {
|
||||
toPushProgressEvent(service.Name, jm, w)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
|
||||
@@ -62,7 +62,8 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(container)
|
||||
w.Event(progress.RestartingEvent(eventName))
|
||||
err := s.apiClient().ContainerRestart(ctx, container.ID, options.Timeout)
|
||||
timeout := utils.DurationSecondToInt(options.Timeout)
|
||||
err := s.apiClient().ContainerRestart(ctx, container.ID, containerType.StopOptions{Timeout: timeout})
|
||||
if err == nil {
|
||||
w.Event(progress.StartedEvent(eventName))
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
||||
}
|
||||
if opts.Entrypoint != nil {
|
||||
service.Entrypoint = opts.Entrypoint
|
||||
if len(opts.Command) == 0 {
|
||||
service.Command = []string{}
|
||||
}
|
||||
}
|
||||
if len(opts.Environment) > 0 {
|
||||
cmdEnv := types.NewMappingWithEquals(opts.Environment)
|
||||
|
||||
@@ -22,8 +22,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -51,14 +54,15 @@ func TestStopTimeout(t *testing.T) {
|
||||
testContainer("service2", "789", false),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
Return(volume.ListResponse{}, 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
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", &timeout).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
|
||||
timeout := 2 * time.Second
|
||||
stopConfig := containerType.StopOptions{Timeout: utils.DurationSecondToInt(&timeout)}
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", stopConfig).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", stopConfig).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", stopConfig).Return(nil)
|
||||
|
||||
err := tested.Stop(ctx, strings.ToLower(testProject), compose.StopOptions{
|
||||
Timeout: &timeout,
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
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)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package e2e
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -85,6 +86,51 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
|
||||
})
|
||||
|
||||
t.Run("build as part of up", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
|
||||
})
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
|
||||
|
||||
output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, "Hello from Nginx container"))
|
||||
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
|
||||
c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
|
||||
})
|
||||
|
||||
t.Run("no rebuild when up again", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("rebuild when up --build", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "up", "-d", "--build")
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
|
||||
})
|
||||
|
||||
t.Run("cleanup build project", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerCmd(t, "rmi", "build-test-nginx")
|
||||
c.RunDockerCmd(t, "rmi", "custom-nginx")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildSSH(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Running on Windows. Skipping...")
|
||||
}
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build failed with ssh default value", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test", "build", "--ssh", "")
|
||||
res.Assert(t, icmd.Expected{
|
||||
@@ -130,47 +176,12 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
})
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-ssh")
|
||||
})
|
||||
|
||||
t.Run("build as part of up", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
|
||||
})
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
|
||||
|
||||
output := HTTPGetWithRetry(t, "http://localhost:8070", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, "Hello from Nginx container"))
|
||||
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
|
||||
c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
|
||||
})
|
||||
|
||||
t.Run("no rebuild when up again", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "COPY static"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("rebuild when up --build", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "up", "-d", "--build")
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
|
||||
})
|
||||
|
||||
t.Run("cleanup build project", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerCmd(t, "rmi", "build-test-nginx")
|
||||
c.RunDockerCmd(t, "rmi", "custom-nginx")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildSecrets(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping test on windows")
|
||||
}
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build with secrets", func(t *testing.T) {
|
||||
@@ -259,6 +270,9 @@ func TestBuildImageDependencies(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Running on Windows. Skipping...")
|
||||
}
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
// declare builder
|
||||
@@ -352,7 +366,7 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
|
||||
"-f", "fixtures/build-test/platforms/compose-service-platform-not-in-build-platforms.yaml", "build")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: `service.platform should be part of the service.build.platforms: "linux/riscv64"`,
|
||||
Err: `service.platform "linux/riscv64" should be part of the service.build.platforms: ["linux/amd64" "linux/arm64"]`,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -363,7 +377,7 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
|
||||
})
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: `DOCKER_DEFAULT_PLATFORM value should be part of the service.build.platforms: "windows/amd64"`,
|
||||
Err: `DOCKER_DEFAULT_PLATFORM "windows/amd64" value should be part of the service.build.platforms: ["linux/amd64" "linux/arm64"]`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
@@ -41,8 +42,12 @@ func TestComposeCancel(t *testing.T) {
|
||||
|
||||
// require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal.
|
||||
// sending kill signal
|
||||
cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build",
|
||||
"--progress", "plain"))
|
||||
stdout := &utils.SafeBuffer{}
|
||||
stderr := &utils.SafeBuffer{}
|
||||
cmd, err := StartWithNewGroupID(context.Background(),
|
||||
c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build", "--progress", "plain"),
|
||||
stdout,
|
||||
stderr)
|
||||
assert.NilError(t, err)
|
||||
|
||||
c.WaitForCondition(t, func() (bool, string) {
|
||||
@@ -65,15 +70,16 @@ func TestComposeCancel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func StartWithNewGroupID(command icmd.Cmd) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
|
||||
cmd := exec.Command(command.Command[0], command.Command[1:]...)
|
||||
func StartWithNewGroupID(ctx context.Context, command icmd.Cmd, stdout *utils.SafeBuffer, stderr *utils.SafeBuffer) (*exec.Cmd, error) {
|
||||
cmd := exec.CommandContext(ctx, command.Command[0], command.Command[1:]...)
|
||||
cmd.Env = command.Env
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if stdout != nil {
|
||||
cmd.Stdout = stdout
|
||||
}
|
||||
if stderr != nil {
|
||||
cmd.Stderr = stderr
|
||||
}
|
||||
err := cmd.Start()
|
||||
return cmd, &stdout, &stderr, err
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestCascadeStop(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "e2e-cascade-stop"
|
||||
|
||||
t.Run("abort-on-container-exit", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Out: `should_fail-1 exited with code 1`})
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Out: `Aborting on container exit...`})
|
||||
})
|
||||
|
||||
t.Run("exit-code-from", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=sleep")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 137, Out: `should_fail-1 exited with code 1`})
|
||||
res.Assert(t, icmd.Expected{ExitCode: 137, Out: `Aborting on container exit...`})
|
||||
})
|
||||
|
||||
t.Run("exit-code-from unknown", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=unknown")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `no such service: unknown`})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
@@ -135,6 +135,9 @@ func TestDownComposefileInParentFolder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttachRestart(t *testing.T) {
|
||||
if _, ok := os.LookupEnv("CI"); ok {
|
||||
t.Skip("Skipping test on CI... flaky")
|
||||
}
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/attach-restart", "up")
|
||||
@@ -146,7 +149,7 @@ func TestAttachRestart(t *testing.T) {
|
||||
return strings.Count(res.Stdout(),
|
||||
"failing-1 exited with code 1") == 3, fmt.Sprintf("'failing-1 exited with code 1' not found 3 times in : \n%s\n",
|
||||
debug)
|
||||
}, 2*time.Minute, 2*time.Second)
|
||||
}, 4*time.Minute, 2*time.Second)
|
||||
|
||||
assert.Equal(t, strings.Count(res.Stdout(), "failing-1 | world"), 3, res.Combined())
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
services:
|
||||
should_fail:
|
||||
image: alpine
|
||||
command: ls /does_not_exist
|
||||
sleep: # will be killed
|
||||
image: alpine
|
||||
command: ping localhost
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -29,6 +30,9 @@ import (
|
||||
)
|
||||
|
||||
func TestPause(t *testing.T) {
|
||||
if _, ok := os.LookupEnv("CI"); ok {
|
||||
t.Skip("Skipping test on CI... flaky")
|
||||
}
|
||||
cli := NewParallelCLI(t, WithEnv(
|
||||
"COMPOSE_PROJECT_NAME=e2e-pause",
|
||||
"COMPOSE_FILE=./fixtures/pause/compose.yaml"))
|
||||
@@ -46,7 +50,7 @@ func TestPause(t *testing.T) {
|
||||
"b": urlForService(t, cli, "b", 80),
|
||||
}
|
||||
for _, url := range urls {
|
||||
HTTPGetWithRetry(t, url, http.StatusOK, 50*time.Millisecond, 5*time.Second)
|
||||
HTTPGetWithRetry(t, url, http.StatusOK, 50*time.Millisecond, 20*time.Second)
|
||||
}
|
||||
|
||||
// pause a and verify that it can no longer be hit but b still can
|
||||
@@ -98,7 +102,7 @@ func TestPauseServiceAlreadyPaused(t *testing.T) {
|
||||
|
||||
// launch a and wait for it to come up
|
||||
cli.RunDockerComposeCmd(t, "up", "-d", "a")
|
||||
HTTPGetWithRetry(t, urlForService(t, cli, "a", 80), http.StatusOK, 50*time.Millisecond, 5*time.Second)
|
||||
HTTPGetWithRetry(t, urlForService(t, cli, "a", 80), http.StatusOK, 50*time.Millisecond, 10*time.Second)
|
||||
|
||||
// pause a twice - first time should pass, second time fail
|
||||
cli.RunDockerComposeCmd(t, "pause", "a")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2022 Docker Compose CLI authors
|
||||
|
||||
@@ -23,6 +26,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/icmd"
|
||||
@@ -49,9 +53,6 @@ func TestUpDependenciesNotStopped(t *testing.T) {
|
||||
reset()
|
||||
t.Cleanup(reset)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
t.Log("Launching orphan container (background)")
|
||||
c.RunDockerComposeCmd(t,
|
||||
"-f=./fixtures/ups-deps-stop/orphan.yaml",
|
||||
@@ -63,22 +64,18 @@ func TestUpDependenciesNotStopped(t *testing.T) {
|
||||
RequireServiceState(t, c, "orphan", "running")
|
||||
|
||||
t.Log("Launching app container with implicit dependency")
|
||||
var upOut lockedBuffer
|
||||
var upCmd *exec.Cmd
|
||||
go func() {
|
||||
testCmd := c.NewDockerComposeCmd(t,
|
||||
"-f=./fixtures/ups-deps-stop/compose.yaml",
|
||||
"up",
|
||||
"app",
|
||||
)
|
||||
cmd := exec.CommandContext(ctx, testCmd.Command[0], testCmd.Command[1:]...)
|
||||
cmd.Env = testCmd.Env
|
||||
cmd.Stdout = &upOut
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
upOut := &utils.SafeBuffer{}
|
||||
testCmd := c.NewDockerComposeCmd(t,
|
||||
"-f=./fixtures/ups-deps-stop/compose.yaml",
|
||||
"up",
|
||||
"app",
|
||||
)
|
||||
|
||||
assert.NoError(t, cmd.Start(), "Failed to run compose up")
|
||||
upCmd = cmd
|
||||
}()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
cmd, err := StartWithNewGroupID(ctx, testCmd, upOut, nil)
|
||||
assert.NoError(t, err, "Failed to run compose up")
|
||||
|
||||
t.Log("Waiting for containers to be in running state")
|
||||
upOut.RequireEventuallyContains(t, "hello app")
|
||||
@@ -86,13 +83,13 @@ func TestUpDependenciesNotStopped(t *testing.T) {
|
||||
RequireServiceState(t, c, "dependency", "running")
|
||||
|
||||
t.Log("Simulating Ctrl-C")
|
||||
require.NoError(t, syscall.Kill(-upCmd.Process.Pid, syscall.SIGINT),
|
||||
require.NoError(t, syscall.Kill(-cmd.Process.Pid, syscall.SIGINT),
|
||||
"Failed to send SIGINT to compose up process")
|
||||
|
||||
time.AfterFunc(5*time.Second, cancel)
|
||||
|
||||
t.Log("Waiting for `compose up` to exit")
|
||||
err := upCmd.Wait()
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
exitErr := err.(*exec.ExitError)
|
||||
require.EqualValues(t, exitErr.ExitCode(), 130)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -99,6 +100,9 @@ func TestProjectVolumeBind(t *testing.T) {
|
||||
const projectName = "compose-e2e-project-volume-bind"
|
||||
|
||||
t.Run("up on project volume with bind specification", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Running on Windows. Skipping...")
|
||||
}
|
||||
tmpDir, err := os.MkdirTemp("", projectName)
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tmpDir) //nolint
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
net "net"
|
||||
http "net/http"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
types "github.com/docker/docker/api/types"
|
||||
container "github.com/docker/docker/api/types/container"
|
||||
@@ -253,10 +252,10 @@ func (mr *MockAPIClientMockRecorder) ContainerCommit(arg0, arg1, arg2 interface{
|
||||
}
|
||||
|
||||
// ContainerCreate mocks base method.
|
||||
func (m *MockAPIClient) ContainerCreate(arg0 context.Context, arg1 *container.Config, arg2 *container.HostConfig, arg3 *network.NetworkingConfig, arg4 *v1.Platform, arg5 string) (container.ContainerCreateCreatedBody, error) {
|
||||
func (m *MockAPIClient) ContainerCreate(arg0 context.Context, arg1 *container.Config, arg2 *container.HostConfig, arg3 *network.NetworkingConfig, arg4 *v1.Platform, arg5 string) (container.CreateResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContainerCreate", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].(container.ContainerCreateCreatedBody)
|
||||
ret0, _ := ret[0].(container.CreateResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -502,7 +501,7 @@ func (mr *MockAPIClientMockRecorder) ContainerResize(arg0, arg1, arg2 interface{
|
||||
}
|
||||
|
||||
// ContainerRestart mocks base method.
|
||||
func (m *MockAPIClient) ContainerRestart(arg0 context.Context, arg1 string, arg2 *time.Duration) error {
|
||||
func (m *MockAPIClient) ContainerRestart(arg0 context.Context, arg1 string, arg2 container.StopOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContainerRestart", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
@@ -575,7 +574,7 @@ func (mr *MockAPIClientMockRecorder) ContainerStatsOneShot(arg0, arg1 interface{
|
||||
}
|
||||
|
||||
// ContainerStop mocks base method.
|
||||
func (m *MockAPIClient) ContainerStop(arg0 context.Context, arg1 string, arg2 *time.Duration) error {
|
||||
func (m *MockAPIClient) ContainerStop(arg0 context.Context, arg1 string, arg2 container.StopOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContainerStop", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
@@ -633,10 +632,10 @@ func (mr *MockAPIClientMockRecorder) ContainerUpdate(arg0, arg1, arg2 interface{
|
||||
}
|
||||
|
||||
// ContainerWait mocks base method.
|
||||
func (m *MockAPIClient) ContainerWait(arg0 context.Context, arg1 string, arg2 container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
func (m *MockAPIClient) ContainerWait(arg0 context.Context, arg1 string, arg2 container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContainerWait", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(<-chan container.ContainerWaitOKBody)
|
||||
ret0, _ := ret[0].(<-chan container.WaitResponse)
|
||||
ret1, _ := ret[1].(<-chan error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -1723,10 +1722,10 @@ func (mr *MockAPIClientMockRecorder) TaskLogs(arg0, arg1, arg2 interface{}) *gom
|
||||
}
|
||||
|
||||
// VolumeCreate mocks base method.
|
||||
func (m *MockAPIClient) VolumeCreate(arg0 context.Context, arg1 volume.VolumeCreateBody) (types.Volume, error) {
|
||||
func (m *MockAPIClient) VolumeCreate(arg0 context.Context, arg1 volume.CreateOptions) (volume.Volume, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VolumeCreate", arg0, arg1)
|
||||
ret0, _ := ret[0].(types.Volume)
|
||||
ret0, _ := ret[0].(volume.Volume)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -1738,10 +1737,10 @@ func (mr *MockAPIClientMockRecorder) VolumeCreate(arg0, arg1 interface{}) *gomoc
|
||||
}
|
||||
|
||||
// VolumeInspect mocks base method.
|
||||
func (m *MockAPIClient) VolumeInspect(arg0 context.Context, arg1 string) (types.Volume, error) {
|
||||
func (m *MockAPIClient) VolumeInspect(arg0 context.Context, arg1 string) (volume.Volume, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VolumeInspect", arg0, arg1)
|
||||
ret0, _ := ret[0].(types.Volume)
|
||||
ret0, _ := ret[0].(volume.Volume)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -1753,10 +1752,10 @@ func (mr *MockAPIClientMockRecorder) VolumeInspect(arg0, arg1 interface{}) *gomo
|
||||
}
|
||||
|
||||
// VolumeInspectWithRaw mocks base method.
|
||||
func (m *MockAPIClient) VolumeInspectWithRaw(arg0 context.Context, arg1 string) (types.Volume, []byte, error) {
|
||||
func (m *MockAPIClient) VolumeInspectWithRaw(arg0 context.Context, arg1 string) (volume.Volume, []byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VolumeInspectWithRaw", arg0, arg1)
|
||||
ret0, _ := ret[0].(types.Volume)
|
||||
ret0, _ := ret[0].(volume.Volume)
|
||||
ret1, _ := ret[1].([]byte)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
@@ -1769,10 +1768,10 @@ func (mr *MockAPIClientMockRecorder) VolumeInspectWithRaw(arg0, arg1 interface{}
|
||||
}
|
||||
|
||||
// VolumeList mocks base method.
|
||||
func (m *MockAPIClient) VolumeList(arg0 context.Context, arg1 filters.Args) (volume.VolumeListOKBody, error) {
|
||||
func (m *MockAPIClient) VolumeList(arg0 context.Context, arg1 filters.Args) (volume.ListResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VolumeList", arg0, arg1)
|
||||
ret0, _ := ret[0].(volume.VolumeListOKBody)
|
||||
ret0, _ := ret[0].(volume.ListResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -1797,6 +1796,20 @@ func (mr *MockAPIClientMockRecorder) VolumeRemove(arg0, arg1, arg2 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeRemove", reflect.TypeOf((*MockAPIClient)(nil).VolumeRemove), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// VolumeUpdate mocks base method.
|
||||
func (m *MockAPIClient) VolumeUpdate(arg0 context.Context, arg1 string, arg2 swarm.Version, arg3 volume.UpdateOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "VolumeUpdate", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// VolumeUpdate indicates an expected call of VolumeUpdate.
|
||||
func (mr *MockAPIClientMockRecorder) VolumeUpdate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeUpdate", reflect.TypeOf((*MockAPIClient)(nil).VolumeUpdate), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// VolumesPrune mocks base method.
|
||||
func (m *MockAPIClient) VolumesPrune(arg0 context.Context, arg1 filters.Args) (types.VolumesPruneReport, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -91,20 +91,6 @@ func (mr *MockCliMockRecorder) Client() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockCli)(nil).Client))
|
||||
}
|
||||
|
||||
// ClientInfo mocks base method.
|
||||
func (m *MockCli) ClientInfo() command.ClientInfo {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClientInfo")
|
||||
ret0, _ := ret[0].(command.ClientInfo)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClientInfo indicates an expected call of ClientInfo.
|
||||
func (mr *MockCliMockRecorder) ClientInfo() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientInfo", reflect.TypeOf((*MockCli)(nil).ClientInfo))
|
||||
}
|
||||
|
||||
// ConfigFile mocks base method.
|
||||
func (m *MockCli) ConfigFile() *configfile.ConfigFile {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -405,15 +405,15 @@ func (m *MockLogConsumer) EXPECT() *MockLogConsumerMockRecorder {
|
||||
}
|
||||
|
||||
// Log mocks base method.
|
||||
func (m *MockLogConsumer) Log(service, container, message string) {
|
||||
func (m *MockLogConsumer) Log(containerName, service, message string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Log", service, container, message)
|
||||
m.ctrl.Call(m, "Log", containerName, service, message)
|
||||
}
|
||||
|
||||
// Log indicates an expected call of Log.
|
||||
func (mr *MockLogConsumerMockRecorder) Log(service, container, message interface{}) *gomock.Call {
|
||||
func (mr *MockLogConsumerMockRecorder) Log(containerName, service, message interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockLogConsumer)(nil).Log), service, container, message)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Log", reflect.TypeOf((*MockLogConsumer)(nil).Log), containerName, service, message)
|
||||
}
|
||||
|
||||
// Register mocks base method.
|
||||
|
||||
@@ -14,21 +14,14 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
import "time"
|
||||
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "e2e-down"
|
||||
|
||||
t.Run("no resource to remove", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0, Err: `No resource found to remove for project "e2e-down"`})
|
||||
})
|
||||
func DurationSecondToInt(d *time.Duration) *int {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
timeout := int(d.Seconds())
|
||||
return &timeout
|
||||
}
|
||||
78
pkg/utils/safebuffer.go
Normal file
78
pkg/utils/safebuffer.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// SafeBuffer is a thread safe version of bytes.Buffer
|
||||
type SafeBuffer struct {
|
||||
m sync.RWMutex
|
||||
b bytes.Buffer
|
||||
}
|
||||
|
||||
// Read is a thread safe version of bytes.Buffer::Read
|
||||
func (b *SafeBuffer) Read(p []byte) (n int, err error) {
|
||||
b.m.RLock()
|
||||
defer b.m.RUnlock()
|
||||
return b.b.Read(p)
|
||||
}
|
||||
|
||||
// Write is a thread safe version of bytes.Buffer::Write
|
||||
func (b *SafeBuffer) Write(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
return b.b.Write(p)
|
||||
}
|
||||
|
||||
// String is a thread safe version of bytes.Buffer::String
|
||||
func (b *SafeBuffer) String() string {
|
||||
b.m.RLock()
|
||||
defer b.m.RUnlock()
|
||||
return b.b.String()
|
||||
}
|
||||
|
||||
// Bytes is a thread safe version of bytes.Buffer::Bytes
|
||||
func (b *SafeBuffer) Bytes() []byte {
|
||||
b.m.RLock()
|
||||
defer b.m.RUnlock()
|
||||
return b.b.Bytes()
|
||||
}
|
||||
|
||||
// RequireEventuallyContains is a thread safe eventual checker for the buffer content
|
||||
func (b *SafeBuffer) RequireEventuallyContains(t testing.TB, v string) {
|
||||
t.Helper()
|
||||
var bufContents strings.Builder
|
||||
require.Eventuallyf(t, func() bool {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
if _, err := b.b.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)
|
||||
}
|
||||
112
verify-go-modules.sh
Executable file
112
verify-go-modules.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright The containerd 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.
|
||||
|
||||
#
|
||||
# verifies if the require and replace directives for two go.mod files are in sync
|
||||
#
|
||||
set -eu -o pipefail
|
||||
|
||||
ROOT=$(dirname "${BASH_SOURCE}")
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 dir-for-second-go-mod"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null ; then
|
||||
echo Please install jq
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load the requires and replaces section in the root go.mod file
|
||||
declare -A map_requires_1
|
||||
declare -A map_replaces_1
|
||||
pushd "${ROOT}" > /dev/null
|
||||
while IFS='#' read -r key value
|
||||
do
|
||||
map_requires_1[$key]="$value"
|
||||
done<<<$(go mod edit -json | jq -r '.Require[] | .Path + " # " + .Version')
|
||||
while IFS='#' read -r key value
|
||||
do
|
||||
[ "$key" = "" ] || map_replaces_1[$key]="$value"
|
||||
done<<<$(go mod edit -json | jq -r 'try .Replace[] | .Old.Path + " # " + .New.Path + " : " + .New.Version')
|
||||
popd > /dev/null
|
||||
|
||||
# Load the requires and replaces section in the other go.mod file
|
||||
declare -A map_requires_2
|
||||
declare -A map_replaces_2
|
||||
pushd "${ROOT}/$1" > /dev/null
|
||||
while IFS='#' read -r key value
|
||||
do
|
||||
[ "$key" = "" ] || map_requires_2[$key]="$value"
|
||||
done<<<$(go mod edit -json | jq -r '.Require[] | .Path + " # " + .Version')
|
||||
while IFS='#' read -r key value
|
||||
do
|
||||
map_replaces_2[$key]="$value"
|
||||
done<<<$(go mod edit -json | jq -r 'try .Replace[] | .Old.Path + " # " + .New.Path + " : " + .New.Version')
|
||||
popd > /dev/null
|
||||
|
||||
# signal for errors later
|
||||
ERRORS=0
|
||||
|
||||
# iterate through the second go.mod's require section and ensure that all items
|
||||
# have the same values in the root go.mod replace section
|
||||
for k in "${!map_requires_2[@]}"
|
||||
do
|
||||
if [ -v "map_requires_1[$k]" ]; then
|
||||
if [ "${map_requires_2[$k]}" != "${map_requires_1[$k]}" ]; then
|
||||
echo "${k} has different values in the go.mod files require section:" \
|
||||
"${map_requires_1[$k]} in root go.mod ${map_requires_2[$k]} in $1/go.mod"
|
||||
ERRORS=$(( ERRORS + 1 ))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# iterate through the second go.mod's replace section and ensure that all items
|
||||
# have the same values in the root go.mod's replace section. Except for the
|
||||
# containerd/containerd which we know will be different
|
||||
for k in "${!map_replaces_2[@]}"
|
||||
do
|
||||
if [[ "${k}" == "github.com/docker/compose"* ]]; then
|
||||
continue
|
||||
fi
|
||||
if [ -v "map_replaces_1[$k]" ]; then
|
||||
if [ "${map_replaces_2[$k]}" != "${map_replaces_1[$k]}" ]; then
|
||||
echo "${k} has different values in the go.mod files replace section:" \
|
||||
"${map_replaces_1[$k]} in root go.mod ${map_replaces_2[$k]} in $1/go.mod"
|
||||
ERRORS=$(( ERRORS + 1 ))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# iterate through the root go.mod's replace section and ensure that all the
|
||||
# same items are present in the second go.mod's replace section and nothing is missing
|
||||
for k in "${!map_replaces_1[@]}"
|
||||
do
|
||||
if [[ "${k}" == "github.com/docker/compose"* ]]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -v "map_replaces_2[$k]" ]; then
|
||||
echo "${k} has an entry in root go.mod replace section, but is missing from" \
|
||||
" replace section in $1/go.mod"
|
||||
ERRORS=$(( ERRORS + 1 ))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$ERRORS" -ne 0 ]; then
|
||||
echo "Found $ERRORS error(s)."
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user