mirror of
https://github.com/docker/compose.git
synced 2026-02-14 20:49:24 +08:00
Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68cf8d5ca0 | ||
|
|
94d6dff89d | ||
|
|
e5dcb8a8f8 | ||
|
|
ab25aabfb9 | ||
|
|
7f441eb013 | ||
|
|
4270987d7c | ||
|
|
3248b7b335 | ||
|
|
4ea3ba77b2 | ||
|
|
9622395c8a | ||
|
|
bec4ea4ab0 | ||
|
|
3f10753178 | ||
|
|
152c2d9a33 | ||
|
|
a261682ca8 | ||
|
|
71b89c2c1b | ||
|
|
ccd87311e8 | ||
|
|
49fb4ca25f | ||
|
|
3ae6c52e8a | ||
|
|
1c41df8f56 | ||
|
|
ea8341865d | ||
|
|
de0f23315b | ||
|
|
d9065050fd | ||
|
|
005fc25823 | ||
|
|
8a5d555de7 | ||
|
|
a48f1e8c13 | ||
|
|
57975094cc | ||
|
|
80b7a8d274 | ||
|
|
e111b651b3 | ||
|
|
638d78516a | ||
|
|
8862f95858 | ||
|
|
c2533e2341 | ||
|
|
08d8fddb8d | ||
|
|
5bd1a9bda3 | ||
|
|
ebb45b400c | ||
|
|
78605e58b2 | ||
|
|
0ea3504fd5 | ||
|
|
c83133f73b | ||
|
|
0f6366afac | ||
|
|
26554884db | ||
|
|
a9d3bfdacd | ||
|
|
0275fac355 | ||
|
|
fc5d7a45e0 | ||
|
|
9465e433fc | ||
|
|
5b02eadb74 | ||
|
|
331930c37d | ||
|
|
6e15989ec8 | ||
|
|
b2c0d25005 | ||
|
|
3599fc8533 | ||
|
|
919f351b4b | ||
|
|
d5528f3a54 | ||
|
|
c64dbbca6c | ||
|
|
e90c6ba332 | ||
|
|
51b4651ea9 | ||
|
|
d2639a8638 | ||
|
|
81182fca53 | ||
|
|
6e7c949787 | ||
|
|
7f32f02817 | ||
|
|
335decceda | ||
|
|
28c0fbfdc0 | ||
|
|
cacff89cef | ||
|
|
6756732fe4 | ||
|
|
67c13cf821 | ||
|
|
dbafb02377 | ||
|
|
a1b3f95709 | ||
|
|
5b6b674da9 | ||
|
|
31d9490a0b | ||
|
|
6b71073ae2 | ||
|
|
e806acce88 | ||
|
|
71600a52bf | ||
|
|
285a9c94f7 | ||
|
|
22194f6ef7 | ||
|
|
e51fd0a844 | ||
|
|
b961d49859 | ||
|
|
9cae9eb0fe | ||
|
|
8d03e29994 | ||
|
|
7ee7becd01 | ||
|
|
97d46a14ef | ||
|
|
a5a1c5f2f1 | ||
|
|
9db90b35bb | ||
|
|
a2770b66ff | ||
|
|
48b150beff | ||
|
|
674aa6a1e4 | ||
|
|
7e3564b7ad | ||
|
|
65b827d08f | ||
|
|
84f2168f80 | ||
|
|
a603e27117 | ||
|
|
6d9d75406c | ||
|
|
a964d5587b | ||
|
|
a983cf551d | ||
|
|
2f47e4582c | ||
|
|
78b06764a1 | ||
|
|
710b637e4a | ||
|
|
4cebef1bf1 | ||
|
|
d89c143c39 | ||
|
|
028cb4dd89 | ||
|
|
147c2d8fae | ||
|
|
69e21d89f0 | ||
|
|
41b3967cb5 | ||
|
|
00fd1c1530 | ||
|
|
14ca112862 | ||
|
|
e0286360a8 | ||
|
|
5d809a2e89 | ||
|
|
0dffd5ba9f | ||
|
|
e01687430e | ||
|
|
a32fdff979 | ||
|
|
9668f600d1 | ||
|
|
672e2367fb | ||
|
|
625a48dcf7 | ||
|
|
03aadcc85a | ||
|
|
500555b834 | ||
|
|
e766352d81 | ||
|
|
db698562a9 | ||
|
|
7fea9f7e8b | ||
|
|
d871cb98e5 | ||
|
|
8d815ff587 | ||
|
|
c9fbb9ba9c | ||
|
|
f2d9acd3d4 | ||
|
|
fcff36fc8a | ||
|
|
804ef4af5b | ||
|
|
0309a735a9 | ||
|
|
56e48f8360 | ||
|
|
c7473c68ae | ||
|
|
5e63f12ae8 | ||
|
|
60363c36df | ||
|
|
e700f0a5d7 | ||
|
|
6dbd6ffe11 | ||
|
|
eee0e8bed9 | ||
|
|
934b596e00 | ||
|
|
ff73827a6f | ||
|
|
3c12b94519 | ||
|
|
9b02add4df | ||
|
|
a73a2c9240 | ||
|
|
890b6808a2 | ||
|
|
950cb1af0d | ||
|
|
d078e30642 | ||
|
|
b7fd6eb7d8 | ||
|
|
5ace5bdeda |
10
.github/workflows/artifacts.yml
vendored
10
.github/workflows/artifacts.yml
vendored
@@ -7,10 +7,10 @@ jobs:
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/generate-artifacts')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -42,6 +42,12 @@ jobs:
|
||||
name: docker-compose-linux-amd64
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-amd64
|
||||
|
||||
- name: Upload linux-ppc64le binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker-compose-linux-ppc64le
|
||||
path: ${{ github.workspace }}/bin/docker-compose-linux-ppc64le
|
||||
|
||||
- name: Upload windows-amd64 binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
@@ -5,6 +5,12 @@ on:
|
||||
branches:
|
||||
- v2
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
description: 'To run with tmate enter "debug_enabled"'
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -13,10 +19,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -40,10 +46,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
@@ -65,10 +71,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@@ -90,7 +96,7 @@ jobs:
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||
|
||||
- name: E2E Test in plugin mode
|
||||
run: make e2e-compose
|
||||
@@ -101,10 +107,10 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@@ -123,7 +129,17 @@ jobs:
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
run: make GIT_TAG=e2e-PR-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.sha }} -f builder.Makefile compose-plugin
|
||||
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
|
||||
- name: E2E Test in standalone mode
|
||||
run: make e2e-compose-standalone
|
||||
run: |
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -11,10 +11,10 @@ jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18.3
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: make GIT_TAG=${{ github.event.inputs.tag }} -f builder.Makefile cross
|
||||
|
||||
- name: Compute checksums
|
||||
run: cd bin; for f in *; do shasum --algorithm 256 $f > $f.sha256; done
|
||||
run: cd bin; for f in *; do shasum --binary --algorithm 256 $f | tee -a checksums.txt > $f.sha256; done
|
||||
|
||||
- name: License
|
||||
run: cp packaging/* bin/
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.17-alpine
|
||||
ARG GO_VERSION=1.18.3-alpine
|
||||
ARG GOLANGCI_LINT_VERSION=v1.40.1-alpine
|
||||
ARG PROTOC_GEN_GO_VERSION=v1.4.3
|
||||
|
||||
@@ -88,7 +88,7 @@ RUN --mount=target=. \
|
||||
make -f builder.Makefile test
|
||||
|
||||
FROM base AS check-license-headers
|
||||
RUN go get -u github.com/kunalkushwaha/ltag
|
||||
RUN go install github.com/kunalkushwaha/ltag@latest
|
||||
RUN --mount=target=. \
|
||||
make -f builder.Makefile check-license-headers
|
||||
|
||||
|
||||
5
Makefile
5
Makefile
@@ -43,16 +43,19 @@ compose-plugin: ## Compile the compose cli-plugin
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
docker compose version
|
||||
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
go test $(TEST_FLAGS) -count=1 --tags=standalone ./pkg/e2e
|
||||
docker-compose version
|
||||
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
|
||||
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
|
||||
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
|
||||
|
||||
.PHONY: e2e
|
||||
e2e: e2e-compose e2e-compose-standalone ## Run end to end local tests in both modes. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
@@ -47,6 +47,7 @@ compose-plugin:
|
||||
.PHONY: cross
|
||||
cross:
|
||||
GOOS=linux GOARCH=amd64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-x86_64 ./cmd
|
||||
GOOS=linux GOARCH=ppc64le $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-ppc64le ./cmd
|
||||
GOOS=linux GOARCH=arm64 $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-aarch64 ./cmd
|
||||
GOOS=linux GOARM=6 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv6 ./cmd
|
||||
GOOS=linux GOARM=7 GOARCH=arm $(GO_BUILD) $(TAGS) -o $(COMPOSE_BINARY)-linux-armv7 ./cmd
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -40,6 +41,28 @@ type buildOptions struct {
|
||||
args []string
|
||||
noCache bool
|
||||
memory string
|
||||
ssh string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
var SSHKeys []types.SSHKey
|
||||
var err error
|
||||
if opts.ssh != "" {
|
||||
SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
|
||||
if err != nil {
|
||||
return api.BuildOptions{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Progress: opts.progress,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
SSHs: SSHKeys,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var printerModes = []string{
|
||||
@@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("ssh") && opts.ssh == "" {
|
||||
opts.ssh = "default"
|
||||
}
|
||||
return runBuild(ctx, backend, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
@@ -82,6 +108,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
@@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Build(ctx, project, api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Progress: opts.progress,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
})
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -89,9 +90,6 @@ func Adapt(fn Command) func(cmd *cobra.Command, args []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Warning is a global warning to be displayed to user on command failure
|
||||
var Warning string
|
||||
|
||||
type projectOptions struct {
|
||||
ProjectName string
|
||||
Profiles []string
|
||||
@@ -132,8 +130,8 @@ func (o *projectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||
f.StringVar(&o.EnvFile, "env-file", "", "Specify an alternate environment file.")
|
||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the Compose file)")
|
||||
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the Compose file)")
|
||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
@@ -143,6 +141,11 @@ func (o *projectOptions) toProjectName() (string, error) {
|
||||
return o.ProjectName, nil
|
||||
}
|
||||
|
||||
envProjectName := os.Getenv("COMPOSE_PROJECT_NAME")
|
||||
if envProjectName != "" {
|
||||
return envProjectName, nil
|
||||
}
|
||||
|
||||
project, err := o.toProject(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -167,7 +170,10 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
|
||||
ef := o.EnvFile
|
||||
if ef != "" && !filepath.IsAbs(ef) {
|
||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
||||
ef, err = filepath.Abs(ef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
@@ -208,9 +214,9 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
cli.WithOsEnv,
|
||||
cli.WithEnvFile(o.EnvFile),
|
||||
cli.WithDotEnv,
|
||||
cli.WithOsEnv,
|
||||
cli.WithConfigFileEnv,
|
||||
cli.WithDefaultConfigPath,
|
||||
cli.WithName(o.ProjectName))...)
|
||||
@@ -252,6 +258,10 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
}
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := setEnvWithDotEnv(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parent := cmd.Root()
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
@@ -267,7 +277,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
|
||||
}
|
||||
ansi = "never"
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n", aec.RedF))
|
||||
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
|
||||
}
|
||||
if verbose {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
@@ -328,3 +338,27 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
command.Flags().MarkHidden("verbose") //nolint:errcheck
|
||||
return command
|
||||
}
|
||||
|
||||
func setEnvWithDotEnv(prjOpts *projectOptions) error {
|
||||
options, err := prjOpts.toProjectOptions()
|
||||
if err != nil {
|
||||
return compose.WrapComposeError(err)
|
||||
}
|
||||
workingDir, err := options.GetWorkingDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envFromFile, err := cli.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range envFromFile {
|
||||
if _, ok := os.LookupEnv(k); !ok {
|
||||
if err = os.Setenv(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.source = args[0]
|
||||
opts.destination = args[1]
|
||||
return runCopy(ctx, backend, opts)
|
||||
@@ -64,8 +64,10 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
flags.IntVar(&opts.index, "index", 1, "Index of the container if there are multiple instances of a service [default: 1].")
|
||||
flags.IntVar(&opts.index, "index", 0, "Index of the container if there are multiple instances of a service .")
|
||||
flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service.")
|
||||
flags.MarkHidden("all") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
|
||||
@@ -59,10 +59,11 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runDown(ctx, backend, opts)
|
||||
}),
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS "))
|
||||
removeOrphans := utils.StringToBool(os.Getenv("COMPOSE_REMOVE_ORPHANS"))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, " Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.")
|
||||
|
||||
@@ -35,11 +35,12 @@ type execOpts struct {
|
||||
environment []string
|
||||
workingDir string
|
||||
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
privileged bool
|
||||
noTty bool
|
||||
user string
|
||||
detach bool
|
||||
index int
|
||||
privileged bool
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -71,7 +72,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service)
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
|
||||
|
||||
runCmd.Flags().BoolP("interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
|
||||
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
@@ -103,6 +104,7 @@ func runExec(ctx context.Context, backend api.Service, opts execOpts) error {
|
||||
Index: opts.index,
|
||||
Detach: opts.detach,
|
||||
WorkingDir: opts.workingDir,
|
||||
Interactive: opts.interactive,
|
||||
}
|
||||
|
||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
formatter2 "github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/pkg/errors"
|
||||
@@ -139,14 +140,14 @@ SERVICES:
|
||||
}
|
||||
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
writter(containers),
|
||||
writer(containers),
|
||||
"NAME", "COMMAND", "SERVICE", "STATUS", "PORTS")
|
||||
}
|
||||
|
||||
func writter(containers []api.ContainerSummary) func(w io.Writer) {
|
||||
func writer(containers []api.ContainerSummary) func(w io.Writer) {
|
||||
return func(w io.Writer) {
|
||||
for _, container := range containers {
|
||||
ports := DisplayablePorts(container)
|
||||
ports := displayablePorts(container)
|
||||
status := container.State
|
||||
if status == "running" && container.Health != "" {
|
||||
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
|
||||
@@ -178,72 +179,20 @@ func hasStatus(c api.ContainerSummary, statuses []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type portRange struct {
|
||||
pStart int
|
||||
pEnd int
|
||||
tStart int
|
||||
tEnd int
|
||||
IP string
|
||||
protocol string
|
||||
}
|
||||
|
||||
func (pr portRange) String() string {
|
||||
var (
|
||||
pub string
|
||||
tgt string
|
||||
)
|
||||
|
||||
if pr.pEnd > pr.pStart {
|
||||
pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
|
||||
} else if pr.pStart > 0 {
|
||||
pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
|
||||
}
|
||||
if pr.tEnd > pr.tStart {
|
||||
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
|
||||
} else {
|
||||
tgt = fmt.Sprintf("%d", pr.tStart)
|
||||
}
|
||||
return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
|
||||
}
|
||||
|
||||
// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
|
||||
func DisplayablePorts(c api.ContainerSummary) string {
|
||||
func displayablePorts(c api.ContainerSummary) string {
|
||||
if c.Publishers == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
sort.Sort(c.Publishers)
|
||||
|
||||
pr := portRange{}
|
||||
ports := []string{}
|
||||
for _, p := range c.Publishers {
|
||||
prIsRange := pr.tEnd != pr.tStart
|
||||
tOverlaps := p.TargetPort <= pr.tEnd
|
||||
|
||||
// Start a new port-range if:
|
||||
// - the protocol is different from the current port-range
|
||||
// - published or target port are not consecutive to the current port-range
|
||||
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
|
||||
if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
|
||||
// start a new port-range, and print the previous port-range (if any)
|
||||
if pr.pStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
pr = portRange{
|
||||
pStart: p.PublishedPort,
|
||||
pEnd: p.PublishedPort,
|
||||
tStart: p.TargetPort,
|
||||
tEnd: p.TargetPort,
|
||||
protocol: p.Protocol,
|
||||
IP: p.URL,
|
||||
}
|
||||
continue
|
||||
ports := make([]types.Port, len(c.Publishers))
|
||||
for i, pub := range c.Publishers {
|
||||
ports[i] = types.Port{
|
||||
IP: pub.URL,
|
||||
PrivatePort: uint16(pub.TargetPort),
|
||||
PublicPort: uint16(pub.PublishedPort),
|
||||
Type: pub.Protocol,
|
||||
}
|
||||
pr.pEnd = p.PublishedPort
|
||||
pr.tEnd = p.TargetPort
|
||||
}
|
||||
if pr.tStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
|
||||
return formatter2.DisplayablePorts(ports)
|
||||
}
|
||||
|
||||
84
cmd/compose/ps_test.go
Normal file
84
cmd/compose/ps_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPsPretty(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
origStdout := os.Stdout
|
||||
t.Cleanup(func() {
|
||||
os.Stdout = origStdout
|
||||
})
|
||||
dir := t.TempDir()
|
||||
f, err := os.Create(filepath.Join(dir, "output.txt"))
|
||||
if err != nil {
|
||||
t.Fatal("could not create output file")
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
os.Stdout = f
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
backend := mocks.NewMockService(ctrl)
|
||||
backend.EXPECT().
|
||||
Ps(gomock.Eq(ctx), gomock.Any(), gomock.Any()).
|
||||
DoAndReturn(func(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
return []api.ContainerSummary{
|
||||
{
|
||||
ID: "abc123",
|
||||
Name: "ABC",
|
||||
Publishers: api.PortPublishers{
|
||||
{
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 8080,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
TargetPort: 8443,
|
||||
PublishedPort: 8443,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}).AnyTimes()
|
||||
|
||||
opts := psOptions{projectOptions: &projectOptions{ProjectName: "test"}}
|
||||
err = runPs(ctx, backend, nil, opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
output := make([]byte, 256)
|
||||
_, err = f.Read(output)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-noTty allocation (default: auto-detected).")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
@@ -262,7 +262,9 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{})
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: &project,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -185,6 +185,9 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
if upOptions.attachDependencies {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
if len(attachTo) == 0 {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
|
||||
create := api.CreateOptions{
|
||||
Services: services,
|
||||
@@ -204,6 +207,7 @@ func runUp(ctx context.Context, backend api.Service, createOptions createOptions
|
||||
return backend.Up(ctx, project, api.UpOptions{
|
||||
Create: create,
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attachTo,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
|
||||
@@ -79,7 +79,7 @@ func (l *logConsumer) Log(container, service, message string) {
|
||||
}
|
||||
p := l.getPresenter(container)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
fmt.Fprintf(l.writer, "%s %s\n", p.prefix, line) // nolint:errcheck
|
||||
fmt.Fprintf(l.writer, "%s%s\n", p.prefix, line) // nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,5 +118,5 @@ type presenter struct {
|
||||
}
|
||||
|
||||
func (p *presenter) setPrefix(width int) {
|
||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s |", p.name))
|
||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
|
||||
}
|
||||
|
||||
@@ -32,11 +32,6 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
func init() {
|
||||
commands.Warning = "The new 'docker compose' command is currently experimental. " +
|
||||
"To provide feedback or request new features please open issues at https://github.com/docker/compose"
|
||||
}
|
||||
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
lazyInit := api.NewServiceProxy()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.17
|
||||
ARG GO_VERSION=1.18.3
|
||||
ARG FORMATS=md,yaml
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine AS docsgen
|
||||
|
||||
@@ -44,7 +44,7 @@ Docker Compose
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory
|
||||
(default: the path of the Compose file) |
|
||||
(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Build or rebuild services
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
|
||||
| `--pull` | | | Always attempt to pull a newer version of the image. |
|
||||
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -7,10 +7,9 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `--all` | | | Copy to all the containers of the service. |
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `1` | Index of the container if there are multiple instances of a service [default: 1]. |
|
||||
| `--index` | `int` | `0` | Index of the container if there are multiple instances of a service . |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -13,7 +13,7 @@ Run a one-off command on a service.
|
||||
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
|
||||
| `-l`, `--label` | `stringArray` | | Add or override a label |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-noTty allocation (default: auto-detected). |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
|
||||
| `--no-deps` | | | Don't start linked services. |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
|
||||
| `--quiet-pull` | | | Pull without printing progress information. |
|
||||
|
||||
@@ -222,7 +222,7 @@ options:
|
||||
value_type: string
|
||||
description: |-
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
@@ -265,7 +265,7 @@ options:
|
||||
description: |-
|
||||
DEPRECATED! USE --project-directory INSTEAD.
|
||||
Specify an alternate working directory
|
||||
(default: the path of the Compose file)
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
|
||||
@@ -117,6 +117,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: ssh
|
||||
value_type: string
|
||||
description: |
|
||||
Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
|
||||
@@ -10,8 +10,8 @@ options:
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Copy to all the containers of the service.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
deprecated: true
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
@@ -40,9 +40,9 @@ options:
|
||||
swarm: false
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "1"
|
||||
default_value: "0"
|
||||
description: |
|
||||
Index of the container if there are multiple instances of a service [default: 1].
|
||||
Index of the container if there are multiple instances of a service .
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
|
||||
@@ -125,7 +125,7 @@ options:
|
||||
shorthand: T
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
description: 'Disable pseudo-noTty allocation (default: auto-detected).'
|
||||
description: 'Disable pseudo-TTY allocation (default: auto-detected).'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
|
||||
88
go.mod
88
go.mod
@@ -1,61 +1,60 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v1.1.0
|
||||
github.com/cnabio/cnab-to-oci v0.3.3
|
||||
github.com/compose-spec/compose-go v1.2.8
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.1
|
||||
github.com/containerd/containerd v1.6.6
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||
github.com/docker/buildx v0.8.0
|
||||
github.com/docker/cli v20.10.12+incompatible
|
||||
github.com/docker/buildx v0.8.2 // when updating, also update the replace rules accordingly
|
||||
github.com/docker/cli v20.10.13+incompatible
|
||||
github.com/docker/cli-docs-tool v0.4.0
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/docker v20.10.13+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/hashicorp/go-version v1.5.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b
|
||||
github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||
github.com/morikuni/aec v1.0.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.2
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/theupdateframework/notary v0.6.1
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/theupdateframework/notary v0.7.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.1.0
|
||||
gotest.tools/v3 v3.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
|
||||
github.com/cnabio/cnab-go v0.23.1 // indirect
|
||||
github.com/containerd/continuity v0.2.2 // indirect
|
||||
github.com/containerd/ttrpc v1.1.0 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
@@ -69,32 +68,31 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.0 // indirect
|
||||
github.com/klauspost/compress v1.15.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/signal v0.6.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.0 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.2-0.20210831022256-780655b2ba0e // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
@@ -111,29 +109,45 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.4.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.44.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/grpc v1.45.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apimachinery v0.23.4 // indirect
|
||||
k8s.io/client-go v0.23.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apimachinery v0.23.4 // indirect; see replace for the actual version used
|
||||
k8s.io/client-go v0.23.4 // indirect; see replace for the actual version used
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20220605182715-4dfcec6e9a8c // indirect
|
||||
github.com/zmap/zlint v1.1.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20220309205733-2b52f62e9627+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220309172631-83b51522df43+incompatible
|
||||
|
||||
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.2 // Can be removed on next bump of containerd to > 1.6.4
|
||||
|
||||
// For k8s dependencies, we use a replace directive, to prevent them being
|
||||
// upgraded to the version specified in containerd, which is not relevant to the
|
||||
// version needed.
|
||||
// See https://github.com/docker/buildx/pull/948 for details.
|
||||
// https://github.com/docker/buildx/blob/v0.8.1/go.mod#L62-L64
|
||||
k8s.io/api => k8s.io/api v0.22.4
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.22.4
|
||||
k8s.io/client-go => k8s.io/client-go v0.22.4
|
||||
)
|
||||
|
||||
@@ -32,9 +32,9 @@ type Service interface {
|
||||
// Push executes the equivalent ot a `compose push`
|
||||
Push(ctx context.Context, project *types.Project, options PushOptions) error
|
||||
// Pull executes the equivalent of a `compose pull`
|
||||
Pull(ctx context.Context, project *types.Project, opts PullOptions) error
|
||||
Pull(ctx context.Context, project *types.Project, options PullOptions) error
|
||||
// Create executes the equivalent to a `compose create`
|
||||
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
Create(ctx context.Context, project *types.Project, options CreateOptions) error
|
||||
// Start executes the equivalent to a `compose start`
|
||||
Start(ctx context.Context, projectName string, options StartOptions) error
|
||||
// Restart restarts containers
|
||||
@@ -54,25 +54,25 @@ type Service interface {
|
||||
// Convert translate compose model into backend's native format
|
||||
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
|
||||
// Kill executes the equivalent to a `compose kill`
|
||||
Kill(ctx context.Context, project string, options KillOptions) error
|
||||
Kill(ctx context.Context, projectName string, options KillOptions) error
|
||||
// RunOneOffContainer creates a service oneoff container and starts its dependencies
|
||||
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||
// Remove executes the equivalent to a `compose rm`
|
||||
Remove(ctx context.Context, project string, options RemoveOptions) error
|
||||
Remove(ctx context.Context, projectName string, options RemoveOptions) error
|
||||
// Exec executes a command in a running service container
|
||||
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||
Exec(ctx context.Context, projectName string, options RunOptions) (int, error)
|
||||
// Copy copies a file/folder between a service container and the local filesystem
|
||||
Copy(ctx context.Context, project string, options CopyOptions) error
|
||||
Copy(ctx context.Context, projectName string, options CopyOptions) error
|
||||
// Pause executes the equivalent to a `compose pause`
|
||||
Pause(ctx context.Context, project string, options PauseOptions) error
|
||||
Pause(ctx context.Context, projectName string, options PauseOptions) error
|
||||
// UnPause executes the equivalent to a `compose unpause`
|
||||
UnPause(ctx context.Context, project string, options PauseOptions) error
|
||||
UnPause(ctx context.Context, projectName string, options PauseOptions) error
|
||||
// Top executes the equivalent to a `compose top`
|
||||
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||
// Events executes the equivalent to a `compose events`
|
||||
Events(ctx context.Context, project string, options EventsOptions) error
|
||||
Events(ctx context.Context, projectName string, options EventsOptions) error
|
||||
// Port executes the equivalent to a `compose port`
|
||||
Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
|
||||
Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error)
|
||||
// Images executes the equivalent of a `compose images`
|
||||
Images(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
|
||||
}
|
||||
@@ -91,6 +91,8 @@ type BuildOptions struct {
|
||||
Quiet bool
|
||||
// Services passed in the command line to be built
|
||||
Services []string
|
||||
// Ssh authentications passed in the command line
|
||||
SSHs []types.SSHKey
|
||||
}
|
||||
|
||||
// CreateOptions group options of the Create API
|
||||
@@ -115,6 +117,8 @@ type CreateOptions struct {
|
||||
|
||||
// StartOptions group options of the Start API
|
||||
type StartOptions struct {
|
||||
// Project is the compose project used to define this app. Might be nil if user ran `start` just with project name
|
||||
Project *types.Project
|
||||
// Attach to container and forward logs if not nil
|
||||
Attach LogConsumer
|
||||
// AttachTo set the services to attach to
|
||||
|
||||
@@ -219,11 +219,11 @@ func (s *ServiceProxy) Convert(ctx context.Context, project *types.Project, opti
|
||||
}
|
||||
|
||||
// Kill implements Service interface
|
||||
func (s *ServiceProxy) Kill(ctx context.Context, project string, options KillOptions) error {
|
||||
func (s *ServiceProxy) Kill(ctx context.Context, projectName string, options KillOptions) error {
|
||||
if s.KillFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.KillFn(ctx, project, options)
|
||||
return s.KillFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// RunOneOffContainer implements Service interface
|
||||
@@ -238,43 +238,43 @@ func (s *ServiceProxy) RunOneOffContainer(ctx context.Context, project *types.Pr
|
||||
}
|
||||
|
||||
// Remove implements Service interface
|
||||
func (s *ServiceProxy) Remove(ctx context.Context, project string, options RemoveOptions) error {
|
||||
func (s *ServiceProxy) Remove(ctx context.Context, projectName string, options RemoveOptions) error {
|
||||
if s.RemoveFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.RemoveFn(ctx, project, options)
|
||||
return s.RemoveFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Exec implements Service interface
|
||||
func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOptions) (int, error) {
|
||||
func (s *ServiceProxy) Exec(ctx context.Context, projectName string, options RunOptions) (int, error) {
|
||||
if s.ExecFn == nil {
|
||||
return 0, ErrNotImplemented
|
||||
}
|
||||
return s.ExecFn(ctx, project, options)
|
||||
return s.ExecFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Copy implements Service interface
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, projectName string, options CopyOptions) error {
|
||||
if s.CopyFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.CopyFn(ctx, project, options)
|
||||
return s.CopyFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Pause implements Service interface
|
||||
func (s *ServiceProxy) Pause(ctx context.Context, project string, options PauseOptions) error {
|
||||
func (s *ServiceProxy) Pause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||
if s.PauseFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.PauseFn(ctx, project, options)
|
||||
return s.PauseFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// UnPause implements Service interface
|
||||
func (s *ServiceProxy) UnPause(ctx context.Context, project string, options PauseOptions) error {
|
||||
func (s *ServiceProxy) UnPause(ctx context.Context, projectName string, options PauseOptions) error {
|
||||
if s.UnPauseFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.UnPauseFn(ctx, project, options)
|
||||
return s.UnPauseFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Top implements Service interface
|
||||
@@ -286,19 +286,19 @@ func (s *ServiceProxy) Top(ctx context.Context, project string, services []strin
|
||||
}
|
||||
|
||||
// Events implements Service interface
|
||||
func (s *ServiceProxy) Events(ctx context.Context, project string, options EventsOptions) error {
|
||||
func (s *ServiceProxy) Events(ctx context.Context, projectName string, options EventsOptions) error {
|
||||
if s.EventsFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
return s.EventsFn(ctx, project, options)
|
||||
return s.EventsFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Port implements Service interface
|
||||
func (s *ServiceProxy) Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error) {
|
||||
func (s *ServiceProxy) Port(ctx context.Context, projectName string, service string, port int, options PortOptions) (string, int, error) {
|
||||
if s.PortFn == nil {
|
||||
return "", 0, ErrNotImplemented
|
||||
}
|
||||
return s.PortFn(ctx, project, service, port, options)
|
||||
return s.PortFn(ctx, projectName, service, port, options)
|
||||
}
|
||||
|
||||
// Images implements Service interface
|
||||
|
||||
@@ -48,7 +48,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
||||
|
||||
for _, container := range containers {
|
||||
err := s.attachContainer(ctx, container, listener, project)
|
||||
err := s.attachContainer(ctx, container, listener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,13 +56,9 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
|
||||
return containers, err
|
||||
}
|
||||
|
||||
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener, project *types.Project) error {
|
||||
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener api.ContainerEventListener) error {
|
||||
serviceName := container.Labels[api.ServiceLabel]
|
||||
containerName := getContainerNameWithoutProject(container)
|
||||
service, err := project.GetService(serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
@@ -78,7 +74,13 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
||||
Line: line,
|
||||
})
|
||||
})
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w, w)
|
||||
|
||||
inspect, err := s.dockerCli.Client().ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, w, w)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
bclient "github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -62,7 +64,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if service.Build != nil {
|
||||
imageName := getImageName(service, project.Name)
|
||||
imagesToBuild = append(imagesToBuild, imageName)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName)
|
||||
buildOptions, err := s.toBuildOptions(project, service, imageName, options.SSHs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -80,7 +82,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
Attrs: map[string]string{"ref": image},
|
||||
})
|
||||
}
|
||||
|
||||
opts[imageName] = buildOptions
|
||||
}
|
||||
}
|
||||
@@ -159,7 +160,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
|
||||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||
continue
|
||||
}
|
||||
opt, err := s.toBuildOptions(project, service, imageName)
|
||||
opt, err := s.toBuildOptions(project, service, imageName, []types.SSHKey{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,7 +210,7 @@ func (s *composeService) doBuild(ctx context.Context, project *types.Project, op
|
||||
return s.doBuildBuildkit(ctx, project, opts, mode)
|
||||
}
|
||||
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string) (build.Options, error) {
|
||||
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, sshKeys []types.SSHKey) (build.Options, error) {
|
||||
var tags []string
|
||||
tags = append(tags, imageTag)
|
||||
|
||||
@@ -234,11 +235,59 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
plats = append(plats, p)
|
||||
}
|
||||
|
||||
cacheFrom, err := buildflags.ParseCacheEntry(service.Build.CacheFrom)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
cacheTo, err := buildflags.ParseCacheEntry(service.Build.CacheTo)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
|
||||
sessionConfig := []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(s.stderr()),
|
||||
}
|
||||
if len(sshKeys) > 0 || len(service.Build.SSH) > 0 {
|
||||
sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, sshKeys...))
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
sessionConfig = append(sessionConfig, sshAgentProvider)
|
||||
}
|
||||
|
||||
if len(service.Build.Secrets) > 0 {
|
||||
var sources []secretsprovider.Source
|
||||
for _, secret := range service.Build.Secrets {
|
||||
config := project.Secrets[secret.Source]
|
||||
if config.File == "" {
|
||||
return build.Options{}, fmt.Errorf("build.secrets only supports file-based secrets: %q", secret.Source)
|
||||
}
|
||||
sources = append(sources, secretsprovider.Source{
|
||||
ID: secret.Source,
|
||||
FilePath: config.File,
|
||||
})
|
||||
}
|
||||
store, err := secretsprovider.NewStore(sources)
|
||||
if err != nil {
|
||||
return build.Options{}, err
|
||||
}
|
||||
p := secretsprovider.NewSecretProvider(store)
|
||||
sessionConfig = append(sessionConfig, p)
|
||||
}
|
||||
|
||||
if len(service.Build.Tags) > 0 {
|
||||
tags = append(tags, service.Build.Tags...)
|
||||
}
|
||||
|
||||
return build.Options{
|
||||
Inputs: build.Inputs{
|
||||
ContextPath: service.Build.Context,
|
||||
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
|
||||
},
|
||||
CacheFrom: cacheFrom,
|
||||
CacheTo: cacheTo,
|
||||
NoCache: service.Build.NoCache,
|
||||
Pull: service.Build.Pull,
|
||||
BuildArgs: buildArgs,
|
||||
Tags: tags,
|
||||
Target: service.Build.Target,
|
||||
@@ -246,10 +295,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
||||
Platforms: plats,
|
||||
Labels: service.Build.Labels,
|
||||
NetworkMode: service.Build.Network,
|
||||
ExtraHosts: service.Build.ExtraHosts,
|
||||
Session: []session.Attachable{
|
||||
authprovider.NewDockerAuthProvider(s.stderr()),
|
||||
},
|
||||
ExtraHosts: service.Build.ExtraHosts.AsList(),
|
||||
Session: sessionConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -283,3 +330,14 @@ func dockerFilePath(context string, dockerfile string) string {
|
||||
}
|
||||
return filepath.Join(context, dockerfile)
|
||||
}
|
||||
|
||||
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
|
||||
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
|
||||
for _, sshKey := range sshKeys {
|
||||
sshConfig = append(sshConfig, sshprovider.AgentConfig{
|
||||
ID: sshKey.ID,
|
||||
Paths: []string{sshKey.Path},
|
||||
})
|
||||
}
|
||||
return sshprovider.NewSSHAgentProvider(sshConfig)
|
||||
}
|
||||
|
||||
@@ -143,17 +143,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
|
||||
return "", err
|
||||
}
|
||||
|
||||
// if up to this point nothing has set the context then we must have another
|
||||
// way for sending it(streaming) and set the context to the Dockerfile
|
||||
if dockerfileCtx != nil && buildCtx == nil {
|
||||
buildCtx = dockerfileCtx
|
||||
}
|
||||
|
||||
progressOutput := streamformatter.NewProgressOutput(progBuff)
|
||||
var body io.Reader
|
||||
if buildCtx != nil {
|
||||
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
}
|
||||
body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
|
||||
configFile := s.configFile()
|
||||
creds, err := configFile.GetAllCredentials()
|
||||
|
||||
@@ -18,14 +18,13 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
// Containers is a set of moby Container
|
||||
@@ -41,17 +40,7 @@ const (
|
||||
|
||||
func (s *composeService) getContainers(ctx context.Context, project string, oneOff oneOff, stopped bool, selectedServices ...string) (Containers, error) {
|
||||
var containers Containers
|
||||
f := []filters.KeyValuePair{projectFilter(project)}
|
||||
if len(selectedServices) == 1 {
|
||||
f = append(f, serviceFilter(selectedServices[0]))
|
||||
}
|
||||
switch oneOff {
|
||||
case oneOffOnly:
|
||||
f = append(f, oneOffFilter(true))
|
||||
case oneOffExclude:
|
||||
f = append(f, oneOffFilter(false))
|
||||
case oneOffInclude:
|
||||
}
|
||||
f := getDefaultFilters(project, oneOff, selectedServices...)
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(f...),
|
||||
All: stopped,
|
||||
@@ -65,6 +54,40 @@ func (s *composeService) getContainers(ctx context.Context, project string, oneO
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func getDefaultFilters(projectName string, oneOff oneOff, selectedServices ...string) []filters.KeyValuePair {
|
||||
f := []filters.KeyValuePair{projectFilter(projectName)}
|
||||
if len(selectedServices) == 1 {
|
||||
f = append(f, serviceFilter(selectedServices[0]))
|
||||
}
|
||||
switch oneOff {
|
||||
case oneOffOnly:
|
||||
f = append(f, oneOffFilter(true))
|
||||
case oneOffExclude:
|
||||
f = append(f, oneOffFilter(false))
|
||||
case oneOffInclude:
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName string, oneOff oneOff, stopped bool, serviceName string, containerIndex int) (moby.Container, error) {
|
||||
defaultFilters := getDefaultFilters(projectName, oneOff, serviceName)
|
||||
defaultFilters = append(defaultFilters, containerNumberFilter(containerIndex))
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
defaultFilters...,
|
||||
),
|
||||
All: stopped,
|
||||
})
|
||||
if err != nil {
|
||||
return moby.Container{}, err
|
||||
}
|
||||
if len(containers) < 1 {
|
||||
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", serviceName, containerIndex)
|
||||
}
|
||||
container := containers[0]
|
||||
return container, nil
|
||||
}
|
||||
|
||||
// containerPredicate define a predicate we want container to satisfy for filtering operations
|
||||
type containerPredicate func(c moby.Container) bool
|
||||
|
||||
@@ -87,14 +110,6 @@ func isNotOneOff(c moby.Container) bool {
|
||||
return !ok || v == "False"
|
||||
}
|
||||
|
||||
func indexed(index int) containerPredicate {
|
||||
return func(c moby.Container) bool {
|
||||
number := c.Labels[api.ContainerNumberLabel]
|
||||
idx, err := strconv.Atoi(number)
|
||||
return err == nil && index == idx
|
||||
}
|
||||
}
|
||||
|
||||
// filter return Containers with elements to match predicate
|
||||
func (containers Containers) filter(predicate containerPredicate) Containers {
|
||||
var filtered Containers
|
||||
|
||||
@@ -523,6 +523,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.injectSecrets(ctx, project, service, created.ID)
|
||||
return created, err
|
||||
}
|
||||
|
||||
|
||||
@@ -42,59 +42,80 @@ const (
|
||||
acrossServices = fromService | toService
|
||||
)
|
||||
|
||||
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
|
||||
srcService, srcPath := splitCpArg(opts.Source)
|
||||
destService, dstPath := splitCpArg(opts.Destination)
|
||||
func (s *composeService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
srcService, srcPath := splitCpArg(options.Source)
|
||||
destService, dstPath := splitCpArg(options.Destination)
|
||||
|
||||
var direction copyDirection
|
||||
var serviceName string
|
||||
var copyFunc func(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error
|
||||
if srcService != "" {
|
||||
direction |= fromService
|
||||
serviceName = srcService
|
||||
copyFunc = s.copyFromContainer
|
||||
|
||||
// copying from multiple containers of a services doesn't make sense.
|
||||
if opts.All {
|
||||
if options.All {
|
||||
return errors.New("cannot use the --all flag when copying from a service")
|
||||
}
|
||||
}
|
||||
if destService != "" {
|
||||
direction |= toService
|
||||
serviceName = destService
|
||||
copyFunc = s.copyToContainer
|
||||
}
|
||||
if direction == acrossServices {
|
||||
return errors.New("copying between services is not supported")
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
|
||||
if direction == 0 {
|
||||
return errors.New("unknown copy direction")
|
||||
}
|
||||
|
||||
containers, err := s.listContainersTargetedForCopy(ctx, projectName, options.Index, direction, serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(containers) < 1 {
|
||||
return fmt.Errorf("no container found for service %q", serviceName)
|
||||
}
|
||||
|
||||
if !opts.All {
|
||||
containers = containers.filter(indexed(opts.Index))
|
||||
}
|
||||
|
||||
g := errgroup.Group{}
|
||||
for _, container := range containers {
|
||||
containerID := container.ID
|
||||
g.Go(func() error {
|
||||
switch direction {
|
||||
case fromService:
|
||||
return s.copyFromContainer(ctx, containerID, srcPath, dstPath, opts)
|
||||
case toService:
|
||||
return s.copyToContainer(ctx, containerID, srcPath, dstPath, opts)
|
||||
case acrossServices:
|
||||
return errors.New("copying between services is not supported")
|
||||
default:
|
||||
return errors.New("unknown copy direction")
|
||||
}
|
||||
return copyFunc(ctx, containerID, srcPath, dstPath, options)
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) listContainersTargetedForCopy(ctx context.Context, projectName string, index int, direction copyDirection, serviceName string) (Containers, error) {
|
||||
var containers Containers
|
||||
var err error
|
||||
switch {
|
||||
case index > 0:
|
||||
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffExclude, true, serviceName, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(containers, container), nil
|
||||
default:
|
||||
containers, err = s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(containers) < 1 {
|
||||
return nil, fmt.Errorf("no container found for service %q", serviceName)
|
||||
}
|
||||
if direction == fromService {
|
||||
return containers[:1], err
|
||||
|
||||
}
|
||||
return containers, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) copyToContainer(ctx context.Context, containerID string, srcPath string, dstPath string, opts api.CopyOptions) error {
|
||||
var err error
|
||||
if srcPath != "-" {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/blkiodev"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
@@ -173,13 +174,21 @@ func prepareServicesDependsOn(p *types.Project) error {
|
||||
dependencies = append(dependencies, spec[0])
|
||||
}
|
||||
|
||||
for _, link := range service.Links {
|
||||
dependencies = append(dependencies, strings.Split(link, ":")[0])
|
||||
}
|
||||
|
||||
if len(dependencies) == 0 {
|
||||
continue
|
||||
}
|
||||
if service.DependsOn == nil {
|
||||
service.DependsOn = make(types.DependsOnConfig)
|
||||
}
|
||||
deps, err := p.GetServices(dependencies...)
|
||||
|
||||
// Verify dependencies exist in the project, whether disabled or not
|
||||
projAllServices := types.Project{}
|
||||
projAllServices.Services = p.AllServices()
|
||||
deps, err := projAllServices.GetServices(dependencies...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -347,6 +356,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
volumesFrom = append(volumesFrom, v[len("container:"):])
|
||||
}
|
||||
|
||||
links, err := s.getLinks(ctx, p.Name, service, number)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
securityOpts, err := parseSecurityOpts(p, service.SecurityOpt)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@@ -371,7 +385,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
DNS: service.DNS,
|
||||
DNSSearch: service.DNSSearch,
|
||||
DNSOptions: service.DNSOpts,
|
||||
ExtraHosts: service.ExtraHosts,
|
||||
ExtraHosts: service.ExtraHosts.AsList(),
|
||||
SecurityOpt: securityOpts,
|
||||
UsernsMode: container.UsernsMode(service.UserNSMode),
|
||||
Privileged: service.Privileged,
|
||||
@@ -381,6 +395,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
Runtime: service.Runtime,
|
||||
LogConfig: logConfig,
|
||||
GroupAdd: service.GroupAdd,
|
||||
Links: links,
|
||||
}
|
||||
|
||||
return &containerConfig, &hostConfig, networkConfig, nil
|
||||
@@ -580,8 +595,12 @@ func setLimits(limits *types.Resource, resources *container.Resources) {
|
||||
resources.Memory = int64(limits.MemoryBytes)
|
||||
}
|
||||
if limits.NanoCPUs != "" {
|
||||
i, _ := strconv.ParseInt(limits.NanoCPUs, 10, 64)
|
||||
resources.NanoCPUs = i
|
||||
if f, err := strconv.ParseFloat(limits.NanoCPUs, 64); err == nil {
|
||||
resources.NanoCPUs = int64(f * 1e9)
|
||||
}
|
||||
}
|
||||
if limits.PIds > 0 {
|
||||
resources.PidsLimit = &limits.PIds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,12 +728,20 @@ func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Proj
|
||||
MOUNTS:
|
||||
for _, m := range mountOptions {
|
||||
volumeMounts[m.Target] = struct{}{}
|
||||
// `Bind` API is used when host path need to be created if missing, `Mount` is preferred otherwise
|
||||
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
|
||||
// `Mount` is preferred but does not offer option to created host path if missing
|
||||
// so `Bind` API is used here with raw volume string
|
||||
// see https://github.com/moby/moby/issues/43483
|
||||
for _, v := range service.Volumes {
|
||||
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
|
||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
|
||||
continue MOUNTS
|
||||
if v.Target == m.Target {
|
||||
switch {
|
||||
case string(m.Type) != v.Type:
|
||||
v.Source = m.Source
|
||||
fallthrough
|
||||
case v.Bind != nil && v.Bind.CreateHostPath:
|
||||
binds = append(binds, v.String())
|
||||
continue MOUNTS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,23 +750,6 @@ MOUNTS:
|
||||
return volumeMounts, binds, mounts, nil
|
||||
}
|
||||
|
||||
func getBindMode(bind *types.ServiceVolumeBind, readOnly bool) string {
|
||||
mode := "rw"
|
||||
|
||||
if readOnly {
|
||||
mode = "ro"
|
||||
}
|
||||
|
||||
switch bind.SELinux {
|
||||
case types.SELinuxShared:
|
||||
mode += ",z"
|
||||
case types.SELinuxPrivate:
|
||||
mode += ",Z"
|
||||
}
|
||||
|
||||
return mode
|
||||
}
|
||||
|
||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
||||
var mounts = map[string]mount.Mount{}
|
||||
if inherit != nil {
|
||||
@@ -879,6 +889,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
||||
}
|
||||
|
||||
if definedSecret.Environment != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||
Type: types.VolumeTypeBind,
|
||||
Source: definedSecret.File,
|
||||
@@ -922,10 +936,14 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||
}
|
||||
}
|
||||
|
||||
bind, vol, tmpfs := buildMountOptions(volume)
|
||||
bind, vol, tmpfs := buildMountOptions(project, volume)
|
||||
|
||||
volume.Target = path.Clean(volume.Target)
|
||||
|
||||
if bind != nil {
|
||||
volume.Type = types.VolumeTypeBind
|
||||
}
|
||||
|
||||
return mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: source,
|
||||
@@ -938,7 +956,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
|
||||
func buildMountOptions(project types.Project, volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
|
||||
switch volume.Type {
|
||||
case "bind":
|
||||
if volume.Volume != nil {
|
||||
@@ -955,6 +973,11 @@ func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *m
|
||||
if volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
|
||||
}
|
||||
if v, ok := project.Volumes[volume.Source]; ok && v.DriverOpts["o"] == types.VolumeTypeBind {
|
||||
return buildBindOption(&types.ServiceVolumeBind{
|
||||
CreateHostPath: true,
|
||||
}), nil, nil
|
||||
}
|
||||
return nil, buildVolumeOptions(volume.Volume), nil
|
||||
case "tmpfs":
|
||||
if volume.Bind != nil {
|
||||
@@ -1008,73 +1031,82 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
||||
_, err := s.apiClient().NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
||||
// NetworkInspect will match on ID prefix, so NetworkList with a name
|
||||
// filter is used to look for an exact match to prevent e.g. a network
|
||||
// named `db` from getting erroneously matched to a network with an ID
|
||||
// like `db9086999caf`
|
||||
networks, err := s.apiClient().NetworkList(ctx, moby.NetworkListOptions{
|
||||
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
|
||||
})
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
if n.External.External {
|
||||
if n.Driver == "overlay" {
|
||||
// Swarm nodes do not register overlay networks that were
|
||||
// created on a different node unless they're in use.
|
||||
// Here we assume `driver` is relevant for a network we don't manage
|
||||
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
||||
// networkAttach will later fail anyway if network actually doesn't exists
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||
}
|
||||
var ipam *network.IPAM
|
||||
if n.Ipam.Config != nil {
|
||||
var config []network.IPAMConfig
|
||||
for _, pool := range n.Ipam.Config {
|
||||
config = append(config, network.IPAMConfig{
|
||||
Subnet: pool.Subnet,
|
||||
IPRange: pool.IPRange,
|
||||
Gateway: pool.Gateway,
|
||||
AuxAddress: pool.AuxiliaryAddresses,
|
||||
})
|
||||
}
|
||||
ipam = &network.IPAM{
|
||||
Driver: n.Ipam.Driver,
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
createOpts := moby.NetworkCreate{
|
||||
// TODO NameSpace Labels
|
||||
Labels: n.Labels,
|
||||
Driver: n.Driver,
|
||||
Options: n.DriverOpts,
|
||||
Internal: n.Internal,
|
||||
Attachable: n.Attachable,
|
||||
IPAM: ipam,
|
||||
EnableIPv6: n.EnableIPv6,
|
||||
}
|
||||
|
||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
||||
createOpts.IPAM = &network.IPAM{}
|
||||
}
|
||||
|
||||
if n.Ipam.Driver != "" {
|
||||
createOpts.IPAM.Driver = n.Ipam.Driver
|
||||
}
|
||||
|
||||
for _, ipamConfig := range n.Ipam.Config {
|
||||
config := network.IPAMConfig{
|
||||
Subnet: ipamConfig.Subnet,
|
||||
}
|
||||
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
||||
}
|
||||
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(networkEventName))
|
||||
if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
||||
w.Event(progress.ErrorEvent(networkEventName))
|
||||
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
||||
}
|
||||
w.Event(progress.CreatedEvent(networkEventName))
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if len(networks) == 0 {
|
||||
if n.External.External {
|
||||
if n.Driver == "overlay" {
|
||||
// Swarm nodes do not register overlay networks that were
|
||||
// created on a different node unless they're in use.
|
||||
// Here we assume `driver` is relevant for a network we don't manage
|
||||
// which is a non-sense, but this is our legacy ¯\(ツ)/¯
|
||||
// networkAttach will later fail anyway if network actually doesn't exists
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
|
||||
}
|
||||
var ipam *network.IPAM
|
||||
if n.Ipam.Config != nil {
|
||||
var config []network.IPAMConfig
|
||||
for _, pool := range n.Ipam.Config {
|
||||
config = append(config, network.IPAMConfig{
|
||||
Subnet: pool.Subnet,
|
||||
IPRange: pool.IPRange,
|
||||
Gateway: pool.Gateway,
|
||||
AuxAddress: pool.AuxiliaryAddresses,
|
||||
})
|
||||
}
|
||||
ipam = &network.IPAM{
|
||||
Driver: n.Ipam.Driver,
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
createOpts := moby.NetworkCreate{
|
||||
// TODO NameSpace Labels
|
||||
Labels: n.Labels,
|
||||
Driver: n.Driver,
|
||||
Options: n.DriverOpts,
|
||||
Internal: n.Internal,
|
||||
Attachable: n.Attachable,
|
||||
IPAM: ipam,
|
||||
EnableIPv6: n.EnableIPv6,
|
||||
}
|
||||
|
||||
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
|
||||
createOpts.IPAM = &network.IPAM{}
|
||||
}
|
||||
|
||||
if n.Ipam.Driver != "" {
|
||||
createOpts.IPAM.Driver = n.Ipam.Driver
|
||||
}
|
||||
|
||||
for _, ipamConfig := range n.Ipam.Config {
|
||||
config := network.IPAMConfig{
|
||||
Subnet: ipamConfig.Subnet,
|
||||
IPRange: ipamConfig.IPRange,
|
||||
Gateway: ipamConfig.Gateway,
|
||||
AuxAddress: ipamConfig.AuxiliaryAddresses,
|
||||
}
|
||||
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
|
||||
}
|
||||
networkEventName := fmt.Sprintf("Network %s", n.Name)
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.CreatingEvent(networkEventName))
|
||||
if _, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts); err != nil {
|
||||
w.Event(progress.ErrorEvent(networkEventName))
|
||||
return errors.Wrapf(err, "failed to create network %s", n.Name)
|
||||
}
|
||||
w.Event(progress.CreatedEvent(networkEventName))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -143,15 +143,6 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||
assert.Equal(t, mounts[1].Target, "/var/myvolume2")
|
||||
}
|
||||
|
||||
func TestGetBindMode(t *testing.T) {
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, false), "rw")
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{}, true), "ro")
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, false), "rw,z")
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, false), "rw,Z")
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxShared}, true), "ro,z")
|
||||
assert.Equal(t, getBindMode(&composetypes.ServiceVolumeBind{SELinux: composetypes.SELinuxPrivate}, true), "ro,Z")
|
||||
}
|
||||
|
||||
func TestGetDefaultNetworkMode(t *testing.T) {
|
||||
t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
|
||||
@@ -132,7 +132,7 @@ func getParents(v *Vertex) []*Vertex {
|
||||
return v.GetParents()
|
||||
}
|
||||
|
||||
// GetParents returns a slice with the parent vertexes of the a Vertex
|
||||
// GetParents returns a slice with the parent vertices of the a Vertex
|
||||
func (v *Vertex) GetParents() []*Vertex {
|
||||
var res []*Vertex
|
||||
for _, p := range v.Parents {
|
||||
@@ -145,7 +145,7 @@ func getChildren(v *Vertex) []*Vertex {
|
||||
return v.GetChildren()
|
||||
}
|
||||
|
||||
// GetChildren returns a slice with the child vertexes of the a Vertex
|
||||
// GetChildren returns a slice with the child vertices of the a Vertex
|
||||
func (v *Vertex) GetChildren() []*Vertex {
|
||||
var res []*Vertex
|
||||
for _, p := range v.Children {
|
||||
@@ -194,7 +194,7 @@ func (g *Graph) AddVertex(key string, service string, initialStatus ServiceStatu
|
||||
g.Vertices[key] = v
|
||||
}
|
||||
|
||||
// AddEdge adds a relationship of dependency between vertexes `source` and `destination`
|
||||
// AddEdge adds a relationship of dependency between vertices `source` and `destination`
|
||||
func (g *Graph) AddEdge(source string, destination string) error {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/registry/client"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -81,7 +82,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
ops := s.ensureNetworksDown(ctx, project, w)
|
||||
|
||||
if options.Images != "" {
|
||||
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
|
||||
ops = append(ops, s.ensureImagesDown(ctx, project, options, w)...)
|
||||
}
|
||||
|
||||
if options.Volumes {
|
||||
@@ -89,7 +90,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
|
||||
if !resourceToRemove && len(ops) == 0 {
|
||||
w.Event(progress.NewEvent(projectName, progress.Done, "Warning: No resource found to remove"))
|
||||
fmt.Fprintf(s.stderr(), "Warning: No resource found to remove for project %q.\n", projectName)
|
||||
}
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
@@ -113,9 +114,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
|
||||
return ops
|
||||
}
|
||||
|
||||
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
|
||||
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, options api.DownOptions, w progress.Writer) []downOp {
|
||||
var ops []downOp
|
||||
for image := range s.getServiceImages(options, projectName) {
|
||||
for image := range s.getServiceImages(options, project) {
|
||||
image := image
|
||||
ops = append(ops, func() error {
|
||||
return s.removeImage(ctx, image, w)
|
||||
@@ -131,6 +132,11 @@ func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.
|
||||
continue
|
||||
}
|
||||
networkName := n.Name
|
||||
_, err := s.apiClient().NetworkInspect(ctx, networkName, moby.NetworkInspectOptions{})
|
||||
if client.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ops = append(ops, func() error {
|
||||
return s.removeNetwork(ctx, networkName, w)
|
||||
})
|
||||
@@ -138,15 +144,15 @@ func (s *composeService) ensureNetworksDown(ctx context.Context, project *types.
|
||||
return ops
|
||||
}
|
||||
|
||||
func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
|
||||
func (s *composeService) getServiceImages(options api.DownOptions, project *types.Project) map[string]struct{} {
|
||||
images := map[string]struct{}{}
|
||||
for _, service := range options.Project.Services {
|
||||
for _, service := range project.Services {
|
||||
image := service.Image
|
||||
if options.Images == "local" && image != "" {
|
||||
continue
|
||||
}
|
||||
if image == "" {
|
||||
image = getImageName(service, projectName)
|
||||
image = getImageName(service, project.Name)
|
||||
}
|
||||
images[image] = struct{}{}
|
||||
}
|
||||
@@ -232,7 +238,10 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
|
||||
func (s *composeService) getProjectWithResources(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
|
||||
containers = containers.filter(isNotOneOff)
|
||||
project, _ := s.projectFromName(containers, projectName)
|
||||
project, err := s.projectFromName(containers, projectName)
|
||||
if err != nil && !api.IsNotFoundError(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumes, err := s.actualVolumes(ctx, projectName)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
@@ -60,6 +59,7 @@ func TestDown(t *testing.T) {
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "456", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, nil)
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{})
|
||||
@@ -94,6 +94,7 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "789", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "321", moby.ContainerRemoveOptions{Force: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkInspect(gomock.Any(), "myProject_default", moby.NetworkInspectOptions{}).Return(moby.NetworkResource{Name: "myProject_default"}, nil)
|
||||
api.EXPECT().NetworkRemove(gomock.Any(), "myProject_default").Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{RemoveOrphans: true})
|
||||
|
||||
@@ -29,9 +29,10 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Events(ctx context.Context, project string, options api.EventsOptions) error {
|
||||
func (s *composeService) Events(ctx context.Context, projectName string, options api.EventsOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
events, errors := s.apiClient().Events(ctx, moby.EventsOptions{
|
||||
Filters: filters.NewArgs(projectFilter(project)),
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
})
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -18,31 +18,31 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) {
|
||||
target, err := s.getExecTarget(ctx, project, opts)
|
||||
func (s *composeService) Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
target, err := s.getExecTarget(ctx, projectName, options)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
exec := container.NewExecOptions()
|
||||
exec.Interactive = opts.Interactive
|
||||
exec.TTY = opts.Tty
|
||||
exec.Detach = opts.Detach
|
||||
exec.User = opts.User
|
||||
exec.Privileged = opts.Privileged
|
||||
exec.Workdir = opts.WorkingDir
|
||||
exec.Interactive = options.Interactive
|
||||
exec.TTY = options.Tty
|
||||
exec.Detach = options.Detach
|
||||
exec.User = options.User
|
||||
exec.Privileged = options.Privileged
|
||||
exec.Workdir = options.WorkingDir
|
||||
exec.Container = target.ID
|
||||
exec.Command = opts.Command
|
||||
for _, v := range opts.Environment {
|
||||
exec.Command = options.Command
|
||||
for _, v := range options.Environment {
|
||||
err := exec.Env.Set(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -57,19 +57,5 @@ func (s *composeService) Exec(ctx context.Context, project string, opts api.RunO
|
||||
}
|
||||
|
||||
func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) {
|
||||
containers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
serviceFilter(opts.Service),
|
||||
containerNumberFilter(opts.Index),
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return moby.Container{}, err
|
||||
}
|
||||
if len(containers) < 1 {
|
||||
return moby.Container{}, fmt.Errorf("service %q is not running container #%d", opts.Service, opts.Index)
|
||||
}
|
||||
container := containers[0]
|
||||
return container, nil
|
||||
return s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, opts.Service, opts.Index)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
allContainers, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -27,19 +28,19 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Kill(ctx context.Context, project string, options api.KillOptions) error {
|
||||
func (s *composeService) Kill(ctx context.Context, projectName string, options api.KillOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.kill(ctx, project, options)
|
||||
return s.kill(ctx, strings.ToLower(projectName), options)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) kill(ctx context.Context, project string, options api.KillOptions) error {
|
||||
func (s *composeService) kill(ctx context.Context, projectName string, options api.KillOptions) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
services := options.Services
|
||||
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, project, oneOffInclude, false, services...)
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Logs(ctx context.Context, projectName string, consumer api.LogConsumer, options api.LogOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, options.Services...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -26,9 +27,9 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Pause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
func (s *composeService) Pause(ctx context.Context, projectName string, options api.PauseOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.pause(ctx, project, options)
|
||||
return s.pause(ctx, strings.ToLower(projectName), options)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,14 +55,14 @@ func (s *composeService) pause(ctx context.Context, project string, options api.
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) UnPause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
func (s *composeService) UnPause(ctx context.Context, projectName string, options api.PauseOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.unPause(ctx, project, options)
|
||||
return s.unPause(ctx, strings.ToLower(projectName), options)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) unPause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
|
||||
func (s *composeService) unPause(ctx context.Context, projectName string, options api.PauseOptions) error {
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffExclude, false, options.Services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
|
||||
@@ -26,10 +27,11 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func (s *composeService) Port(ctx context.Context, project string, service string, port int, options api.PortOptions) (string, int, error) {
|
||||
func (s *composeService) Port(ctx context.Context, projectName string, service string, port int, options api.PortOptions) (string, int, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
list, err := s.apiClient().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(project),
|
||||
projectFilter(projectName),
|
||||
serviceFilter(service),
|
||||
containerNumberFilter(options.Index),
|
||||
),
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Ps(ctx context.Context, projectName string, options api.PsOptions) ([]api.ContainerSummary, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
oneOff := oneOffExclude
|
||||
if options.All {
|
||||
oneOff = oneOffInclude
|
||||
|
||||
@@ -36,12 +36,12 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
|
||||
if opts.Quiet {
|
||||
return s.pull(ctx, project, opts)
|
||||
func (s *composeService) Pull(ctx context.Context, project *types.Project, options api.PullOptions) error {
|
||||
if options.Quiet {
|
||||
return s.pull(ctx, project, options)
|
||||
}
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.pull(ctx, project, opts)
|
||||
return s.pull(ctx, project, options)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
info.IndexServerAddress = registry.IndexServer
|
||||
}
|
||||
|
||||
images, err := s.getLocalImagesDigests(ctx, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
@@ -69,8 +74,28 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
switch service.PullPolicy {
|
||||
case types.PullPolicyNever, types.PullPolicyBuild:
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Done,
|
||||
Text: "Skipped",
|
||||
})
|
||||
continue
|
||||
case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
|
||||
if _, ok := images[service.Image]; ok {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Done,
|
||||
Text: "Exists",
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
|
||||
_, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
|
||||
if err != nil {
|
||||
if !opts.IgnoreFailures {
|
||||
if service.Build != nil {
|
||||
@@ -93,7 +118,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) error {
|
||||
func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) (string, error) {
|
||||
w.Event(progress.Event{
|
||||
ID: service.Name,
|
||||
Status: progress.Working,
|
||||
@@ -101,12 +126,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
})
|
||||
ref, err := reference.ParseNormalizedNamed(service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := repoInfo.Index.Name
|
||||
@@ -116,12 +141,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
|
||||
authConfig, err := configFile.GetAuthConfig(key)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
|
||||
@@ -134,7 +159,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
Status: progress.Error,
|
||||
Text: "Error",
|
||||
})
|
||||
return WrapCategorisedComposeError(err, PullFailure)
|
||||
return "", WrapCategorisedComposeError(err, PullFailure)
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(stream)
|
||||
@@ -144,10 +169,10 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return WrapCategorisedComposeError(err, PullFailure)
|
||||
return "", WrapCategorisedComposeError(err, PullFailure)
|
||||
}
|
||||
if jm.Error != nil {
|
||||
return WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
|
||||
return "", WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
|
||||
}
|
||||
if !quietPull {
|
||||
toPullProgressEvent(service.Name, jm, w)
|
||||
@@ -158,7 +183,12 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
|
||||
Status: progress.Done,
|
||||
Text: "Pulled",
|
||||
})
|
||||
return nil
|
||||
|
||||
inspected, _, err := s.dockerCli.Client().ImageInspectWithRaw(ctx, service.Image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return inspected.ID, nil
|
||||
}
|
||||
|
||||
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
|
||||
@@ -195,10 +225,12 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, service := range needPull {
|
||||
service := service
|
||||
pulledImages := make([]string, len(needPull))
|
||||
for i, service := range needPull {
|
||||
i, service := i, service
|
||||
eg.Go(func() error {
|
||||
err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
|
||||
id, err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
|
||||
pulledImages[i] = id
|
||||
if err != nil && service.Build != nil {
|
||||
// image can be built, so we can ignore pull failure
|
||||
return nil
|
||||
@@ -206,7 +238,16 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
||||
return err
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
for i, service := range needPull {
|
||||
if pulledImages[i] != "" {
|
||||
images[service.Image] = pulledImages[i]
|
||||
}
|
||||
}
|
||||
err := eg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
containers, _, err := s.actualState(ctx, projectName, options.Services)
|
||||
if err != nil {
|
||||
if api.IsNotFoundError(err) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -28,7 +29,7 @@ import (
|
||||
|
||||
func (s *composeService) Restart(ctx context.Context, projectName string, options api.RestartOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.restart(ctx, projectName, options)
|
||||
return s.restart(ctx, strings.ToLower(projectName), options)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
updateServices(&service, observedState)
|
||||
|
||||
created, err := s.createContainer(ctx, project, service, service.ContainerName, 1,
|
||||
opts.Detach && opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
|
||||
opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -116,6 +116,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
||||
if len(opts.Environment) > 0 {
|
||||
env := types.NewMappingWithEquals(opts.Environment)
|
||||
projectEnv := env.Resolve(func(s string) (string, bool) {
|
||||
if _, ok := service.Environment[s]; ok {
|
||||
return "", false
|
||||
}
|
||||
v, ok := project.Environment[s]
|
||||
return v, ok
|
||||
}).RemoveEmpty()
|
||||
|
||||
88
pkg/compose/secrets.go
Normal file
88
pkg/compose/secrets.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func (s *composeService) injectSecrets(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error {
|
||||
for _, config := range service.Secrets {
|
||||
secret := project.Secrets[config.Source]
|
||||
if secret.Environment == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
env, ok := project.Environment[secret.Environment]
|
||||
if !ok {
|
||||
return fmt.Errorf("environment variable %q required by secret %q is not set", secret.Environment, secret.Name)
|
||||
}
|
||||
b, err := createTar(env, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.apiClient().CopyToContainer(ctx, id, "/", &b, moby.CopyToContainerOptions{
|
||||
CopyUIDGID: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, error) {
|
||||
value := []byte(env)
|
||||
b := bytes.Buffer{}
|
||||
tarWriter := tar.NewWriter(&b)
|
||||
mode := uint32(0400)
|
||||
if config.Mode != nil {
|
||||
mode = *config.Mode
|
||||
}
|
||||
|
||||
target := config.Target
|
||||
if config.Target == "" {
|
||||
target = "/run/secrets/" + config.Source
|
||||
} else if !isUnixAbs(config.Target) {
|
||||
target = "/run/secrets/" + config.Target
|
||||
}
|
||||
|
||||
header := &tar.Header{
|
||||
Name: target,
|
||||
Size: int64(len(value)),
|
||||
Mode: int64(mode),
|
||||
ModTime: time.Now(),
|
||||
}
|
||||
err := tarWriter.WriteHeader(header)
|
||||
if err != nil {
|
||||
return bytes.Buffer{}, err
|
||||
}
|
||||
_, err = tarWriter.Write(value)
|
||||
if err != nil {
|
||||
return bytes.Buffer{}, err
|
||||
}
|
||||
err = tarWriter.Close()
|
||||
return b, err
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
@@ -30,20 +31,23 @@ import (
|
||||
|
||||
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.start(ctx, projectName, options, nil)
|
||||
return s.start(ctx, strings.ToLower(projectName), options, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error {
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project := options.Project
|
||||
if project == nil {
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err := s.projectFromName(containers, projectName, options.AttachTo...)
|
||||
if err != nil {
|
||||
return err
|
||||
project, err = s.projectFromName(containers, projectName, options.AttachTo...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
@@ -55,12 +59,12 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.watchContainers(context.Background(), project.Name, options.AttachTo, listener, attached, func(container moby.Container) error {
|
||||
return s.attachContainer(ctx, container, listener, project)
|
||||
return s.attachContainer(ctx, container, listener)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
err = InDependencyOrder(ctx, project, func(c context.Context, name string) error {
|
||||
err := InDependencyOrder(ctx, project, func(c context.Context, name string) error {
|
||||
service, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
|
||||
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
|
||||
return progress.Run(ctx, func(ctx context.Context) error {
|
||||
return s.stop(ctx, projectName, options)
|
||||
return s.stop(ctx, strings.ToLower(projectName), options)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,14 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (s *composeService) Top(ctx context.Context, projectName string, services []string) ([]api.ContainerProcSummary, error) {
|
||||
projectName = strings.ToLower(projectName)
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -61,12 +61,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
go func() {
|
||||
<-signalChan
|
||||
s.Kill(ctx, project.Name, api.KillOptions{ // nolint:errcheck
|
||||
Services: options.Create.Services,
|
||||
Services: project.ServiceNames(),
|
||||
})
|
||||
}()
|
||||
|
||||
return s.Stop(ctx, project.Name, api.StopOptions{
|
||||
Services: options.Create.Services,
|
||||
Services: project.ServiceNames(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,30 +33,34 @@ import (
|
||||
)
|
||||
|
||||
func TestComposeCancel(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("metrics on cancel Compose build", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("ls")
|
||||
c.RunDockerComposeCmd(t, "ls")
|
||||
buildProjectPath := "fixtures/build-infinite/compose.yaml"
|
||||
|
||||
// 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.NewDockerCmd("compose", "-f", buildProjectPath, "build", "--progress", "plain"))
|
||||
cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build",
|
||||
"--progress", "plain"))
|
||||
assert.NilError(t, err)
|
||||
|
||||
c.WaitForCondition(func() (bool, string) {
|
||||
c.WaitForCondition(t, func() (bool, string) {
|
||||
out := stdout.String()
|
||||
errors := stderr.String()
|
||||
return strings.Contains(out, "RUN sleep infinity"), fmt.Sprintf("'RUN sleep infinity' not found in : \n%s\nStderr: \n%s\n", out, errors)
|
||||
return strings.Contains(out,
|
||||
"RUN sleep infinity"), fmt.Sprintf("'RUN sleep infinity' not found in : \n%s\nStderr: \n%s\n", out,
|
||||
errors)
|
||||
}, 30*time.Second, 1*time.Second)
|
||||
|
||||
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
|
||||
|
||||
assert.NilError(t, err)
|
||||
c.WaitForCondition(func() (bool, string) {
|
||||
c.WaitForCondition(t, func() (bool, string) {
|
||||
out := stdout.String()
|
||||
errors := stderr.String()
|
||||
return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out, errors)
|
||||
return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out,
|
||||
errors)
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,28 +23,28 @@ import (
|
||||
)
|
||||
|
||||
func TestCascadeStop(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "e2e-cascade-stop"
|
||||
|
||||
t.Run("abort-on-container-exit", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
|
||||
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.RunDockerOrExitError("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=sleep")
|
||||
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.RunDockerOrExitError("compose", "-f", "./fixtures/cascade-stop-test/compose.yaml", "--project-name", projectName, "up", "--exit-code-from=unknown")
|
||||
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("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,65 +27,116 @@ import (
|
||||
)
|
||||
|
||||
func TestLocalComposeBuild(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build named and unnamed images", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
|
||||
|
||||
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "build")
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build")
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
c.RunDockerCmd("image", "inspect", "build-test_nginx")
|
||||
c.RunDockerCmd("image", "inspect", "custom-nginx")
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test_nginx")
|
||||
c.RunDockerCmd(t, "image", "inspect", "custom-nginx")
|
||||
})
|
||||
|
||||
t.Run("build with build-arg", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
|
||||
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
|
||||
|
||||
res := c.RunDockerCmd("image", "inspect", "build-test_nginx")
|
||||
res := c.RunDockerCmd(t, "image", "inspect", "build-test_nginx")
|
||||
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
|
||||
})
|
||||
|
||||
t.Run("build with build-arg set by env", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
|
||||
|
||||
icmd.RunCmd(c.NewDockerCmd("compose", "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO"),
|
||||
icmd.RunCmd(c.NewDockerComposeCmd(t,
|
||||
"--project-directory",
|
||||
"fixtures/build-test",
|
||||
"build",
|
||||
"--build-arg",
|
||||
"FOO"),
|
||||
func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "FOO=BAR")
|
||||
})
|
||||
|
||||
res := c.RunDockerCmd("image", "inspect", "build-test_nginx")
|
||||
res := c.RunDockerCmd(t, "image", "inspect", "build-test_nginx")
|
||||
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
|
||||
})
|
||||
|
||||
t.Run("build with multiple build-args ", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError("rmi", "-f", "multi-args_multiargs")
|
||||
cmd := c.NewDockerCmd("compose", "--project-directory", "fixtures/build-test/multi-args", "build")
|
||||
c.RunDockerOrExitError(t, "rmi", "-f", "multi-args_multiargs")
|
||||
cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/build-test/multi-args", "build")
|
||||
|
||||
icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
|
||||
})
|
||||
|
||||
res := c.RunDockerCmd("image", "inspect", "multi-args_multiargs")
|
||||
res := c.RunDockerCmd(t, "image", "inspect", "multi-args_multiargs")
|
||||
res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
|
||||
})
|
||||
|
||||
t.Run("build as part of up", func(t *testing.T) {
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
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{
|
||||
ExitCode: 1,
|
||||
Err: "invalid empty ssh agent socket: make sure SSH_AUTH_SOCK is set",
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("build succeed with ssh from Compose file", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/ssh", "build")
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-ssh")
|
||||
})
|
||||
|
||||
t.Run("build succeed with ssh from CLI", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/ssh/compose-without-ssh.yaml", "--project-directory",
|
||||
"fixtures/build-test/ssh", "build", "--no-cache", "--ssh", "fake-ssh=./fixtures/build-test/ssh/fake_rsa")
|
||||
c.RunDockerCmd(t, "image", "inspect", "build-test-ssh")
|
||||
})
|
||||
|
||||
t.Run("build failed with wrong ssh key id from CLI", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/build-test/ssh/compose-without-ssh.yaml",
|
||||
"--project-directory", "fixtures/build-test/ssh", "build", "--no-cache", "--ssh",
|
||||
"wrong-ssh=./fixtures/build-test/ssh/fake_rsa")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 17,
|
||||
Err: "failed to solve: rpc error: code = Unknown desc = unset ssh forward key fake-ssh",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("build succeed as part of up with ssh from Compose file", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-ssh")
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/ssh", "up", "-d", "--build")
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/ssh", "down")
|
||||
})
|
||||
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"})
|
||||
@@ -94,26 +145,59 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
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("image", "inspect", "build-test_nginx")
|
||||
c.RunDockerCmd("image", "inspect", "custom-nginx")
|
||||
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("--project-directory", "fixtures/build-test", "up", "-d")
|
||||
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("--workdir", "fixtures/build-test", "up", "-d", "--build")
|
||||
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("--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerCmd("rmi", "build-test_nginx")
|
||||
c.RunDockerCmd("rmi", "custom-nginx")
|
||||
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) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build with secrets", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-secret")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test/secrets", "build")
|
||||
res.Assert(t, icmd.Success)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildTags(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("build with tags", func(t *testing.T) {
|
||||
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError(t, "rmi", "build-test-tags")
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "./fixtures/build-test/tags", "build", "--no-cache")
|
||||
|
||||
res := c.RunDockerCmd(t, "image", "inspect", "build-test-tags")
|
||||
expectedOutput := `"RepoTags": [
|
||||
"docker/build-test-tags:1.0.0",
|
||||
"build-test-tags:latest",
|
||||
"other-image-name:v1.0.0"
|
||||
],
|
||||
`
|
||||
res.Assert(t, icmd.Expected{Out: expectedOutput})
|
||||
})
|
||||
}
|
||||
|
||||
34
pkg/e2e/compose_down_test.go
Normal file
34
pkg/e2e/compose_down_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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 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"`})
|
||||
})
|
||||
}
|
||||
163
pkg/e2e/compose_environment_test.go
Normal file
163
pkg/e2e/compose_environment_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
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 (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestEnvPriority(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
projectDir := "./fixtures/environment/env-priority"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "env-compose-priority")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
|
||||
"--project-directory", projectDir, "up", "-d", "--build")
|
||||
})
|
||||
|
||||
// Full options activated
|
||||
// 1. Compose file <-- Result expected
|
||||
// 2. Shell environment variables
|
||||
// 3. Environment file
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("compose file priority", func(t *testing.T) {
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
|
||||
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run",
|
||||
"--rm", "-e", "WHEREAMI", "env-compose-priority")
|
||||
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
|
||||
res := icmd.RunCmd(cmd)
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Compose File")
|
||||
})
|
||||
|
||||
// No Compose file, all other options
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables <-- Result expected
|
||||
// 3. Environment file
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("shell priority", func(t *testing.T) {
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
|
||||
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e",
|
||||
"WHEREAMI", "env-compose-priority")
|
||||
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
|
||||
res := icmd.RunCmd(cmd)
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell")
|
||||
})
|
||||
|
||||
// No Compose file and env variable pass to the run command
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables <-- Result expected
|
||||
// 3. Environment file
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("shell priority from run command", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
|
||||
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e",
|
||||
"WHEREAMI=shell-run", "env-compose-priority")
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell-run")
|
||||
})
|
||||
|
||||
// No Compose file & no env variable but override env file
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables
|
||||
// 3. Environment file <-- Result expected
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("override env file", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
|
||||
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e",
|
||||
"WHEREAMI", "env-compose-priority")
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "override")
|
||||
})
|
||||
|
||||
// No Compose file & no env variable but override env file
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables
|
||||
// 3. Environment file <-- Result expected
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("env file", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
|
||||
projectDir, "run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Env File")
|
||||
})
|
||||
|
||||
// No Compose file & no env variable, using an empty override env file
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables
|
||||
// 3. Environment file
|
||||
// 4. Dockerfile <-- Result expected
|
||||
// 5. Variable is not defined
|
||||
t.Run("use Dockerfile", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
|
||||
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.empty", "run", "--rm", "-e", "WHEREAMI",
|
||||
"env-compose-priority")
|
||||
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Dockerfile")
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", projectDir, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnvInterpolation(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
projectDir := "./fixtures/environment/env-interpolation"
|
||||
|
||||
// No variable defined in the Compose file and env variable pass to the run command
|
||||
// 1. Compose file
|
||||
// 2. Shell environment variables <-- Result expected
|
||||
// 3. Environment file
|
||||
// 4. Dockerfile
|
||||
// 5. Variable is not defined
|
||||
t.Run("shell priority from run command", func(t *testing.T) {
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation/compose.yaml",
|
||||
"--project-directory", projectDir, "config")
|
||||
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
|
||||
res := icmd.RunCmd(cmd)
|
||||
res.Assert(t, icmd.Expected{Out: `IMAGE: default_env:shell`})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommentsInEnvFile(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
projectDir := "./fixtures/environment/env-file-comments"
|
||||
|
||||
t.Run("comments in env files", func(t *testing.T) {
|
||||
c.RunDockerOrExitError(t, "rmi", "env-file-comments")
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml", "--project-directory",
|
||||
projectDir, "up", "-d", "--build")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-file-comments/compose.yaml",
|
||||
"--project-directory", projectDir, "run", "--rm", "-e", "COMMENT", "-e", "NO_COMMENT", "env-file-comments")
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: `COMMENT=1234`})
|
||||
res.Assert(t, icmd.Expected{Out: `NO_COMMENT=1234#5`})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-directory", projectDir, "down", "--rmi", "all")
|
||||
})
|
||||
}
|
||||
@@ -25,24 +25,29 @@ import (
|
||||
)
|
||||
|
||||
func TestLocalComposeExec(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-exec"
|
||||
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
|
||||
cmdArgs := func(cmd string, args ...string) []string {
|
||||
ret := []string{"--project-directory", "fixtures/simple-composefile", "--project-name", projectName, cmd}
|
||||
ret = append(ret, args...)
|
||||
return ret
|
||||
}
|
||||
|
||||
c.RunDockerComposeCmd(t, cmdArgs("up", "-d")...)
|
||||
|
||||
t.Run("exec true", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("exec", "compose-e2e-exec-simple-1", "/bin/true")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
c.RunDockerComposeCmd(t, cmdArgs("exec", "simple", "/bin/true")...)
|
||||
})
|
||||
|
||||
t.Run("exec false", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("exec", "compose-e2e-exec-simple-1", "/bin/false")
|
||||
res := c.RunDockerComposeCmdNoCheck(t, cmdArgs("exec", "simple", "/bin/false")...)
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1})
|
||||
})
|
||||
|
||||
t.Run("exec with env set", func(t *testing.T) {
|
||||
res := icmd.RunCmd(c.NewDockerCmd("exec", "-e", "FOO", "compose-e2e-exec-simple-1", "/usr/bin/env"),
|
||||
res := icmd.RunCmd(c.NewDockerComposeCmd(t, cmdArgs("exec", "-e", "FOO", "simple", "/usr/bin/env")...),
|
||||
func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "FOO=BAR")
|
||||
})
|
||||
@@ -50,8 +55,7 @@ func TestLocalComposeExec(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("exec without env set", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("exec", "-e", "FOO", "compose-e2e-exec-simple-1", "/usr/bin/env")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
assert.Check(t, !strings.Contains(res.Stdout(), "FOO="))
|
||||
res := c.RunDockerComposeCmd(t, cmdArgs("exec", "-e", "FOO", "simple", "/usr/bin/env")...)
|
||||
assert.Check(t, !strings.Contains(res.Stdout(), "FOO="), res.Combined())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,21 +26,22 @@ import (
|
||||
)
|
||||
|
||||
func TestLocalComposeRun(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("compose run", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "back")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back")
|
||||
lines := Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello there!!", res.Stdout())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
|
||||
res = c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello one more time")
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo",
|
||||
"Hello one more time")
|
||||
lines = Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello one more time", res.Stdout())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
|
||||
})
|
||||
|
||||
t.Run("check run container exited", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
lines := Lines(res.Stdout())
|
||||
var runContainerID string
|
||||
var truncatedSlug string
|
||||
@@ -59,7 +60,7 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assert.Assert(t, runContainerID != "")
|
||||
res = c.RunDockerCmd("inspect", runContainerID)
|
||||
res = c.RunDockerCmd(t, "inspect", runContainerID)
|
||||
res.Assert(t, icmd.Expected{Out: ` "Status": "exited"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "run-test"`})
|
||||
@@ -68,43 +69,46 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("compose run --rm", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo", "Hello again")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo",
|
||||
"Hello again")
|
||||
lines := Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello again", res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd("ps", "--all")
|
||||
res = c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "run-test_back"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run --volumes", func(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo",
|
||||
"back", "/bin/sh", "-c", "ls /foo")
|
||||
res.Assert(t, icmd.Expected{Out: "compose_run_test.go"})
|
||||
|
||||
res = c.RunDockerCmd("ps", "--all")
|
||||
res = c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "run-test_back"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run --publish", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
|
||||
res := c.RunDockerCmd("ps")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back",
|
||||
"/bin/sh", "-c", "sleep 1")
|
||||
res := c.RunDockerCmd(t, "ps")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "8081->80/tcp"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run orphan", func(t *testing.T) {
|
||||
// Use different compose files to get an orphan container
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/orphan.yaml", "run", "simple")
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/orphan.yaml", "run", "simple")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "orphan"))
|
||||
|
||||
cmd := c.NewDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "COMPOSE_IGNORE_ORPHANS=True")
|
||||
})
|
||||
@@ -112,9 +116,23 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/orphan.yaml", "down")
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
icmd.RunCmd(cmd, func(c *icmd.Cmd) {
|
||||
c.Env = append(c.Env, "COMPOSE_REMOVE_ORPHANS=True")
|
||||
})
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("run starts only container and dependencies", func(t *testing.T) {
|
||||
// ensure that even if another service is up run does not start it: https://github.com/docker/compose/issues/9459
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "up", "service_b")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "run", "service_a")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "shared_dep"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "service_b"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,31 +31,30 @@ import (
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
var binDir string
|
||||
|
||||
func TestLocalComposeUp(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
// this test shares a fixture with TestCompatibility and can't run at the same time
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-demo"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check accessing running app", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
|
||||
endpoint := "http://localhost:90"
|
||||
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, `"word":`))
|
||||
|
||||
res = c.RunDockerCmd("network", "ls")
|
||||
res = c.RunDockerCmd(t, "network", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: projectName + "_default"})
|
||||
})
|
||||
|
||||
t.Run("top", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "top")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "top")
|
||||
output := res.Stdout()
|
||||
head := []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}
|
||||
for _, h := range head {
|
||||
@@ -66,7 +65,7 @@ func TestLocalComposeUp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("check compose labels", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("inspect", projectName+"-web-1")
|
||||
res := c.RunDockerCmd(t, "inspect", projectName+"-web-1")
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "compose-e2e-demo"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "False",`})
|
||||
@@ -76,55 +75,55 @@ func TestLocalComposeUp(t *testing.T) {
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.service": "web"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version":`})
|
||||
|
||||
res = c.RunDockerCmd("network", "inspect", projectName+"_default")
|
||||
res = c.RunDockerCmd(t, "network", "inspect", projectName+"_default")
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.network": "default"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": `})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version": `})
|
||||
})
|
||||
|
||||
t.Run("check user labels", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("inspect", projectName+"-web-1")
|
||||
res := c.RunDockerCmd(t, "inspect", projectName+"-web-1")
|
||||
res.Assert(t, icmd.Expected{Out: `"my-label": "test"`})
|
||||
|
||||
})
|
||||
|
||||
t.Run("check healthcheck output", func(t *testing.T) {
|
||||
c.WaitForCmdResult(c.NewDockerCmd("compose", "-p", projectName, "ps", "--format", "json"),
|
||||
c.WaitForCmdResult(t, c.NewDockerComposeCmd(t, "-p", projectName, "ps", "--format", "json"),
|
||||
StdoutContains(`"Name":"compose-e2e-demo-web-1","Command":"/dispatcher","Project":"compose-e2e-demo","Service":"web","State":"running","Health":"healthy"`),
|
||||
5*time.Second, 1*time.Second)
|
||||
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `NAME COMMAND SERVICE STATUS PORTS`})
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-web-1 "/dispatcher" web running (healthy) 0.0.0.0:90->80/tcp, :::90->80/tcp`})
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-web-1 "/dispatcher" web running (healthy) 0.0.0.0:90->80/tcp`})
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-db-1 "docker-entrypoint.s…" db running 5432/tcp`})
|
||||
})
|
||||
|
||||
t.Run("images", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "images")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "images")
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-db-1 gtardif/sentences-db latest`})
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-web-1 gtardif/sentences-web latest`})
|
||||
res.Assert(t, icmd.Expected{Out: `compose-e2e-demo-words-1 gtardif/sentences-api latest`})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
t.Run("check containers after down", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("check networks after down", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("network", "ls")
|
||||
res := c.RunDockerCmd(t, "network", "ls")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
})
|
||||
}
|
||||
|
||||
func TestComposePull(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
res := c.RunDockerOrExitError("compose", "--project-directory", "fixtures/simple-composefile", "pull")
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/simple-composefile", "pull")
|
||||
output := res.Combined()
|
||||
|
||||
assert.Assert(t, strings.Contains(output, "simple Pulled"))
|
||||
@@ -132,102 +131,108 @@ func TestComposePull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDownComposefileInParentFolder(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
tmpFolder, err := ioutil.TempDir("fixtures/simple-composefile", "test-tmp")
|
||||
assert.NilError(t, err)
|
||||
defer os.Remove(tmpFolder) // nolint: errcheck
|
||||
projectName := filepath.Base(tmpFolder)
|
||||
|
||||
res := c.RunDockerComposeCmd("--project-directory", tmpFolder, "up", "-d")
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", tmpFolder, "up", "-d")
|
||||
res.Assert(t, icmd.Expected{Err: "Started", ExitCode: 0})
|
||||
|
||||
res = c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
res = c.RunDockerComposeCmd(t, "-p", projectName, "down")
|
||||
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
|
||||
}
|
||||
|
||||
func TestAttachRestart(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cmd := c.NewDockerCmd("compose", "--ansi=never", "--project-directory", "./fixtures/attach-restart", "up")
|
||||
cmd := c.NewDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/attach-restart", "up")
|
||||
res := icmd.StartCmd(cmd)
|
||||
defer c.RunDockerOrExitError("compose", "-p", "attach-restart", "down")
|
||||
defer c.RunDockerComposeCmd(t, "-p", "attach-restart", "down")
|
||||
|
||||
c.WaitForCondition(func() (bool, string) {
|
||||
c.WaitForCondition(t, func() (bool, string) {
|
||||
debug := res.Combined()
|
||||
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)
|
||||
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)
|
||||
|
||||
assert.Equal(t, strings.Count(res.Stdout(), "failing-1 | world"), 3, res.Combined())
|
||||
}
|
||||
|
||||
func TestInitContainer(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
res := c.RunDockerOrExitError("compose", "--ansi=never", "--project-directory", "./fixtures/init-container", "up")
|
||||
defer c.RunDockerOrExitError("compose", "-p", "init-container", "down")
|
||||
res := c.RunDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/init-container", "up")
|
||||
defer c.RunDockerComposeCmd(t, "-p", "init-container", "down")
|
||||
testify.Regexp(t, "foo-1 | hello(?m:.*)bar-1 | world", res.Stdout())
|
||||
}
|
||||
|
||||
func TestRm(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-rm"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("rm -sf", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm",
|
||||
"-sf", "simple")
|
||||
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
|
||||
})
|
||||
|
||||
t.Run("check containers after rm -sf", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName+"_simple"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("rm -sf <none>", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm",
|
||||
"-sf", "simple")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompatibility(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
// this test shares a fixture with TestLocalComposeUp and can't run at the same time
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-compatibility"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name",
|
||||
projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check container names", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("ps", "--format", "{{.Names}}")
|
||||
res := c.RunDockerCmd(t, "ps", "--format", "{{.Names}}")
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_web_1"})
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_words_1"})
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_db_1"})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
const projectName = "compose-e2e-convert"
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
|
||||
nginx:
|
||||
build:
|
||||
|
||||
@@ -26,12 +26,12 @@ import (
|
||||
)
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "copy_e2e"
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "down")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "down")
|
||||
|
||||
os.Remove("./fixtures/cp-test/from-default.txt") //nolint:errcheck
|
||||
os.Remove("./fixtures/cp-test/from-indexed.txt") //nolint:errcheck
|
||||
@@ -39,52 +39,45 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("start service", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "up", "--scale", "nginx=5", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "up",
|
||||
"--scale", "nginx=5", "-d")
|
||||
})
|
||||
|
||||
t.Run("make sure service is running", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `nginx running`})
|
||||
})
|
||||
|
||||
t.Run("copy to container copies the file to the first container by default", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/default.txt")
|
||||
t.Run("copy to container copies the file to the all containers by default", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp",
|
||||
"./fixtures/cp-test/cp-me.txt", "nginx:/tmp/default.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/default.txt").Stdout()
|
||||
output := c.RunDockerCmd(t, "exec", projectName+"-nginx-1", "cat", "/tmp/default.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
|
||||
res = c.RunDockerOrExitError("exec", projectName+"_nginx_2", "cat", "/tmp/default.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1})
|
||||
output = c.RunDockerCmd(t, "exec", projectName+"-nginx-2", "cat", "/tmp/default.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
|
||||
output = c.RunDockerCmd(t, "exec", projectName+"-nginx-3", "cat", "/tmp/default.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
})
|
||||
|
||||
t.Run("copy to container with a given index copies the file to the given container", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/indexed.txt")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3",
|
||||
"./fixtures/cp-test/cp-me.txt", "nginx:/tmp/indexed.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
output := c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/indexed.txt").Stdout()
|
||||
output := c.RunDockerCmd(t, "exec", projectName+"-nginx-3", "cat", "/tmp/indexed.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
|
||||
res = c.RunDockerOrExitError("exec", projectName+"-nginx-2", "cat", "/tmp/indexed.txt")
|
||||
res = c.RunDockerOrExitError(t, "exec", projectName+"-nginx-2", "cat", "/tmp/indexed.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1})
|
||||
})
|
||||
|
||||
t.Run("copy to container with the all flag copies the file to all containers", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--all", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/all.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/all.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
|
||||
output = c.RunDockerCmd("exec", projectName+"-nginx-2", "cat", "/tmp/all.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
|
||||
output = c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/all.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world`), output)
|
||||
})
|
||||
|
||||
t.Run("copy from a container copies the file to the host from the first container by default", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/default.txt", "./fixtures/cp-test/from-default.txt")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp",
|
||||
"nginx:/tmp/default.txt", "./fixtures/cp-test/from-default.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
data, err := os.ReadFile("./fixtures/cp-test/from-default.txt")
|
||||
@@ -93,7 +86,8 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy from a container with a given index copies the file to host", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "nginx:/tmp/indexed.txt", "./fixtures/cp-test/from-indexed.txt")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3",
|
||||
"nginx:/tmp/indexed.txt", "./fixtures/cp-test/from-indexed.txt")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
data, err := os.ReadFile("./fixtures/cp-test/from-indexed.txt")
|
||||
@@ -102,13 +96,15 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy to and from a container also work with folder", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-folder", "nginx:/tmp")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp",
|
||||
"./fixtures/cp-test/cp-folder", "nginx:/tmp")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
output := c.RunDockerCmd("exec", projectName+"-nginx-1", "cat", "/tmp/cp-folder/cp-me.txt").Stdout()
|
||||
output := c.RunDockerCmd(t, "exec", projectName+"-nginx-1", "cat", "/tmp/cp-folder/cp-me.txt").Stdout()
|
||||
assert.Assert(t, strings.Contains(output, `hello world from folder`), output)
|
||||
|
||||
res = c.RunDockerComposeCmd("-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/cp-folder", "./fixtures/cp-test/cp-folder2")
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp",
|
||||
"nginx:/tmp/cp-folder", "./fixtures/cp-test/cp-folder2")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
data, err := os.ReadFile("./fixtures/cp-test/cp-folder2/cp-me.txt")
|
||||
|
||||
106
pkg/e2e/ddev_test.go
Normal file
106
pkg/e2e/ddev_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
const ddevVersion = "v1.19.1"
|
||||
|
||||
func TestComposeRunDdev(t *testing.T) {
|
||||
if !composeStandaloneMode {
|
||||
t.Skip("Not running in plugin mode - ddev only supports invoking standalone `docker-compose`")
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Running on Windows. Skipping...")
|
||||
}
|
||||
|
||||
// ddev shells out to `docker` and `docker-compose` (standalone), so a
|
||||
// temporary directory is created with symlinks to system Docker and the
|
||||
// locally-built standalone Compose binary to use as PATH
|
||||
requiredTools := []string{
|
||||
findToolInPath(t, DockerExecutableName),
|
||||
ComposeStandalonePath(t),
|
||||
findToolInPath(t, "tar"),
|
||||
findToolInPath(t, "gzip"),
|
||||
}
|
||||
pathDir := t.TempDir()
|
||||
for _, tool := range requiredTools {
|
||||
require.NoError(t, os.Symlink(tool, filepath.Join(pathDir, filepath.Base(tool))),
|
||||
"Could not create symlink for %q", tool)
|
||||
}
|
||||
|
||||
c := NewCLI(t, WithEnv(
|
||||
"DDEV_DEBUG=true",
|
||||
fmt.Sprintf("PATH=%s", pathDir),
|
||||
))
|
||||
|
||||
ddevDir := t.TempDir()
|
||||
siteName := filepath.Base(ddevDir)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "delete", "-Oy")
|
||||
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
|
||||
})
|
||||
|
||||
osName := "linux"
|
||||
if runtime.GOOS == "darwin" {
|
||||
osName = "macos"
|
||||
}
|
||||
|
||||
compressedFilename := fmt.Sprintf("ddev_%s-%s.%s.tar.gz", osName, runtime.GOARCH, ddevVersion)
|
||||
c.RunCmdInDir(t, ddevDir, "curl", "-LO", fmt.Sprintf("https://github.com/drud/ddev/releases/download/%s/%s",
|
||||
ddevVersion,
|
||||
compressedFilename))
|
||||
|
||||
c.RunCmdInDir(t, ddevDir, "tar", "-xzf", compressedFilename)
|
||||
|
||||
// Create a simple index.php we can test against.
|
||||
c.RunCmdInDir(t, ddevDir, "sh", "-c", "echo '<?php\nprint \"ddev is working\";' >index.php")
|
||||
|
||||
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "--auto")
|
||||
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "global", "--use-docker-compose-from-path")
|
||||
vRes := c.RunCmdInDir(t, ddevDir, "./ddev", "version")
|
||||
out := vRes.Stdout()
|
||||
fmt.Printf("ddev version: %s\n", out)
|
||||
|
||||
c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
|
||||
|
||||
c.RunCmdInDir(t, ddevDir, "./ddev", "start", "-y")
|
||||
|
||||
curlRes := c.RunCmdInDir(t, ddevDir, "curl", "-sSL", fmt.Sprintf("http://%s.ddev.site", siteName))
|
||||
out = curlRes.Stdout()
|
||||
fmt.Println(out)
|
||||
assert.Assert(t, strings.Contains(out, "ddev is working"), "Could not start project")
|
||||
}
|
||||
|
||||
func findToolInPath(t testing.TB, name string) string {
|
||||
t.Helper()
|
||||
binPath, err := exec.LookPath(name)
|
||||
require.NoError(t, err, "Could not find %q in path", name)
|
||||
return binPath
|
||||
}
|
||||
22
pkg/e2e/fixtures/build-test/secrets/Dockerfile
Normal file
22
pkg/e2e/fixtures/build-test/secrets/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
# syntax=docker/dockerfile:1.2
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
FROM alpine
|
||||
|
||||
RUN echo "foo" > /tmp/expected
|
||||
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /tmp/actual
|
||||
RUN diff /tmp/expected /tmp/actual
|
||||
11
pkg/e2e/fixtures/build-test/secrets/compose.yml
Normal file
11
pkg/e2e/fixtures/build-test/secrets/compose.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
ssh:
|
||||
image: build-test-secret
|
||||
build:
|
||||
context: .
|
||||
secrets:
|
||||
- mysecret
|
||||
|
||||
secrets:
|
||||
mysecret:
|
||||
file: ./secret.txt
|
||||
1
pkg/e2e/fixtures/build-test/secrets/secret.txt
Normal file
1
pkg/e2e/fixtures/build-test/secrets/secret.txt
Normal file
@@ -0,0 +1 @@
|
||||
foo
|
||||
24
pkg/e2e/fixtures/build-test/ssh/Dockerfile
Normal file
24
pkg/e2e/fixtures/build-test/ssh/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
# syntax=docker/dockerfile:1.2
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
FROM alpine
|
||||
RUN apk add --no-cache openssh-client
|
||||
|
||||
WORKDIR /compose
|
||||
COPY fake_rsa.pub /compose/
|
||||
|
||||
RUN --mount=type=ssh,id=fake-ssh,required=true diff <(ssh-add -L) <(cat /compose/fake_rsa.pub)
|
||||
5
pkg/e2e/fixtures/build-test/ssh/compose-without-ssh.yaml
Normal file
5
pkg/e2e/fixtures/build-test/ssh/compose-without-ssh.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
ssh:
|
||||
image: build-test-ssh
|
||||
build:
|
||||
context: .
|
||||
7
pkg/e2e/fixtures/build-test/ssh/compose.yaml
Normal file
7
pkg/e2e/fixtures/build-test/ssh/compose.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
ssh:
|
||||
image: build-test-ssh
|
||||
build:
|
||||
context: .
|
||||
ssh:
|
||||
- fake-ssh=./fixtures/build-test/ssh/fake_rsa
|
||||
49
pkg/e2e/fixtures/build-test/ssh/fake_rsa
Normal file
49
pkg/e2e/fixtures/build-test/ssh/fake_rsa
Normal file
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAgEA7nJ4xAhJ7VwI63tuay3DCHaTXeEY92H6YNZ8ptAIBY0mUn6Gc9ms
|
||||
94HvcAKemCJkO0fy6U2JOoST+q1YPAJf86NrIU41hZdzrw2QdqG/A3ja4VTAaOJbH9wafK
|
||||
HpWLs6kyigGti3KSBabm4HARU8lgtRE6AuCC1+mw821FzTsMWMxRp/rKVxgsiMUsdd57WR
|
||||
KOdn8TRm6NHcEsy7X7zAJ7+Ch/muGGCCk3Z9+YUzoVVtY/wGYmWXXj/NUzxnEq0XLyO8HC
|
||||
+QU/9dWlh1OLmoMuxN1lYtHRFWWstCboKNsOcIiJsLKfQ1t4z4jXq5P7JTLE5Pngemrr4x
|
||||
K21RFjVaGQpOjyQgZn1o0wAvy78KORwgN0Elwcb/XIKJepzzezCIyXlSafeXuHP+oMjM2s
|
||||
2MXNHlMKv6Jwh4QYwUQ61+bAcPkcmIdltiAMNLcxYiqEud85EQQl9ciuhMKa0bZl1OEILw
|
||||
VSIasEu9BEKVrz52ZZVLGMchqOV/4f1PqPEagnfnRYEttJ6AuaYUaJXvSQP6Zj4AFb6WrP
|
||||
wEBIFOuAH9i4WtG52QAK6uc1wsPZlHm8J+VnTEBKFuGERu/uJBWPo43Lju8VrHuZU8QeON
|
||||
ERKfJbc1EI9XpqWi+3VcWT0QJtxEGW2YmD505+cKNc31xwOtcqwogtwT0wnuj0BAf33HY3
|
||||
8AAAc465v1nOub9ZwAAAAHc3NoLXJzYQAAAgEA7nJ4xAhJ7VwI63tuay3DCHaTXeEY92H6
|
||||
YNZ8ptAIBY0mUn6Gc9ms94HvcAKemCJkO0fy6U2JOoST+q1YPAJf86NrIU41hZdzrw2Qdq
|
||||
G/A3ja4VTAaOJbH9wafKHpWLs6kyigGti3KSBabm4HARU8lgtRE6AuCC1+mw821FzTsMWM
|
||||
xRp/rKVxgsiMUsdd57WRKOdn8TRm6NHcEsy7X7zAJ7+Ch/muGGCCk3Z9+YUzoVVtY/wGYm
|
||||
WXXj/NUzxnEq0XLyO8HC+QU/9dWlh1OLmoMuxN1lYtHRFWWstCboKNsOcIiJsLKfQ1t4z4
|
||||
jXq5P7JTLE5Pngemrr4xK21RFjVaGQpOjyQgZn1o0wAvy78KORwgN0Elwcb/XIKJepzzez
|
||||
CIyXlSafeXuHP+oMjM2s2MXNHlMKv6Jwh4QYwUQ61+bAcPkcmIdltiAMNLcxYiqEud85EQ
|
||||
Ql9ciuhMKa0bZl1OEILwVSIasEu9BEKVrz52ZZVLGMchqOV/4f1PqPEagnfnRYEttJ6Aua
|
||||
YUaJXvSQP6Zj4AFb6WrPwEBIFOuAH9i4WtG52QAK6uc1wsPZlHm8J+VnTEBKFuGERu/uJB
|
||||
WPo43Lju8VrHuZU8QeONERKfJbc1EI9XpqWi+3VcWT0QJtxEGW2YmD505+cKNc31xwOtcq
|
||||
wogtwT0wnuj0BAf33HY38AAAADAQABAAACAGK7A0YoKHQfp5HZid7XE+ptLpewnKXR69os
|
||||
9XAcszWZPETsHr/ZYcUaCApZC1Hy642gPPRdJnUUcDFblS1DzncTM0iXGZI3I69X7nkwf+
|
||||
bwI7EpZoIHN7P5bv4sDHKxE4/bQm/bS/u7abZP2JaaNHvsM6XsrSK1s7aAljNYPE71fVQf
|
||||
pL3Xwyhj4bZk1n0asQA+0MsO541/V6BxJSR/AxFyOpoSyANP8sEcTw0CGl6zAJhlwj770b
|
||||
E0uc+9MvCIuxDJuxnwl9Iv6nd+KQtT1FFBhvk4tXVTuG3fu6IGbKTTBLWLfRPiClv2AvSR
|
||||
3CKDs+ykgFLu2BWCqtlQakLH1IW9DTkPExV4ZjkGCRWHEvmJxxOqL6B48tBjwa5gBuPJRA
|
||||
aYRi15Z3sprsqCBfp+aHPkMXkkNGSe5ROj8lFFY/f50ZS/9DSlyuUURFLtIGe5XuPNJk7L
|
||||
xJkYJAdNbgvk4IPgzsU2EuYvSja5mtuo3dVyEIRtsIAN4xl01edDAxHEow6ar4gZCtXnBb
|
||||
WqeqchEi4zVTdkkuDP3SF362pktdY7Op0mS/yFd8LFrca3VCy2PqNhKvlxClRqM9Tlp9cY
|
||||
qDuyS9AGT1QO4BMtvSJGFa3P+h76rQsNldC+nGa4wNWvpAUcT5NS8W9QnGp7ah/qOK07t7
|
||||
fwYYENeRaAK3OItBABAAABAFjyDlnERaZ+/23B+zN0kQhCvmiNS5HE2+ooR5ofX08F3Uar
|
||||
VPevy9p6s2LA+AlXY1ZZ1k0p5MI+4TkAbcB/VXaxrRUw9633p9rAgyumFGhK3i0M4whOCO
|
||||
MJxmlp5sz5Qea+YzIa9z0F4ZwwvdHt7cp5joYBZoQ+Kv9OUy4xCs1zZ4ZbEsakGBrtLiTo
|
||||
H3odXSg0mXQf10Ae3WkvAJ8M1xL/z1ryFeCvyv1sGwEx+5gvmZ6nnuJEEuXUBlpOwhPlST
|
||||
4X9VL7gmdH9OoHnhUn3q2JEBQdVTegGij9wvoYT1bdzwBN/Amisn29K9w1aNdrNbYUJ6PO
|
||||
0kE2lotSJ11qD8MAAAEBAP6IRuU25yj7zv0mEsaRWoQ5v3fYKKn4C6Eg3DbzKXybZkLyX7
|
||||
6QlyO7uWf54SdXM7sQW8KoXaMu9qbo/o+4o3m7YfOY1MYeTz3yICYObVA7Fc9ZHwKzc1PB
|
||||
dFNzy6/G+2niNQF3Q1Fjp31Ve9LwKJK8Kj/eUYZ3QiUIropkw4ppA8q3h+nkVGS23xSrTM
|
||||
kGLugBjcnWUfuN0tKx/b5mqziRoyzr5u0qzFDtx97QAyETo/onFrd1bMGED2BHVyrCwtqI
|
||||
p6SXo2uFzwm/nLtOMlmfpixNcK6dtql/brx3Lsu18a+0a42O5Q/TYRdRq8D60O16rUS/LN
|
||||
sFOjIYSA3spnUAAAEBAO/Sc3NTarFylk+yhOTE8G9xDt5ndbY0gsfhM9D4byKlY4yYIvs+
|
||||
yQAq3UHgSoN2f087zNubXSNiLJ8TOIPpbk8MzdvjqcpmnBhHcd4V2FLe9+hC8zEBf8MPPf
|
||||
Cy1kXdCZ0bDMLTdgONiDTIc/0YXhFLZherXNIF1o/7Pcnu6IPwMDl/gcG3H1ncDxaLqxAm
|
||||
L29SDXLX2hH9k+YJr9kFaho7PZBAwNYnMooupROSbQ9/lmfCt09ep/83n5G0mo93uGkyV2
|
||||
1wcQw9X2ZT8eVHZ4ni3ACC6VYbUn2M3Z+e3tpGaYzKXd/yq0YyppoRvEaxM/ewXappUJul
|
||||
Xsd/RqSc66MAAAAAAQID
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
pkg/e2e/fixtures/build-test/ssh/fake_rsa.pub
Normal file
1
pkg/e2e/fixtures/build-test/ssh/fake_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDucnjECEntXAjre25rLcMIdpNd4Rj3Yfpg1nym0AgFjSZSfoZz2az3ge9wAp6YImQ7R/LpTYk6hJP6rVg8Al/zo2shTjWFl3OvDZB2ob8DeNrhVMBo4lsf3Bp8oelYuzqTKKAa2LcpIFpubgcBFTyWC1EToC4ILX6bDzbUXNOwxYzFGn+spXGCyIxSx13ntZEo52fxNGbo0dwSzLtfvMAnv4KH+a4YYIKTdn35hTOhVW1j/AZiZZdeP81TPGcSrRcvI7wcL5BT/11aWHU4uagy7E3WVi0dEVZay0Jugo2w5wiImwsp9DW3jPiNerk/slMsTk+eB6auvjErbVEWNVoZCk6PJCBmfWjTAC/Lvwo5HCA3QSXBxv9cgol6nPN7MIjJeVJp95e4c/6gyMzazYxc0eUwq/onCHhBjBRDrX5sBw+RyYh2W2IAw0tzFiKoS53zkRBCX1yK6EwprRtmXU4QgvBVIhqwS70EQpWvPnZllUsYxyGo5X/h/U+o8RqCd+dFgS20noC5phRole9JA/pmPgAVvpas/AQEgU64Af2Lha0bnZAArq5zXCw9mUebwn5WdMQEoW4YRG7+4kFY+jjcuO7xWse5lTxB440REp8ltzUQj1empaL7dVxZPRAm3EQZbZiYPnTn5wo1zfXHA61yrCiC3BPTCe6PQEB/fcdjfw==
|
||||
17
pkg/e2e/fixtures/build-test/tags/Dockerfile
Normal file
17
pkg/e2e/fixtures/build-test/tags/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
RUN echo "SUCCESS"
|
||||
9
pkg/e2e/fixtures/build-test/tags/compose.yaml
Normal file
9
pkg/e2e/fixtures/build-test/tags/compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
nginx:
|
||||
image: build-test-tags
|
||||
build:
|
||||
context: .
|
||||
tags:
|
||||
- docker.io/docker/build-test-tags:1.0.0
|
||||
- other-image-name:v1.0.0
|
||||
|
||||
11
pkg/e2e/fixtures/env-secret/compose.yaml
Normal file
11
pkg/e2e/fixtures/env-secret/compose.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
foo:
|
||||
image: alpine
|
||||
secrets:
|
||||
- bar
|
||||
command: cat /run/secrets/bar
|
||||
|
||||
secrets:
|
||||
bar:
|
||||
environment: SECRET
|
||||
|
||||
2
pkg/e2e/fixtures/environment/env-file-comments/.env
Normal file
2
pkg/e2e/fixtures/environment/env-file-comments/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
COMMENT=1234#5
|
||||
NO_COMMENT="1234#5"
|
||||
18
pkg/e2e/fixtures/environment/env-file-comments/Dockerfile
Normal file
18
pkg/e2e/fixtures/environment/env-file-comments/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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.
|
||||
|
||||
FROM alpine
|
||||
ENV COMMENT=Dockerfile
|
||||
ENV NO_COMMENT=Dockerfile
|
||||
CMD ["sh", "-c", "printenv", "|", "grep", "COMMENT"]
|
||||
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
env-file-comments:
|
||||
build:
|
||||
context: .
|
||||
image: env-file-comments
|
||||
2
pkg/e2e/fixtures/environment/env-interpolation/.env
Normal file
2
pkg/e2e/fixtures/environment/env-interpolation/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
WHEREAMI=Env File
|
||||
IMAGE=default_env:${WHEREAMI}
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
env-interpolation:
|
||||
image: bash
|
||||
environment:
|
||||
IMAGE: ${IMAGE}
|
||||
command: echo "$IMAGE"
|
||||
1
pkg/e2e/fixtures/environment/env-priority/.env
Normal file
1
pkg/e2e/fixtures/environment/env-priority/.env
Normal file
@@ -0,0 +1 @@
|
||||
WHEREAMI=Env File
|
||||
1
pkg/e2e/fixtures/environment/env-priority/.env.override
Normal file
1
pkg/e2e/fixtures/environment/env-priority/.env.override
Normal file
@@ -0,0 +1 @@
|
||||
WHEREAMI=override
|
||||
17
pkg/e2e/fixtures/environment/env-priority/Dockerfile
Normal file
17
pkg/e2e/fixtures/environment/env-priority/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
|
||||
FROM alpine
|
||||
ENV WHEREAMI=Dockerfile
|
||||
CMD ["printenv", "WHEREAMI"]
|
||||
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
env-compose-priority:
|
||||
image: env-compose-priority
|
||||
build:
|
||||
context: .
|
||||
environment:
|
||||
WHEREAMI: "Compose File"
|
||||
3
pkg/e2e/fixtures/environment/env-priority/compose.yaml
Normal file
3
pkg/e2e/fixtures/environment/env-priority/compose.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
services:
|
||||
env-compose-priority:
|
||||
image: env-compose-priority
|
||||
8
pkg/e2e/fixtures/links/compose.yaml
Normal file
8
pkg/e2e/fixtures/links/compose.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
foo:
|
||||
image: nginx:alpine
|
||||
links:
|
||||
- bar
|
||||
|
||||
bar:
|
||||
image: nginx:alpine
|
||||
9
pkg/e2e/fixtures/network-links/compose.yaml
Normal file
9
pkg/e2e/fixtures/network-links/compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
container1:
|
||||
image: nginx
|
||||
network_mode: bridge
|
||||
container2:
|
||||
image: nginx
|
||||
network_mode: bridge
|
||||
links:
|
||||
- container1
|
||||
14
pkg/e2e/fixtures/project-volume-bind-test/docker-compose.yml
Normal file
14
pkg/e2e/fixtures/project-volume-bind-test/docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
frontend:
|
||||
image: nginx
|
||||
container_name: frontend
|
||||
volumes:
|
||||
- project_data:/data
|
||||
|
||||
volumes:
|
||||
project_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: "${TEST_DIR}"
|
||||
12
pkg/e2e/fixtures/ps-test/compose.yaml
Normal file
12
pkg/e2e/fixtures/ps-test/compose.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
expose:
|
||||
- '80'
|
||||
- '443'
|
||||
- '8080'
|
||||
busybox:
|
||||
image: busybox
|
||||
command: busybox httpd -f -p 8000
|
||||
ports:
|
||||
- '127.0.0.1:8001:8000'
|
||||
14
pkg/e2e/fixtures/run-test/deps.yaml
Normal file
14
pkg/e2e/fixtures/run-test/deps.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
version: "3.6"
|
||||
services:
|
||||
service_a:
|
||||
image: bash
|
||||
command: echo "a"
|
||||
depends_on:
|
||||
- shared_dep
|
||||
service_b:
|
||||
image: bash
|
||||
command: echo "b"
|
||||
depends_on:
|
||||
- shared_dep
|
||||
shared_dep:
|
||||
image: bash
|
||||
@@ -29,12 +29,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -56,54 +57,85 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// E2eCLI is used to wrap the CLI for end to end testing
|
||||
// nolint stutter
|
||||
type E2eCLI struct {
|
||||
BinDir string
|
||||
// CLI is used to wrap the CLI for end to end testing
|
||||
type CLI struct {
|
||||
// ConfigDir for Docker configuration (set as DOCKER_CONFIG)
|
||||
ConfigDir string
|
||||
test *testing.T
|
||||
|
||||
// HomeDir for tools that look for user files (set as HOME)
|
||||
HomeDir string
|
||||
|
||||
// env overrides to apply to every invoked command
|
||||
//
|
||||
// To populate, use WithEnv when creating a CLI instance.
|
||||
env []string
|
||||
}
|
||||
|
||||
// NewParallelE2eCLI returns a configured TestE2eCLI with t.Parallel() set
|
||||
func NewParallelE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
// CLIOption to customize behavior for all commands for a CLI instance.
|
||||
type CLIOption func(c *CLI)
|
||||
|
||||
// NewParallelCLI marks the parent test as parallel and returns a CLI instance
|
||||
// suitable for usage across child tests.
|
||||
func NewParallelCLI(t *testing.T, opts ...CLIOption) *CLI {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
return newE2eCLI(t, binDir)
|
||||
return NewCLI(t, opts...)
|
||||
}
|
||||
|
||||
func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
d, err := ioutil.TempDir("", "")
|
||||
assert.Check(t, is.Nil(err))
|
||||
// NewCLI creates a CLI instance for running E2E tests.
|
||||
func NewCLI(t testing.TB, opts ...CLIOption) *CLI {
|
||||
t.Helper()
|
||||
|
||||
configDir := t.TempDir()
|
||||
initializePlugins(t, configDir)
|
||||
|
||||
c := &CLI{
|
||||
ConfigDir: configDir,
|
||||
HomeDir: t.TempDir(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEnv sets environment variables that will be passed to commands.
|
||||
func WithEnv(env ...string) CLIOption {
|
||||
return func(c *CLI) {
|
||||
c.env = append(c.env, env...)
|
||||
}
|
||||
}
|
||||
|
||||
// initializePlugins copies the necessary plugin files to the temporary config
|
||||
// directory for the test.
|
||||
func initializePlugins(t testing.TB, configDir string) {
|
||||
t.Helper()
|
||||
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
conf, _ := ioutil.ReadFile(filepath.Join(d, "config.json"))
|
||||
t.Errorf("Config: %s\n", string(conf))
|
||||
t.Error("Contents of config dir:")
|
||||
for _, p := range dirContents(d) {
|
||||
t.Errorf(p)
|
||||
if conf, err := ioutil.ReadFile(filepath.Join(configDir, "config.json")); err == nil {
|
||||
t.Logf("Config: %s\n", string(conf))
|
||||
}
|
||||
t.Log("Contents of config dir:")
|
||||
for _, p := range dirContents(configDir) {
|
||||
t.Logf(" - %s", p)
|
||||
}
|
||||
}
|
||||
_ = os.RemoveAll(d)
|
||||
})
|
||||
|
||||
_ = os.MkdirAll(filepath.Join(d, "cli-plugins"), 0755)
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(configDir, "cli-plugins"), 0755),
|
||||
"Failed to create cli-plugins directory")
|
||||
composePlugin, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("WARNING: docker-compose cli-plugin not found")
|
||||
t.Logf("WARNING: docker-compose cli-plugin not found")
|
||||
}
|
||||
if err == nil {
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerComposeExecutableName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
CopyFile(t, composePlugin, filepath.Join(configDir, "cli-plugins", DockerComposeExecutableName))
|
||||
// We don't need a functional scan plugin, but a valid plugin binary
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerScanExecutableName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
CopyFile(t, composePlugin, filepath.Join(configDir, "cli-plugins", DockerScanExecutableName))
|
||||
}
|
||||
|
||||
return &E2eCLI{binDir, d, t}
|
||||
}
|
||||
|
||||
func dirContents(dir string) []string {
|
||||
@@ -133,88 +165,145 @@ func findExecutable(executableName string, paths []string) (string, error) {
|
||||
}
|
||||
|
||||
// CopyFile copies a file from a sourceFile to a destinationFile setting permissions to 0755
|
||||
func CopyFile(sourceFile string, destinationFile string) error {
|
||||
func CopyFile(t testing.TB, sourceFile string, destinationFile string) {
|
||||
t.Helper()
|
||||
|
||||
src, err := os.Open(sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
require.NoError(t, err, "Failed to open source file: %s")
|
||||
// nolint: errcheck
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.OpenFile(destinationFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
require.NoError(t, err, "Failed to open destination file: %s", destinationFile)
|
||||
// nolint: errcheck
|
||||
defer dst.Close()
|
||||
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(dst, src)
|
||||
require.NoError(t, err, "Failed to copy file: %s", sourceFile)
|
||||
}
|
||||
|
||||
return err
|
||||
// BaseEnvironment provides the minimal environment variables used across all
|
||||
// Docker / Compose commands.
|
||||
func (c *CLI) BaseEnvironment() []string {
|
||||
return []string{
|
||||
"HOME=" + c.HomeDir,
|
||||
"USER=" + os.Getenv("USER"),
|
||||
"DOCKER_CONFIG=" + c.ConfigDir,
|
||||
"KUBECONFIG=invalid",
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmd creates a cmd object configured with the test environment set
|
||||
func (c *E2eCLI) NewCmd(command string, args ...string) icmd.Cmd {
|
||||
env := append(os.Environ(),
|
||||
"DOCKER_CONFIG="+c.ConfigDir,
|
||||
"KUBECONFIG=invalid",
|
||||
)
|
||||
func (c *CLI) NewCmd(command string, args ...string) icmd.Cmd {
|
||||
return icmd.Cmd{
|
||||
Command: append([]string{command}, args...),
|
||||
Env: env,
|
||||
Env: append(c.BaseEnvironment(), c.env...),
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdWithEnv creates a cmd object configured with the test environment set with additional env vars
|
||||
func (c *CLI) NewCmdWithEnv(envvars []string, command string, args ...string) icmd.Cmd {
|
||||
// base env -> CLI overrides -> cmd overrides
|
||||
cmdEnv := append(c.BaseEnvironment(), c.env...)
|
||||
cmdEnv = append(cmdEnv, envvars...)
|
||||
return icmd.Cmd{
|
||||
Command: append([]string{command}, args...),
|
||||
Env: cmdEnv,
|
||||
}
|
||||
}
|
||||
|
||||
// MetricsSocket get the path where test metrics will be sent
|
||||
func (c *E2eCLI) MetricsSocket() string {
|
||||
func (c *CLI) MetricsSocket() string {
|
||||
return filepath.Join(c.ConfigDir, "./docker-cli.sock")
|
||||
}
|
||||
|
||||
// NewDockerCmd creates a docker cmd without running it
|
||||
func (c *E2eCLI) NewDockerCmd(args ...string) icmd.Cmd {
|
||||
func (c *CLI) NewDockerCmd(t testing.TB, args ...string) icmd.Cmd {
|
||||
t.Helper()
|
||||
for _, arg := range args {
|
||||
if arg == compose.PluginName {
|
||||
t.Fatal("This test called 'RunDockerCmd' for 'compose'. Please prefer 'RunDockerComposeCmd' to be able to test as a plugin and standalone")
|
||||
}
|
||||
}
|
||||
return c.NewCmd(DockerExecutableName, args...)
|
||||
}
|
||||
|
||||
// RunDockerOrExitError runs a docker command and returns a result
|
||||
func (c *E2eCLI) RunDockerOrExitError(args ...string) *icmd.Result {
|
||||
fmt.Printf("\t[%s] docker %s\n", c.test.Name(), strings.Join(args, " "))
|
||||
return icmd.RunCmd(c.NewDockerCmd(args...))
|
||||
func (c *CLI) RunDockerOrExitError(t testing.TB, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
t.Logf("\t[%s] docker %s\n", t.Name(), strings.Join(args, " "))
|
||||
return icmd.RunCmd(c.NewDockerCmd(t, args...))
|
||||
}
|
||||
|
||||
// RunCmd runs a command, expects no error and returns a result
|
||||
func (c *E2eCLI) RunCmd(args ...string) *icmd.Result {
|
||||
fmt.Printf("\t[%s] %s\n", c.test.Name(), strings.Join(args, " "))
|
||||
assert.Assert(c.test, len(args) >= 1, "require at least one command in parameters")
|
||||
func (c *CLI) RunCmd(t testing.TB, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
t.Logf("\t[%s] %s\n", t.Name(), strings.Join(args, " "))
|
||||
assert.Assert(t, len(args) >= 1, "require at least one command in parameters")
|
||||
res := icmd.RunCmd(c.NewCmd(args[0], args[1:]...))
|
||||
res.Assert(c.test, icmd.Success)
|
||||
res.Assert(t, icmd.Success)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunCmdInDir runs a command in a given dir, expects no error and returns a result
|
||||
func (c *CLI) RunCmdInDir(t testing.TB, dir string, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
t.Logf("\t[%s] %s\n", t.Name(), strings.Join(args, " "))
|
||||
assert.Assert(t, len(args) >= 1, "require at least one command in parameters")
|
||||
cmd := c.NewCmd(args[0], args[1:]...)
|
||||
cmd.Dir = dir
|
||||
res := icmd.RunCmd(cmd)
|
||||
res.Assert(t, icmd.Success)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunDockerCmd runs a docker command, expects no error and returns a result
|
||||
func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
|
||||
if len(args) > 0 && args[0] == compose.PluginName {
|
||||
c.test.Fatal("This test called 'RunDockerCmd' for 'compose'. Please prefer 'RunDockerComposeCmd' to be able to test as a plugin and standalone")
|
||||
}
|
||||
res := c.RunDockerOrExitError(args...)
|
||||
res.Assert(c.test, icmd.Success)
|
||||
func (c *CLI) RunDockerCmd(t testing.TB, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
res := c.RunDockerOrExitError(t, args...)
|
||||
res.Assert(t, icmd.Success)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
|
||||
func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
|
||||
func (c *CLI) RunDockerComposeCmd(t testing.TB, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
res := c.RunDockerComposeCmdNoCheck(t, args...)
|
||||
res.Assert(t, icmd.Success)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunDockerComposeCmdNoCheck runs a docker compose command, don't presume of any expectation and returns a result
|
||||
func (c *CLI) RunDockerComposeCmdNoCheck(t testing.TB, args ...string) *icmd.Result {
|
||||
t.Helper()
|
||||
return icmd.RunCmd(c.NewDockerComposeCmd(t, args...))
|
||||
}
|
||||
|
||||
// NewDockerComposeCmd creates a command object for Compose, either in plugin
|
||||
// or standalone mode (based on build tags).
|
||||
func (c *CLI) NewDockerComposeCmd(t testing.TB, args ...string) icmd.Cmd {
|
||||
t.Helper()
|
||||
if composeStandaloneMode {
|
||||
composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
|
||||
assert.NilError(c.test, err)
|
||||
res := icmd.RunCmd(c.NewCmd(composeBinary, args...))
|
||||
res.Assert(c.test, icmd.Success)
|
||||
return res
|
||||
return c.NewCmd(ComposeStandalonePath(t), args...)
|
||||
}
|
||||
args = append([]string{"compose"}, args...)
|
||||
res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
|
||||
res.Assert(c.test, icmd.Success)
|
||||
return res
|
||||
return c.NewCmd(DockerExecutableName, args...)
|
||||
}
|
||||
|
||||
// ComposeStandalonePath returns the path to the locally-built Compose
|
||||
// standalone binary from the repo.
|
||||
//
|
||||
// This function will fail the test immediately if invoked when not running
|
||||
// in standalone test mode.
|
||||
func ComposeStandalonePath(t testing.TB) string {
|
||||
t.Helper()
|
||||
if !composeStandaloneMode {
|
||||
require.Fail(t, "Not running in standalone mode")
|
||||
}
|
||||
composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
|
||||
require.NoError(t, err, "Could not find standalone Compose binary (%q)",
|
||||
DockerComposeExecutableName)
|
||||
return composeBinary
|
||||
}
|
||||
|
||||
// StdoutContains returns a predicate on command result expecting a string in stdout
|
||||
@@ -225,22 +314,35 @@ func StdoutContains(expected string) func(*icmd.Result) bool {
|
||||
}
|
||||
|
||||
// WaitForCmdResult try to execute a cmd until resulting output matches given predicate
|
||||
func (c *E2eCLI) WaitForCmdResult(command icmd.Cmd, predicate func(*icmd.Result) bool, timeout time.Duration, delay time.Duration) {
|
||||
assert.Assert(c.test, timeout.Nanoseconds() > delay.Nanoseconds(), "timeout must be greater than delay")
|
||||
func (c *CLI) WaitForCmdResult(
|
||||
t testing.TB,
|
||||
command icmd.Cmd,
|
||||
predicate func(*icmd.Result) bool,
|
||||
timeout time.Duration,
|
||||
delay time.Duration,
|
||||
) {
|
||||
t.Helper()
|
||||
assert.Assert(t, timeout.Nanoseconds() > delay.Nanoseconds(), "timeout must be greater than delay")
|
||||
var res *icmd.Result
|
||||
checkStopped := func(logt poll.LogT) poll.Result {
|
||||
fmt.Printf("\t[%s] %s\n", c.test.Name(), strings.Join(command.Command, " "))
|
||||
fmt.Printf("\t[%s] %s\n", t.Name(), strings.Join(command.Command, " "))
|
||||
res = icmd.RunCmd(command)
|
||||
if !predicate(res) {
|
||||
return poll.Continue("Cmd output did not match requirement: %q", res.Combined())
|
||||
}
|
||||
return poll.Success()
|
||||
}
|
||||
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
|
||||
poll.WaitOn(t, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
|
||||
}
|
||||
|
||||
// WaitForCondition wait for predicate to execute to true
|
||||
func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.Duration, delay time.Duration) {
|
||||
func (c *CLI) WaitForCondition(
|
||||
t testing.TB,
|
||||
predicate func() (bool, string),
|
||||
timeout time.Duration,
|
||||
delay time.Duration,
|
||||
) {
|
||||
t.Helper()
|
||||
checkStopped := func(logt poll.LogT) poll.Result {
|
||||
pass, description := predicate()
|
||||
if !pass {
|
||||
@@ -248,7 +350,7 @@ func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.
|
||||
}
|
||||
return poll.Success()
|
||||
}
|
||||
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
|
||||
poll.WaitOn(t, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
|
||||
}
|
||||
|
||||
// Lines split output into lines
|
||||
@@ -257,9 +359,16 @@ func Lines(output string) []string {
|
||||
}
|
||||
|
||||
// HTTPGetWithRetry performs an HTTP GET on an `endpoint`, using retryDelay also as a request timeout.
|
||||
// In the case of an error or the response status is not the expeted one, it retries the same request,
|
||||
// In the case of an error or the response status is not the expected one, it retries the same request,
|
||||
// returning the response body as a string (empty if we could not reach it)
|
||||
func HTTPGetWithRetry(t *testing.T, endpoint string, expectedStatus int, retryDelay time.Duration, timeout time.Duration) string {
|
||||
func HTTPGetWithRetry(
|
||||
t testing.TB,
|
||||
endpoint string,
|
||||
expectedStatus int,
|
||||
retryDelay time.Duration,
|
||||
timeout time.Duration,
|
||||
) string {
|
||||
t.Helper()
|
||||
var (
|
||||
r *http.Response
|
||||
err error
|
||||
|
||||
@@ -25,39 +25,40 @@ import (
|
||||
)
|
||||
|
||||
func TestIPC(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "ipc_e2e"
|
||||
var cid string
|
||||
t.Run("create ipc mode container", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("run", "-d", "--rm", "--ipc=shareable", "--name", "ipc_mode_container", "alpine", "top")
|
||||
res := c.RunDockerCmd(t, "run", "-d", "--rm", "--ipc=shareable", "--name", "ipc_mode_container", "alpine",
|
||||
"top")
|
||||
cid = strings.Trim(res.Stdout(), "\n")
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check running project", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `shareable`})
|
||||
})
|
||||
|
||||
t.Run("check ipcmode in container inspect", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("inspect", projectName+"-shareable-1")
|
||||
res := c.RunDockerCmd(t, "inspect", projectName+"-shareable-1")
|
||||
res.Assert(t, icmd.Expected{Out: `"IpcMode": "shareable",`})
|
||||
|
||||
res = c.RunDockerCmd("inspect", projectName+"-service-1")
|
||||
res = c.RunDockerCmd(t, "inspect", projectName+"-service-1")
|
||||
res.Assert(t, icmd.Expected{Out: `"IpcMode": "container:`})
|
||||
|
||||
res = c.RunDockerCmd("inspect", projectName+"-container-1")
|
||||
res = c.RunDockerCmd(t, "inspect", projectName+"-container-1")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"IpcMode": "container:%s",`, cid)})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
t.Run("remove ipc mode container", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("rm", "-f", "ipc_mode_container")
|
||||
_ = c.RunDockerCmd(t, "rm", "-f", "ipc_mode_container")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,33 +26,33 @@ import (
|
||||
)
|
||||
|
||||
func TestLocalComposeLogs(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-logs"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("logs", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("--project-name", projectName, "logs")
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "logs")
|
||||
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
|
||||
res.Assert(t, icmd.Expected{Out: `hello`})
|
||||
})
|
||||
|
||||
t.Run("logs ping", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("--project-name", projectName, "logs", "ping")
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "logs", "ping")
|
||||
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "hello"))
|
||||
})
|
||||
|
||||
t.Run("logs hello", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("--project-name", projectName, "logs", "hello", "ping")
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "logs", "hello", "ping")
|
||||
res.Assert(t, icmd.Expected{Out: `PING localhost (127.0.0.1)`})
|
||||
res.Assert(t, icmd.Expected{Out: `hello`})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,32 +24,32 @@ import (
|
||||
)
|
||||
|
||||
func TestComposeMetrics(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("catch specific failure metrics", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("compose", "-f", "fixtures/does-not-exist/compose.yaml", "build")
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/does-not-exist/compose.yaml", "build")
|
||||
expectedErr := "fixtures/does-not-exist/compose.yaml: no such file or directory"
|
||||
if runtime.GOOS == "windows" {
|
||||
expectedErr = "does-not-exist\\compose.yaml: The system cannot find the path specified"
|
||||
}
|
||||
res.Assert(t, icmd.Expected{ExitCode: 14, Err: expectedErr})
|
||||
res = c.RunDockerOrExitError("compose", "-f", "fixtures/wrong-composefile/compose.yaml", "up", "-d")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/wrong-composefile/compose.yaml", "up", "-d")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 15, Err: "services.simple Additional property wrongField is not allowed"})
|
||||
res = c.RunDockerOrExitError("compose", "up")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "up")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 14, Err: "no configuration file provided: not found"})
|
||||
res = c.RunDockerOrExitError("compose", "up", "-f", "fixtures/wrong-composefile/compose.yaml")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "up", "-f", "fixtures/wrong-composefile/compose.yaml")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 16, Err: "unknown shorthand flag: 'f' in -f"})
|
||||
res = c.RunDockerOrExitError("compose", "up", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "up", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 16, Err: "unknown flag: --file"})
|
||||
res = c.RunDockerOrExitError("compose", "donw", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "donw", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 16, Err: `unknown docker command: "compose donw"`})
|
||||
res = c.RunDockerOrExitError("compose", "--file", "fixtures/wrong-composefile/build-error.yml", "build")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "--file", "fixtures/wrong-composefile/build-error.yml", "build")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 17, Err: `line 17: unknown instruction: WRONG`})
|
||||
res = c.RunDockerOrExitError("compose", "--file", "fixtures/wrong-composefile/build-error.yml", "up")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "--file", "fixtures/wrong-composefile/build-error.yml", "up")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 17, Err: `line 17: unknown instruction: WRONG`})
|
||||
res = c.RunDockerOrExitError("compose", "--file", "fixtures/wrong-composefile/unknown-image.yml", "pull")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "--file", "fixtures/wrong-composefile/unknown-image.yml", "pull")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 18, Err: `pull access denied for unknownimage, repository does not exist or may require 'docker login'`})
|
||||
res = c.RunDockerOrExitError("compose", "--file", "fixtures/wrong-composefile/unknown-image.yml", "up")
|
||||
res = c.RunDockerComposeCmdNoCheck(t, "--file", "fixtures/wrong-composefile/unknown-image.yml", "up")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 18, Err: `pull access denied for unknownimage, repository does not exist or may require 'docker login'`})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,107 +27,135 @@ import (
|
||||
)
|
||||
|
||||
func TestNetworks(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
// fixture is shared with TestNetworkModes and is not safe to run concurrently
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "network_e2e"
|
||||
|
||||
t.Run("ensure we do not reuse previous networks", func(t *testing.T) {
|
||||
c.RunDockerOrExitError("network", "rm", projectName+"_dbnet")
|
||||
c.RunDockerOrExitError("network", "rm", "microservices")
|
||||
c.RunDockerOrExitError(t, "network", "rm", projectName+"_dbnet")
|
||||
c.RunDockerOrExitError(t, "network", "rm", "microservices")
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up",
|
||||
"-d")
|
||||
})
|
||||
|
||||
t.Run("check running project", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd(t, "-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
|
||||
endpoint := "http://localhost:80"
|
||||
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
|
||||
assert.Assert(t, strings.Contains(output, `"word":`))
|
||||
|
||||
res = c.RunDockerCmd("network", "ls")
|
||||
res = c.RunDockerCmd(t, "network", "ls")
|
||||
res.Assert(t, icmd.Expected{Out: projectName + "_dbnet"})
|
||||
res.Assert(t, icmd.Expected{Out: "microservices"})
|
||||
})
|
||||
|
||||
t.Run("port", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("--project-name", projectName, "port", "words", "8080")
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "port", "words", "8080")
|
||||
res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
t.Run("check networks after down", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("network", "ls")
|
||||
res := c.RunDockerCmd(t, "network", "ls")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "microservices"), res.Combined())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetworkAliassesAndLinks(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
func TestNetworkAliases(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "network_alias_e2e"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up",
|
||||
"-d")
|
||||
})
|
||||
|
||||
t.Run("curl alias", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://alias-of-container2/")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName,
|
||||
"exec", "-T", "container1", "curl", "http://alias-of-container2/")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "Welcome to nginx!"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("curl links", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://container/")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName,
|
||||
"exec", "-T", "container1", "curl", "http://container/")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "Welcome to nginx!"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetworkLinks(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "network_link_e2e"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-links/compose.yaml", "--project-name", projectName, "up",
|
||||
"-d")
|
||||
})
|
||||
|
||||
t.Run("curl links in default bridge network", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-links/compose.yaml", "--project-name", projectName,
|
||||
"exec", "-T", "container2", "curl", "http://container1/")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "Welcome to nginx!"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIPAMConfig(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "ipam_e2e"
|
||||
|
||||
t.Run("ensure we do not reuse previous networks", func(t *testing.T) {
|
||||
c.RunDockerOrExitError("network", "rm", projectName+"_default")
|
||||
c.RunDockerOrExitError(t, "network", "rm", projectName+"_default")
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("ensure service get fixed IP assigned", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("inspect", projectName+"-foo-1", "-f", "{{ .NetworkSettings.Networks."+projectName+"_default.IPAddress }}")
|
||||
res := c.RunDockerCmd(t, "inspect", projectName+"-foo-1", "-f",
|
||||
"{{ .NetworkSettings.Networks."+projectName+"_default.IPAddress }}")
|
||||
res.Assert(t, icmd.Expected{Out: "10.1.0.100"})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNetworkModes(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
// fixture is shared with TestNetworks and is not safe to run concurrently
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "network_mode_service_run"
|
||||
|
||||
t.Run("run with service mode dependency", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("compose", "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "run", "-T", "mydb", "echo", "success")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "run", "-T", "mydb", "echo", "success")
|
||||
res.Assert(t, icmd.Expected{Out: "success"})
|
||||
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user