mirror of
https://github.com/docker/compose.git
synced 2026-02-10 02:29:25 +08:00
Compare commits
125 Commits
config_no_
...
v2.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2b9c81254 | ||
|
|
d75f22cc7b | ||
|
|
2282159922 | ||
|
|
8c7951465e | ||
|
|
9054f6a18b | ||
|
|
0ef4b90fae | ||
|
|
908c12120e | ||
|
|
dbe7de50a7 | ||
|
|
fcff39631a | ||
|
|
bcaa908f74 | ||
|
|
c64b044b7e | ||
|
|
1a4e6c2465 | ||
|
|
67b4669f9b | ||
|
|
61735c0012 | ||
|
|
c12a948f97 | ||
|
|
bf5785307b | ||
|
|
52eeda9aa7 | ||
|
|
1f0cf0723c | ||
|
|
315f16e5fb | ||
|
|
327a1bb27b | ||
|
|
16914e372e | ||
|
|
ec080f184a | ||
|
|
b5b8e7a116 | ||
|
|
9d73cc88cc | ||
|
|
9c68c76bea | ||
|
|
aeb7448449 | ||
|
|
42c3adb236 | ||
|
|
35f37cd1f7 | ||
|
|
c0465616bb | ||
|
|
32d44dfc25 | ||
|
|
5885a250bc | ||
|
|
ce1c788237 | ||
|
|
67f7b84829 | ||
|
|
fd676adc5d | ||
|
|
aa864fde20 | ||
|
|
f7a6c3bc54 | ||
|
|
8a9498c571 | ||
|
|
7e7262bc77 | ||
|
|
981aea674d | ||
|
|
64a9e4bf01 | ||
|
|
4be38f84df | ||
|
|
09e0fa94b8 | ||
|
|
416498441c | ||
|
|
cb45c6f2df | ||
|
|
90ca37344f | ||
|
|
fb3f9e270f | ||
|
|
02f78d2893 | ||
|
|
d47dcef1a6 | ||
|
|
fb9310caf2 | ||
|
|
ced9eba940 | ||
|
|
2eeed8481d | ||
|
|
10ca0314bc | ||
|
|
336b825fdd | ||
|
|
213d9166dc | ||
|
|
598b59f8bf | ||
|
|
65ed8cf4c2 | ||
|
|
a23cbb580e | ||
|
|
a89e194558 | ||
|
|
b31695a66e | ||
|
|
5262d3bbf5 | ||
|
|
a4836391a5 | ||
|
|
bfd7428619 | ||
|
|
feba34e406 | ||
|
|
37f763f009 | ||
|
|
99cd90a4b2 | ||
|
|
a279c3a934 | ||
|
|
ee586e7f1e | ||
|
|
5eb314a4ca | ||
|
|
c5cdce0b60 | ||
|
|
6dc6bedb60 | ||
|
|
9f06a02eb5 | ||
|
|
9d0421a929 | ||
|
|
340b5482b0 | ||
|
|
faaa93bf12 | ||
|
|
381df20010 | ||
|
|
a9e8164a8d | ||
|
|
1191023fb6 | ||
|
|
a108690ac2 | ||
|
|
0e81d1c88e | ||
|
|
22002531d8 | ||
|
|
b66ff0c3d8 | ||
|
|
7b6439997d | ||
|
|
d37b3fe413 | ||
|
|
ce7a2412b1 | ||
|
|
9e52e9fbe3 | ||
|
|
c5b7624d10 | ||
|
|
cf7319fc6e | ||
|
|
740276f550 | ||
|
|
40bca10250 | ||
|
|
8ae8d99528 | ||
|
|
f03b7085c3 | ||
|
|
36c2947e4d | ||
|
|
3bbcc3d4d0 | ||
|
|
b47d8ea868 | ||
|
|
a97a73600e | ||
|
|
6735220557 | ||
|
|
d7d29b25bc | ||
|
|
e2f33af831 | ||
|
|
19b9fdf536 | ||
|
|
a842522f49 | ||
|
|
bc1160de72 | ||
|
|
f791bc8a42 | ||
|
|
0d7567131a | ||
|
|
7b84f2c2a5 | ||
|
|
cf7b1441d9 | ||
|
|
32005b0bfe | ||
|
|
025a72a417 | ||
|
|
95c4502b81 | ||
|
|
2290ce2c24 | ||
|
|
bac732837e | ||
|
|
b725c56c42 | ||
|
|
cfcc9533b3 | ||
|
|
cffdb69c5e | ||
|
|
709190312c | ||
|
|
e1a38f984b | ||
|
|
4dafeb57a5 | ||
|
|
45956c36fb | ||
|
|
9eb69465b7 | ||
|
|
5f392258cb | ||
|
|
382c1cd68e | ||
|
|
5994050f51 | ||
|
|
28a00571ef | ||
|
|
d00eacbba0 | ||
|
|
a6c76a9c0f | ||
|
|
5754d6084c |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
51
.github/workflows/ci.yml
vendored
51
.github/workflows/ci.yml
vendored
@@ -28,9 +28,9 @@ jobs:
|
||||
- name: Run golangci-lint
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/bin/ v1.39.0
|
||||
make -f builder.Makefile lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
args: --timeout=180s
|
||||
|
||||
# only on main branch, costs too much for the gain on every PR
|
||||
validate-cross-build:
|
||||
@@ -59,8 +59,8 @@ jobs:
|
||||
- name: Build packages
|
||||
run: make -f builder.Makefile cross
|
||||
|
||||
build:
|
||||
name: Build
|
||||
build-plugin:
|
||||
name: Build and tests in plugin mode
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
@@ -71,10 +71,6 @@ jobs:
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Set up gosum
|
||||
run: |
|
||||
go get -u gotest.tools/gotestsum
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
@@ -89,8 +85,6 @@ jobs:
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
BUILD_TAGS: kube
|
||||
run: make -f builder.Makefile test
|
||||
|
||||
- name: Build for local E2E
|
||||
@@ -98,5 +92,38 @@ jobs:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
|
||||
- name: E2E Test
|
||||
- name: E2E Test in plugin mode
|
||||
run: make e2e-compose
|
||||
|
||||
build-standalone:
|
||||
name: Build and tests in standalone mode
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Set up Go 1.17
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
id: go
|
||||
|
||||
- name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.3.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build for local E2E
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
run: make -f builder.Makefile compose-plugin
|
||||
|
||||
- name: E2E Test in standalone mode
|
||||
run: make e2e-compose-standalone
|
||||
|
||||
3
.github/workflows/release.yaml
vendored
3
.github/workflows/release.yaml
vendored
@@ -44,7 +44,8 @@ jobs:
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "bin/*"
|
||||
prerelease: true
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
commit: "v2"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.tag }}
|
||||
|
||||
@@ -83,7 +83,7 @@ don't get discouraged! Our contributor's guide explains
|
||||
<tr>
|
||||
<td>Community Slack</td>
|
||||
<td>
|
||||
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://dockercommunity.slack.com/ssb/redirect" target="_blank">with this link</a>.
|
||||
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://www.docker.com/docker-community" target="_blank">with this link</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
12
Makefile
12
Makefile
@@ -42,8 +42,16 @@ compose-plugin: ## Compile the compose cli-plugin
|
||||
--output ./bin
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run End to end local tests. Set E2E_TEST=TestName to run a single test
|
||||
gotestsum $(TEST_FLAGS) ./pkg/e2e -- -count=1
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go test $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
go test $(TEST_FLAGS) -count=1 --tags=standalone ./pkg/e2e
|
||||
|
||||
|
||||
.PHONY: 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
|
||||
|
||||
.PHONY: cross
|
||||
cross: ## Compile the CLI for linux, darwin and windows
|
||||
|
||||
97
cmd/compatibility/convert.go
Normal file
97
cmd/compatibility/convert.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
|
||||
func getBoolFlags() []string {
|
||||
return []string{
|
||||
"--debug", "-D",
|
||||
"--verbose",
|
||||
"--tls",
|
||||
"--tlsverify",
|
||||
}
|
||||
}
|
||||
|
||||
func getStringFlags() []string {
|
||||
return []string{
|
||||
"--tlscacert",
|
||||
"--tlscert",
|
||||
"--tlskey",
|
||||
"--host", "-H",
|
||||
"--context",
|
||||
"--log-level",
|
||||
}
|
||||
}
|
||||
|
||||
// Convert transforms standalone docker-compose args into CLI plugin compliant ones
|
||||
func Convert(args []string) []string {
|
||||
var rootFlags []string
|
||||
command := []string{compose.PluginName}
|
||||
l := len(args)
|
||||
for i := 0; i < l; i++ {
|
||||
arg := args[i]
|
||||
if arg[0] != '-' {
|
||||
// not a top-level flag anymore, keep the rest of the command unmodified
|
||||
if arg == compose.PluginName {
|
||||
i++
|
||||
}
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
if arg == "--verbose" {
|
||||
arg = "--debug"
|
||||
}
|
||||
if arg == "-h" {
|
||||
// docker cli has deprecated -h to avoid ambiguity with -H, while docker-compose still support it
|
||||
arg = "--help"
|
||||
}
|
||||
if arg == "--version" || arg == "-v" {
|
||||
// redirect --version pseudo-command to actual command
|
||||
arg = "version"
|
||||
}
|
||||
if contains(getBoolFlags(), arg) {
|
||||
rootFlags = append(rootFlags, arg)
|
||||
continue
|
||||
}
|
||||
if contains(getStringFlags(), arg) {
|
||||
i++
|
||||
if i >= l {
|
||||
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
rootFlags = append(rootFlags, arg, args[i])
|
||||
continue
|
||||
}
|
||||
command = append(command, arg)
|
||||
}
|
||||
return append(rootFlags, command...)
|
||||
}
|
||||
|
||||
func contains(array []string, needle string) bool {
|
||||
for _, val := range array {
|
||||
if val == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
78
cmd/compatibility/convert_test.go
Normal file
78
cmd/compatibility/convert_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func Test_convert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "compose only",
|
||||
args: []string{"up"},
|
||||
want: []string{"compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "with context",
|
||||
args: []string{"--context", "foo", "-f", "compose.yaml", "up"},
|
||||
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
|
||||
},
|
||||
{
|
||||
name: "with host",
|
||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||
want: []string{"--host", "tcp://1.2.3.4", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "compose --version",
|
||||
args: []string{"--version"},
|
||||
want: []string{"compose", "version"},
|
||||
},
|
||||
{
|
||||
name: "help",
|
||||
args: []string{"-h"},
|
||||
want: []string{"compose", "--help"},
|
||||
},
|
||||
{
|
||||
name: "issues/1962",
|
||||
args: []string{"psql", "-h", "postgres"},
|
||||
want: []string{"compose", "psql", "-h", "postgres"}, // -h should not be converted to --help
|
||||
},
|
||||
{
|
||||
name: "issues/8648",
|
||||
args: []string{"exec", "mongo", "mongo", "--host", "mongo"},
|
||||
want: []string{"compose", "exec", "mongo", "mongo", "--host", "mongo"}, // --host is passed to exec
|
||||
},
|
||||
{
|
||||
name: "issues/12",
|
||||
args: []string{"--log-level", "INFO", "up"},
|
||||
want: []string{"--log-level", "INFO", "compose", "up"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Convert(tt.args)
|
||||
assert.DeepEqual(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -120,24 +121,6 @@ func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Co
|
||||
return err
|
||||
}
|
||||
|
||||
if o.EnvFile != "" {
|
||||
var services types.Services
|
||||
for _, s := range project.Services {
|
||||
ef := o.EnvFile
|
||||
if ef != "" {
|
||||
if !filepath.IsAbs(ef) {
|
||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
||||
}
|
||||
if s.Labels == nil {
|
||||
s.Labels = make(map[string]string)
|
||||
}
|
||||
s.Labels[api.EnvironmentFileLabel] = ef
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
project.Services = services
|
||||
}
|
||||
|
||||
return fn(ctx, project, args)
|
||||
})
|
||||
}
|
||||
@@ -176,10 +159,29 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
|
||||
return nil, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if o.Compatibility || project.Environment["COMPOSE_COMPATIBILITY"] == "true" {
|
||||
if o.Compatibility || utils.StringToBool(project.Environment["COMPOSE_COMPATIBILITY"]) {
|
||||
compose.Separator = "_"
|
||||
}
|
||||
|
||||
ef := o.EnvFile
|
||||
if ef != "" && !filepath.IsAbs(ef) {
|
||||
ef = filepath.Join(project.WorkingDir, o.EnvFile)
|
||||
}
|
||||
for i, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
api.ServiceLabel: s.Name,
|
||||
api.VersionLabel: api.ComposeVersion,
|
||||
api.WorkingDirLabel: project.WorkingDir,
|
||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||
api.OneoffLabel: "False", // default, will be overridden by `run` command
|
||||
}
|
||||
if ef != "" {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = ef
|
||||
}
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
s, err := project.GetServices(services...)
|
||||
if err != nil {
|
||||
@@ -212,11 +214,12 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
|
||||
cli.WithName(o.ProjectName))...)
|
||||
}
|
||||
|
||||
const pluginName = "compose"
|
||||
// PluginName is the name of the plugin
|
||||
const PluginName = "compose"
|
||||
|
||||
// RunningAsStandalone detects when running as a standalone program
|
||||
func RunningAsStandalone() bool {
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != pluginName
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
@@ -230,7 +233,7 @@ func RootCommand(backend api.Service) *cobra.Command {
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
Use: pluginName,
|
||||
Use: PluginName,
|
||||
TraverseChildren: true,
|
||||
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -44,9 +44,11 @@ type convertOptions struct {
|
||||
quiet bool
|
||||
resolveImageDigests bool
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
}
|
||||
|
||||
@@ -66,6 +68,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@@ -81,6 +86,9 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if opts.profiles {
|
||||
return runProfiles(opts, args)
|
||||
}
|
||||
if opts.images {
|
||||
return runConfigImages(opts, args)
|
||||
}
|
||||
|
||||
return runConvert(ctx, backend, opts, args)
|
||||
}),
|
||||
@@ -91,10 +99,12 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
@@ -103,7 +113,12 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
|
||||
func runConvert(ctx context.Context, backend api.Service, opts convertOptions, services []string) error {
|
||||
var json []byte
|
||||
project, err := opts.toProject(services, cli.WithInterpolation(!opts.noInterpolate), cli.WithResolvedPaths(true))
|
||||
project, err := opts.toProject(services,
|
||||
cli.WithInterpolation(!opts.noInterpolate),
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithNormalization(!opts.noNormalize),
|
||||
cli.WithDiscardEnvFile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,3 +222,18 @@ func runProfiles(opts convertOptions, services []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runConfigImages(opts convertOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range project.Services {
|
||||
if s.Image != "" {
|
||||
fmt.Println(s.Image)
|
||||
} else {
|
||||
fmt.Printf("%s_%s\n", project.Name, s.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, backend api.Service, opts copyOptions) error {
|
||||
projects, err := opts.toProject(nil)
|
||||
name, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Copy(ctx, projects, api.CopyOptions{
|
||||
return backend.Copy(ctx, name, api.CopyOptions{
|
||||
Source: opts.source,
|
||||
Destination: opts.destination,
|
||||
All: opts.all,
|
||||
|
||||
@@ -19,10 +19,14 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
@@ -58,10 +62,19 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
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.")
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
switch name {
|
||||
case "volume":
|
||||
name = "volumes"
|
||||
logrus.Warn("--volume is deprecated, please use --volumes")
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return downCmd
|
||||
}
|
||||
|
||||
|
||||
@@ -70,9 +70,14 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if there are multiple instances of a service [default: 1].")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", false, "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. DEPRECATED")
|
||||
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
|
||||
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY. DEPRECATED")
|
||||
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
|
||||
runCmd.Flags().SetInterspersed(false)
|
||||
return runCmd
|
||||
}
|
||||
|
||||
@@ -92,22 +92,24 @@ func runList(ctx context.Context, backend api.Service, opts lsOptions) error {
|
||||
view := viewFromStackList(stackList)
|
||||
return formatter.Print(view, opts.Format, os.Stdout, func(w io.Writer) {
|
||||
for _, stack := range view {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", stack.Name, stack.Status, stack.ConfigFiles)
|
||||
}
|
||||
}, "NAME", "STATUS")
|
||||
}, "NAME", "STATUS", "CONFIG FILES")
|
||||
}
|
||||
|
||||
type stackView struct {
|
||||
Name string
|
||||
Status string
|
||||
Name string
|
||||
Status string
|
||||
ConfigFiles string
|
||||
}
|
||||
|
||||
func viewFromStackList(stackList []api.Stack) []stackView {
|
||||
retList := make([]stackView, len(stackList))
|
||||
for i, s := range stackList {
|
||||
retList[i] = stackView{
|
||||
Name: s.Name,
|
||||
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
|
||||
Name: s.Name,
|
||||
Status: strings.TrimSpace(fmt.Sprintf("%s %s", s.Status, s.Reason)),
|
||||
ConfigFiles: s.ConfigFiles,
|
||||
}
|
||||
}
|
||||
return retList
|
||||
|
||||
@@ -59,7 +59,7 @@ func (p *psOptions) parseFilter() error {
|
||||
case "source":
|
||||
return api.ErrNotImplemented
|
||||
default:
|
||||
return fmt.Errorf("unknow filter %s", parts[0])
|
||||
return fmt.Errorf("unknown filter %s", parts[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func runRemove(ctx context.Context, backend api.Service, opts removeOptions, ser
|
||||
}
|
||||
|
||||
if opts.stop {
|
||||
err := backend.Stop(ctx, project, api.StopOptions{
|
||||
err := backend.Stop(ctx, project.Name, api.StopOptions{
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -49,13 +49,13 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, backend api.Service, opts restartOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.Duration(opts.timeout) * time.Second
|
||||
return backend.Restart(ctx, project, api.RestartOptions{
|
||||
return backend.Restart(ctx, projectName, api.RestartOptions{
|
||||
Timeout: &timeout,
|
||||
Services: services,
|
||||
})
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
cgo "github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -43,6 +42,7 @@ type runOptions struct {
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
interactive bool
|
||||
user string
|
||||
workdir string
|
||||
entrypoint string
|
||||
@@ -54,6 +54,8 @@ type runOptions struct {
|
||||
servicePorts bool
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func (opts runOptions) apply(project *types.Project) error {
|
||||
@@ -134,6 +136,8 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
opts.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
return runRun(ctx, backend, project, opts)
|
||||
}),
|
||||
ValidArgsFunction: serviceCompletion(p),
|
||||
@@ -143,7 +147,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", notAtTTY(), "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
|
||||
flags.BoolVarP(&opts.noTty, "no-TTY", "T", false, "Disable pseudo-noTty allocation. By default docker compose run allocates a TTY")
|
||||
flags.StringVar(&opts.name, "name", "", " Assign a name to the container")
|
||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
@@ -153,6 +157,11 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
cmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
|
||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||
flags.SetInterspersed(false)
|
||||
@@ -169,11 +178,6 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
func notAtTTY() bool {
|
||||
b := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
|
||||
return !b
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions) error {
|
||||
err := opts.apply(project)
|
||||
if err != nil {
|
||||
@@ -181,7 +185,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
return startDependencies(ctx, backend, *project, opts.Service)
|
||||
return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -215,7 +219,16 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
UseNetworkAliases: opts.useAliases,
|
||||
NoDeps: opts.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: opts.quietPull,
|
||||
}
|
||||
|
||||
for i, service := range project.Services {
|
||||
if service.Name == opts.Service {
|
||||
service.StdinOpen = opts.interactive
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
@@ -227,7 +240,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string) error {
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, requestedServiceName string, ignoreOrphans bool) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for _, service := range project.Services {
|
||||
@@ -240,8 +253,15 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices = append(project.DisabledServices, requestedService)
|
||||
if err := backend.Create(ctx, &project, api.CreateOptions{}); err != nil {
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
IgnoreOrphans: ignoreOrphans,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return backend.Start(ctx, &project, api.StartOptions{})
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, backend api.Service, opts startOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Start(ctx, project, api.StartOptions{})
|
||||
return backend.Start(ctx, projectName, api.StartOptions{
|
||||
AttachTo: services,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, backend api.Service, opts stopOptions, services []string) error {
|
||||
project, err := opts.toProject(services)
|
||||
projectName, err := opts.toProjectName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func runStop(ctx context.Context, backend api.Service, opts stopOptions, service
|
||||
timeoutValue := time.Duration(opts.timeout) * time.Second
|
||||
timeout = &timeoutValue
|
||||
}
|
||||
return backend.Stop(ctx, project, api.StopOptions{
|
||||
return backend.Stop(ctx, projectName, api.StopOptions{
|
||||
Timeout: timeout,
|
||||
Services: services,
|
||||
})
|
||||
|
||||
@@ -103,8 +103,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command {
|
||||
return validateFlags(&up, &create)
|
||||
}),
|
||||
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
|
||||
ignore := project.Environment["COMPOSE_IGNORE_ORPHANS"]
|
||||
create.ignoreOrphans = strings.ToLower(ignore) == "true"
|
||||
create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
|
||||
if create.ignoreOrphans && create.removeOrphans {
|
||||
return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
@@ -52,11 +53,11 @@ func versionCommand() *cobra.Command {
|
||||
|
||||
func runVersion(opts versionOptions) {
|
||||
if opts.short {
|
||||
fmt.Println(internal.Version)
|
||||
fmt.Println(strings.TrimPrefix(internal.Version, "v"))
|
||||
return
|
||||
}
|
||||
if opts.format == formatter.JSON {
|
||||
fmt.Printf(`{"version":%q}\n`, internal.Version)
|
||||
fmt.Printf("{\"version\":%q}\n", internal.Version)
|
||||
return
|
||||
}
|
||||
fmt.Println("Docker Compose version", internal.Version)
|
||||
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose-switch/redirect"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compatibility"
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -69,7 +69,7 @@ func pluginMain() {
|
||||
|
||||
func main() {
|
||||
if commands.RunningAsStandalone() {
|
||||
os.Args = append([]string{"docker"}, redirect.Convert(os.Args[1:])...)
|
||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||
}
|
||||
pluginMain()
|
||||
}
|
||||
|
||||
@@ -99,3 +99,6 @@ Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f
|
||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||
|
||||
If flags are explicitly set on command line, associated environment variable is ignored
|
||||
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
|
||||
@@ -5,7 +5,7 @@ Builds, (re)creates, starts, and attaches to containers for a service.
|
||||
|
||||
Unless they are already running, this command also starts any linked services.
|
||||
|
||||
The `docker compose up` command aggregates the output of each container (liked `docker compose logs --follow` does).
|
||||
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
|
||||
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
|
||||
background and leaves them running.
|
||||
|
||||
|
||||
@@ -98,6 +98,9 @@ long: |-
|
||||
and so does `COMPOSE_PROFILES` environment variable for to the `--profiles` flag.
|
||||
|
||||
If flags are explicitly set on command line, associated environment variable is ignored
|
||||
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` will stop docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
usage: docker compose
|
||||
pname: docker
|
||||
plink: docker.yaml
|
||||
|
||||
@@ -5,7 +5,7 @@ long: |-
|
||||
|
||||
Unless they are already running, this command also starts any linked services.
|
||||
|
||||
The `docker compose up` command aggregates the output of each container (liked `docker compose logs --follow` does).
|
||||
The `docker compose up` command aggregates the output of each container (like `docker compose logs --follow` does).
|
||||
When the command exits, all containers are stopped. Running `docker compose up --detach` starts the containers in the
|
||||
background and leaves them running.
|
||||
|
||||
|
||||
@@ -32,7 +32,16 @@ func generateCliYaml(opts *options) error {
|
||||
disableFlagsInUseLine(cmd)
|
||||
|
||||
cmd.DisableAutoGenTag = true
|
||||
return clidocstool.GenYamlTree(cmd, opts.target)
|
||||
tool, err := clidocstool.New(clidocstool.Options{
|
||||
Root: cmd,
|
||||
SourceDir: opts.source,
|
||||
TargetDir: opts.target,
|
||||
Plugin: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tool.GenYamlTree(cmd)
|
||||
}
|
||||
|
||||
func disableFlagsInUseLine(cmd *cobra.Command) {
|
||||
|
||||
137
go.mod
137
go.mod
@@ -3,129 +3,142 @@ module github.com/docker/compose/v2
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.2.3
|
||||
github.com/buger/goterm v1.0.0
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/cnabio/cnab-to-oci v0.3.1-beta1
|
||||
github.com/compose-spec/compose-go v1.0.5
|
||||
github.com/containerd/console v1.0.2
|
||||
github.com/containerd/containerd v1.5.8
|
||||
github.com/compose-spec/compose-go v1.1.0
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.1
|
||||
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
|
||||
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
|
||||
github.com/docker/cli v20.10.7+incompatible
|
||||
github.com/docker/cli-docs-tool v0.1.1
|
||||
github.com/docker/compose-switch v1.0.2
|
||||
github.com/docker/buildx v0.7.1
|
||||
github.com/docker/cli v20.10.12+incompatible
|
||||
github.com/docker/cli-docs-tool v0.2.1
|
||||
github.com/docker/docker v20.10.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.4.0
|
||||
github.com/golang/mock v1.5.0
|
||||
github.com/hashicorp/go-multierror v1.1.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/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf
|
||||
github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da
|
||||
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.1
|
||||
github.com/opencontainers/image-spec v1.0.2
|
||||
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.2.1
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.3
|
||||
gotest.tools/v3 v3.1.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.4.17 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.23 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
|
||||
github.com/compose-spec/godotenv v1.1.0 // indirect
|
||||
github.com/containerd/cgroups v1.0.1 // indirect
|
||||
github.com/containerd/continuity v0.1.0 // indirect
|
||||
github.com/containerd/continuity v0.2.2 // indirect
|
||||
github.com/containerd/typeurl v1.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4-0.20210125172408-38bea2ce277a // indirect
|
||||
github.com/docker/distribution v2.8.0+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/go-logr/logr v0.4.0 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gofrs/flock v0.8.0 // indirect
|
||||
github.com/gogo/googleapis v1.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/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/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea // indirect
|
||||
github.com/json-iterator/go v1.1.11 // 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.11.13 // indirect
|
||||
github.com/kr/pty v1.1.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/klauspost/compress v1.13.5 // 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.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/mount v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.4.1 // indirect
|
||||
github.com/moby/sys/symlink v0.1.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.6.0 // indirect
|
||||
github.com/moby/sys/symlink v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.7.1 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.30.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.0 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
go.opentelemetry.io/contrib v0.21.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.21.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.3.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.3.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/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-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.43.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.21.0 // indirect
|
||||
k8s.io/client-go v0.21.0 // indirect
|
||||
k8s.io/klog/v2 v2.8.0 // indirect
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
|
||||
k8s.io/apimachinery v0.22.5 // indirect
|
||||
k8s.io/client-go v0.22.5 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
// (for buildx)
|
||||
replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305
|
||||
replace (
|
||||
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
|
||||
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/otelhttp v0.0.0-20210714055410-d010b05b4939
|
||||
)
|
||||
|
||||
@@ -37,11 +37,11 @@ type Service interface {
|
||||
// Create executes the equivalent to a `compose create`
|
||||
Create(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
// Start executes the equivalent to a `compose start`
|
||||
Start(ctx context.Context, project *types.Project, options StartOptions) error
|
||||
Start(ctx context.Context, projectName string, options StartOptions) error
|
||||
// Restart restarts containers
|
||||
Restart(ctx context.Context, project *types.Project, options RestartOptions) error
|
||||
Restart(ctx context.Context, projectName string, options RestartOptions) error
|
||||
// Stop executes the equivalent to a `compose stop`
|
||||
Stop(ctx context.Context, project *types.Project, options StopOptions) error
|
||||
Stop(ctx context.Context, projectName string, options StopOptions) error
|
||||
// Up executes the equivalent to a `compose up`
|
||||
Up(ctx context.Context, project *types.Project, options UpOptions) error
|
||||
// Down executes the equivalent to a `compose down`
|
||||
@@ -63,7 +63,7 @@ type Service interface {
|
||||
// Exec executes a command in a running service container
|
||||
Exec(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||
// Copy copies a file/folder between a service container and the local filesystem
|
||||
Copy(ctx context.Context, project *types.Project, opts CopyOptions) error
|
||||
Copy(ctx context.Context, project string, options CopyOptions) error
|
||||
// Pause executes the equivalent to a `compose pause`
|
||||
Pause(ctx context.Context, project string, options PauseOptions) error
|
||||
// UnPause executes the equivalent to a `compose unpause`
|
||||
@@ -227,6 +227,8 @@ type RunOptions struct {
|
||||
Privileged bool
|
||||
UseNetworkAliases bool
|
||||
NoDeps bool
|
||||
// QuietPull makes the pulling process quiet
|
||||
QuietPull bool
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
@@ -402,10 +404,11 @@ const (
|
||||
|
||||
// Stack holds the name and state of a compose application/stack
|
||||
type Stack struct {
|
||||
ID string
|
||||
Name string
|
||||
Status string
|
||||
Reason string
|
||||
ID string
|
||||
Name string
|
||||
Status string
|
||||
ConfigFiles string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// LogConsumer is a callback to process log messages from services
|
||||
@@ -434,6 +437,8 @@ const (
|
||||
ContainerEventLog = iota
|
||||
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
|
||||
ContainerEventAttach
|
||||
// ContainerEventStopped is a ContainerEvent of type stopped.
|
||||
ContainerEventStopped
|
||||
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
|
||||
ContainerEventExit
|
||||
// UserCancel user cancelled compose up, we are stopping containers
|
||||
|
||||
@@ -28,9 +28,9 @@ type ServiceProxy struct {
|
||||
PushFn func(ctx context.Context, project *types.Project, options PushOptions) error
|
||||
PullFn func(ctx context.Context, project *types.Project, opts PullOptions) error
|
||||
CreateFn func(ctx context.Context, project *types.Project, opts CreateOptions) error
|
||||
StartFn func(ctx context.Context, project *types.Project, options StartOptions) error
|
||||
RestartFn func(ctx context.Context, project *types.Project, options RestartOptions) error
|
||||
StopFn func(ctx context.Context, project *types.Project, options StopOptions) error
|
||||
StartFn func(ctx context.Context, projectName string, options StartOptions) error
|
||||
RestartFn func(ctx context.Context, projectName string, options RestartOptions) error
|
||||
StopFn func(ctx context.Context, projectName string, options StopOptions) error
|
||||
UpFn func(ctx context.Context, project *types.Project, options UpOptions) error
|
||||
DownFn func(ctx context.Context, projectName string, options DownOptions) error
|
||||
LogsFn func(ctx context.Context, projectName string, consumer LogConsumer, options LogOptions) error
|
||||
@@ -41,7 +41,7 @@ type ServiceProxy struct {
|
||||
RunOneOffContainerFn func(ctx context.Context, project *types.Project, opts RunOptions) (int, error)
|
||||
RemoveFn func(ctx context.Context, project *types.Project, options RemoveOptions) error
|
||||
ExecFn func(ctx context.Context, project string, opts RunOptions) (int, error)
|
||||
CopyFn func(ctx context.Context, project *types.Project, opts CopyOptions) error
|
||||
CopyFn func(ctx context.Context, project string, options CopyOptions) error
|
||||
PauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||
UnPauseFn func(ctx context.Context, project string, options PauseOptions) error
|
||||
TopFn func(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
|
||||
@@ -141,36 +141,27 @@ func (s *ServiceProxy) Create(ctx context.Context, project *types.Project, optio
|
||||
}
|
||||
|
||||
// Start implements Service interface
|
||||
func (s *ServiceProxy) Start(ctx context.Context, project *types.Project, options StartOptions) error {
|
||||
func (s *ServiceProxy) Start(ctx context.Context, projectName string, options StartOptions) error {
|
||||
if s.StartFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.StartFn(ctx, project, options)
|
||||
return s.StartFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Restart implements Service interface
|
||||
func (s *ServiceProxy) Restart(ctx context.Context, project *types.Project, options RestartOptions) error {
|
||||
func (s *ServiceProxy) Restart(ctx context.Context, projectName string, options RestartOptions) error {
|
||||
if s.RestartFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.RestartFn(ctx, project, options)
|
||||
return s.RestartFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Stop implements Service interface
|
||||
func (s *ServiceProxy) Stop(ctx context.Context, project *types.Project, options StopOptions) error {
|
||||
func (s *ServiceProxy) Stop(ctx context.Context, projectName string, options StopOptions) error {
|
||||
if s.StopFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.StopFn(ctx, project, options)
|
||||
return s.StopFn(ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Up implements Service interface
|
||||
@@ -269,13 +260,10 @@ func (s *ServiceProxy) Exec(ctx context.Context, project string, options RunOpti
|
||||
}
|
||||
|
||||
// Copy implements Service interface
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, project *types.Project, options CopyOptions) error {
|
||||
func (s *ServiceProxy) Copy(ctx context.Context, project string, options CopyOptions) error {
|
||||
if s.CopyFn == nil {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
for _, i := range s.interceptors {
|
||||
i(ctx, project)
|
||||
}
|
||||
return s.CopyFn(ctx, project, options)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
@@ -142,7 +141,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
||||
if project.Services[i].Labels == nil {
|
||||
project.Services[i].Labels = types.Labels{}
|
||||
}
|
||||
project.Services[i].Labels[api.ImageDigestLabel] = digest
|
||||
project.Services[i].CustomLabels[api.ImageDigestLabel] = digest
|
||||
project.Services[i].Image = image
|
||||
}
|
||||
}
|
||||
@@ -289,7 +288,7 @@ func mergeArgs(m ...types.Mapping) types.Mapping {
|
||||
}
|
||||
|
||||
func dockerFilePath(context string, dockerfile string) string {
|
||||
if urlutil.IsGitURL(context) || path.IsAbs(dockerfile) {
|
||||
if urlutil.IsGitURL(context) || filepath.IsAbs(dockerfile) {
|
||||
return dockerfile
|
||||
}
|
||||
return filepath.Join(context, dockerfile)
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/buildx/build"
|
||||
@@ -28,7 +29,7 @@ import (
|
||||
|
||||
func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
|
||||
const drivername = "default"
|
||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, "", nil, nil, project.WorkingDir)
|
||||
d, err := driver.GetDriver(ctx, drivername, nil, s.apiClient, s.configFile, nil, nil, nil, nil, nil, project.WorkingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, project *types.Pro
|
||||
w := xprogress.NewPrinter(progressCtx, os.Stdout, mode)
|
||||
|
||||
// We rely on buildx "docker" builder integrated in docker engine, so don't need a DockerAPI here
|
||||
response, err := build.Build(ctx, driverInfo, opts, nil, nil, w)
|
||||
response, err := build.Build(ctx, driverInfo, opts, nil, filepath.Dir(s.configFile.Filename), w)
|
||||
errW := w.Wait()
|
||||
if err == nil {
|
||||
err = errW
|
||||
|
||||
@@ -231,6 +231,7 @@ func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
|
||||
return dockertypes.ImageBuildOptions{
|
||||
Tags: options.Tags,
|
||||
NoCache: options.NoCache,
|
||||
Remove: true,
|
||||
PullParent: options.Pull,
|
||||
BuildArgs: toMapStringStringPtr(options.BuildArgs),
|
||||
Labels: options.Labels,
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
@@ -92,3 +93,59 @@ func escapeDollarSign(marshal []byte) []byte {
|
||||
escDollar := []byte{'$', '$'}
|
||||
return bytes.ReplaceAll(marshal, dollar, escDollar)
|
||||
}
|
||||
|
||||
// projectFromName builds a types.Project based on actual resources with compose labels set
|
||||
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
|
||||
}
|
||||
set := map[string]*types.ServiceConfig{}
|
||||
for _, c := range containers {
|
||||
serviceLabel := c.Labels[api.ServiceLabel]
|
||||
_, ok := set[serviceLabel]
|
||||
if !ok {
|
||||
set[serviceLabel] = &types.ServiceConfig{
|
||||
Name: serviceLabel,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
}
|
||||
set[serviceLabel].Scale++
|
||||
}
|
||||
for _, service := range set {
|
||||
dependencies := service.Labels[api.DependenciesLabel]
|
||||
if len(dependencies) > 0 {
|
||||
service.DependsOn = types.DependsOnConfig{}
|
||||
for _, dc := range strings.Split(dependencies, ",") {
|
||||
dcArr := strings.Split(dc, ":")
|
||||
condition := ServiceConditionRunningOrHealthy
|
||||
dependency := dcArr[0]
|
||||
|
||||
// backward compatibility
|
||||
if len(dcArr) > 1 {
|
||||
condition = dcArr[1]
|
||||
}
|
||||
service.DependsOn[dependency] = types.ServiceDependency{Condition: condition}
|
||||
}
|
||||
}
|
||||
project.Services = append(project.Services, *service)
|
||||
}
|
||||
SERVICES:
|
||||
for _, qs := range services {
|
||||
for _, es := range project.Services {
|
||||
if es.Name == qs {
|
||||
continue SERVICES
|
||||
}
|
||||
}
|
||||
return project, errors.New("no such service: " + qs)
|
||||
}
|
||||
err := project.ForServices(services)
|
||||
if err != nil {
|
||||
return project, err
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -86,6 +87,14 @@ 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
|
||||
|
||||
@@ -261,11 +261,33 @@ func getContainerProgressName(container moby.Container) string {
|
||||
return "Container " + getCanonicalContainerName(container)
|
||||
}
|
||||
|
||||
func containerEvents(containers Containers, eventFunc func(string) progress.Event) []progress.Event {
|
||||
events := []progress.Event{}
|
||||
for _, container := range containers {
|
||||
events = append(events, eventFunc(getContainerProgressName(container)))
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// ServiceConditionRunningOrHealthy is a service condition on statys running or healthy
|
||||
const ServiceConditionRunningOrHealthy = "running_or_healthy"
|
||||
|
||||
func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependencies types.DependsOnConfig) error {
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
w := progress.ContextWriter(ctx)
|
||||
for dep, config := range dependencies {
|
||||
if shouldWait, err := shouldWaitForDependency(dep, config, project); err != nil {
|
||||
return err
|
||||
} else if !shouldWait {
|
||||
continue
|
||||
}
|
||||
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, false, dep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Events(containerEvents(containers, progress.Waiting))
|
||||
|
||||
dep, config := dep, config
|
||||
eg.Go(func() error {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
@@ -279,6 +301,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
w.Events(containerEvents(containers, progress.Healthy))
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionHealthy:
|
||||
@@ -287,6 +310,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if healthy {
|
||||
w.Events(containerEvents(containers, progress.Healthy))
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionCompletedSuccessfully:
|
||||
@@ -295,14 +319,12 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return err
|
||||
}
|
||||
if exited {
|
||||
w.Events(containerEvents(containers, progress.Exited))
|
||||
if code != 0 {
|
||||
return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, code)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case types.ServiceConditionStarted:
|
||||
// already managed by InDependencyOrder
|
||||
return nil
|
||||
default:
|
||||
logrus.Warnf("unsupported depends_on condition: %s", config.Condition)
|
||||
return nil
|
||||
@@ -313,6 +335,20 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
|
||||
if dependencyConfig.Condition == types.ServiceConditionStarted {
|
||||
// already managed by InDependencyOrder
|
||||
return false, nil
|
||||
}
|
||||
if service, err := project.GetService(serviceName); err != nil {
|
||||
return false, err
|
||||
} else if service.Scale == 0 {
|
||||
// don't wait for the dependency which configured to have 0 containers running
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func nextContainerNumber(containers []moby.Container) (int, error) {
|
||||
max := 0
|
||||
for _, c := range containers {
|
||||
@@ -448,7 +484,10 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||
},
|
||||
}
|
||||
links := append(service.Links, service.ExternalLinks...)
|
||||
links, err := s.getLinks(ctx, project.Name, service, number)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
for _, netName := range service.NetworksByPriority() {
|
||||
netwrk := project.Networks[netName]
|
||||
cfg := service.Networks[netName]
|
||||
@@ -476,6 +515,64 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
|
||||
return created, err
|
||||
}
|
||||
|
||||
// getLinks mimics V1 compose/service.py::Service::_get_links()
|
||||
func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) {
|
||||
var links []string
|
||||
format := func(k, v string) string {
|
||||
return fmt.Sprintf("%s:%s", k, v)
|
||||
}
|
||||
getServiceContainers := func(serviceName string) (Containers, error) {
|
||||
return s.getContainers(ctx, projectName, oneOffExclude, true, serviceName)
|
||||
}
|
||||
|
||||
for _, rawLink := range service.Links {
|
||||
linkSplit := strings.Split(rawLink, ":")
|
||||
linkServiceName := linkSplit[0]
|
||||
linkName := linkServiceName
|
||||
if len(linkSplit) == 2 {
|
||||
linkName = linkSplit[1] // linkName if informed like in: "serviceName:linkName"
|
||||
}
|
||||
cnts, err := getServiceContainers(linkServiceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, linkName),
|
||||
format(containerName, strings.Join([]string{linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
format(containerName, strings.Join([]string{projectName, linkServiceName, strconv.Itoa(number)}, Separator)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if service.Labels[api.OneoffLabel] == "True" {
|
||||
cnts, err := getServiceContainers(service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range cnts {
|
||||
containerName := getCanonicalContainerName(c)
|
||||
links = append(links,
|
||||
format(containerName, service.Name),
|
||||
format(containerName, strings.TrimPrefix(containerName, projectName+Separator)),
|
||||
format(containerName, containerName),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rawExtLink := range service.ExternalLinks {
|
||||
extLinkSplit := strings.Split(rawExtLink, ":")
|
||||
externalLink := extLinkSplit[0]
|
||||
linkName := externalLink
|
||||
if len(extLinkSplit) == 2 {
|
||||
linkName = extLinkSplit[1]
|
||||
}
|
||||
links = append(links, format(externalLink, linkName))
|
||||
}
|
||||
return links, nil
|
||||
}
|
||||
|
||||
func shortIDAliasExists(containerID string, aliases ...string) bool {
|
||||
for _, alias := range aliases {
|
||||
if alias == containerID[:12] {
|
||||
@@ -534,8 +631,15 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
|
||||
if container.State == nil || container.State.Health == nil {
|
||||
return false, fmt.Errorf("container for service %q has no healthcheck configured", service)
|
||||
}
|
||||
if container.State.Health.Status != moby.Healthy {
|
||||
switch container.State.Health.Status {
|
||||
case moby.Healthy:
|
||||
// Continue by checking the next container.
|
||||
case moby.Unhealthy:
|
||||
return false, fmt.Errorf("container for service %q is unhealthy", service)
|
||||
case moby.Starting:
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("container for service %q had unexpected health status %q", service, container.State.Health.Status)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
@@ -559,6 +663,10 @@ func (s *composeService) isServiceCompleted(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.waitDependencies(ctx, project, service.DependsOn)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -579,7 +687,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
|
||||
if scale, err := getScale(service); err != nil && scale == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no containers to start")
|
||||
return fmt.Errorf("service %q has no container to start", service.Name)
|
||||
}
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
@@ -17,10 +17,17 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
@@ -46,3 +53,163 @@ func TestContainerName(t *testing.T) {
|
||||
_, err = getScale(s)
|
||||
assert.Error(t, err, fmt.Sprintf(doubledContainerNameWarning, s.Name, s.ContainerName))
|
||||
}
|
||||
|
||||
func TestServiceLinks(t *testing.T) {
|
||||
const dbContainerName = "/" + testProject + "-db-1"
|
||||
const webContainerName = "/" + testProject + "-web-1"
|
||||
s := types.ServiceConfig{
|
||||
Name: "web",
|
||||
Scale: 1,
|
||||
}
|
||||
|
||||
containerListOptions := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("db"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
|
||||
t.Run("service links default", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:db"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:db")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links name", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
})
|
||||
|
||||
t.Run("service links external links", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{"db:dbname"}
|
||||
s.ExternalLinks = []string{"db1:db2"}
|
||||
|
||||
c := testContainer("db", dbContainerName, false)
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptions).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 4)
|
||||
assert.Equal(t, links[0], "testProject-db-1:dbname")
|
||||
assert.Equal(t, links[1], "testProject-db-1:db-1")
|
||||
assert.Equal(t, links[2], "testProject-db-1:testProject-db-1")
|
||||
|
||||
// ExternalLink
|
||||
assert.Equal(t, links[3], "db1:db2")
|
||||
})
|
||||
|
||||
t.Run("service links itself oneoff", func(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
apiClient := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = apiClient
|
||||
|
||||
s.Links = []string{}
|
||||
s.ExternalLinks = []string{}
|
||||
s.Labels = s.Labels.Add(api.OneoffLabel, "True")
|
||||
|
||||
c := testContainer("web", webContainerName, true)
|
||||
containerListOptionsOneOff := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(testProject),
|
||||
serviceFilter("web"),
|
||||
oneOffFilter(false),
|
||||
),
|
||||
All: true,
|
||||
}
|
||||
apiClient.EXPECT().ContainerList(gomock.Any(), containerListOptionsOneOff).Return([]moby.Container{c}, nil)
|
||||
|
||||
links, err := tested.getLinks(context.Background(), testProject, s, 1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(links), 3)
|
||||
assert.Equal(t, links[0], "testProject-web-1:web")
|
||||
assert.Equal(t, links[1], "testProject-web-1:web-1")
|
||||
assert.Equal(t, links[2], "testProject-web-1:testProject-web-1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWaitDependencies(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
api := mocks.NewMockAPIClient(mockCtrl)
|
||||
tested.apiClient = api
|
||||
|
||||
t.Run("should skip dependencies with scale 0", func(t *testing.T) {
|
||||
dbService := types.ServiceConfig{Name: "db", Scale: 0}
|
||||
redisService := types.ServiceConfig{Name: "redis", Scale: 0}
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
|
||||
dependencies := types.DependsOnConfig{
|
||||
"db": {Condition: ServiceConditionRunningOrHealthy},
|
||||
"redis": {Condition: ServiceConditionRunningOrHealthy},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
|
||||
})
|
||||
t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
|
||||
dbService := types.ServiceConfig{Name: "db", Scale: 1}
|
||||
redisService := types.ServiceConfig{Name: "redis", Scale: 1}
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{dbService, redisService}}
|
||||
dependencies := types.DependsOnConfig{
|
||||
"db": {Condition: types.ServiceConditionStarted},
|
||||
"redis": {Condition: types.ServiceConditionStarted},
|
||||
}
|
||||
assert.NilError(t, tested.waitDependencies(context.Background(), &project, dependencies))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,11 +26,9 @@ import (
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/pkg/errors"
|
||||
@@ -44,7 +42,7 @@ const (
|
||||
acrossServices = fromService | toService
|
||||
)
|
||||
|
||||
func (s *composeService) Copy(ctx context.Context, project *types.Project, opts api.CopyOptions) error {
|
||||
func (s *composeService) Copy(ctx context.Context, project string, opts api.CopyOptions) error {
|
||||
srcService, srcPath := splitCpArg(opts.Source)
|
||||
destService, dstPath := splitCpArg(opts.Destination)
|
||||
|
||||
@@ -64,20 +62,17 @@ func (s *composeService) Copy(ctx context.Context, project *types.Project, opts
|
||||
serviceName = destService
|
||||
}
|
||||
|
||||
f := filters.NewArgs(
|
||||
projectFilter(project.Name),
|
||||
serviceFilter(serviceName),
|
||||
)
|
||||
if !opts.All {
|
||||
f.Add("label", fmt.Sprintf("%s=%d", api.ContainerNumberLabel, opts.Index))
|
||||
}
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{Filters: f})
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, true, serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(containers) < 1 {
|
||||
return fmt.Errorf("service %s not running", serviceName)
|
||||
return fmt.Errorf("no container found for service %q", serviceName)
|
||||
}
|
||||
|
||||
if !opts.All {
|
||||
containers = containers.filter(indexed(opts.Index))
|
||||
}
|
||||
|
||||
g := errgroup.Group{}
|
||||
|
||||
@@ -229,7 +229,7 @@ func getImageName(service types.ServiceConfig, projectName string) string {
|
||||
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig,
|
||||
number int, inherit *moby.Container, autoRemove bool, attachStdin bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
||||
|
||||
labels, err := s.prepareLabels(p, service, number)
|
||||
labels, err := s.prepareLabels(service, number)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -380,6 +380,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
|
||||
Isolation: container.Isolation(service.Isolation),
|
||||
Runtime: service.Runtime,
|
||||
LogConfig: logConfig,
|
||||
GroupAdd: service.GroupAdd,
|
||||
}
|
||||
|
||||
return &containerConfig, &hostConfig, networkConfig, nil
|
||||
@@ -413,45 +414,42 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, error
|
||||
return securityOpts, nil
|
||||
}
|
||||
|
||||
func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
|
||||
func (s *composeService) prepareLabels(service types.ServiceConfig, number int) (map[string]string, error) {
|
||||
labels := map[string]string{}
|
||||
for k, v := range service.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
labels[api.ProjectLabel] = p.Name
|
||||
labels[api.ServiceLabel] = service.Name
|
||||
labels[api.VersionLabel] = api.ComposeVersion
|
||||
if _, ok := service.Labels[api.OneoffLabel]; !ok {
|
||||
labels[api.OneoffLabel] = "False"
|
||||
for k, v := range service.CustomLabels {
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
hash, err := ServiceHash(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels[api.ConfigHashLabel] = hash
|
||||
labels[api.WorkingDirLabel] = p.WorkingDir
|
||||
labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
|
||||
|
||||
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
|
||||
|
||||
var dependencies []string
|
||||
for s := range service.DependsOn {
|
||||
dependencies = append(dependencies, s)
|
||||
for s, d := range service.DependsOn {
|
||||
dependencies = append(dependencies, s+":"+d.Condition)
|
||||
}
|
||||
labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
|
||||
mode := "none"
|
||||
if len(project.Networks) > 0 {
|
||||
for name := range getNetworksForService(service) {
|
||||
mode = project.Networks[name].Name
|
||||
break
|
||||
}
|
||||
if len(project.Networks) == 0 {
|
||||
return "none"
|
||||
}
|
||||
return mode
|
||||
|
||||
if len(service.Networks) > 0 {
|
||||
name := service.NetworksByPriority()[0]
|
||||
return project.Networks[name].Name
|
||||
}
|
||||
|
||||
return project.Networks["default"].Name
|
||||
}
|
||||
|
||||
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
|
||||
@@ -640,15 +638,11 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
|
||||
bindings := nat.PortMap{}
|
||||
for _, port := range s.Ports {
|
||||
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
|
||||
bind := bindings[p]
|
||||
binding := nat.PortBinding{
|
||||
HostIP: port.HostIP,
|
||||
HostIP: port.HostIP,
|
||||
HostPort: port.Published,
|
||||
}
|
||||
if port.Published > 0 {
|
||||
binding.HostPort = fmt.Sprint(port.Published)
|
||||
}
|
||||
bind = append(bind, binding)
|
||||
bindings[p] = bind
|
||||
bindings[p] = append(bindings[p], binding)
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
@@ -718,11 +712,7 @@ MOUNTS:
|
||||
if m.Type == mount.TypeBind || m.Type == mount.TypeNamedPipe {
|
||||
for _, v := range service.Volumes {
|
||||
if v.Target == m.Target && v.Bind != nil && v.Bind.CreateHostPath {
|
||||
mode := "rw"
|
||||
if m.ReadOnly {
|
||||
mode = "ro"
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, mode))
|
||||
binds = append(binds, fmt.Sprintf("%s:%s:%s", m.Source, m.Target, getBindMode(v.Bind, m.ReadOnly)))
|
||||
continue MOUNTS
|
||||
}
|
||||
}
|
||||
@@ -732,6 +722,23 @@ 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 {
|
||||
@@ -999,21 +1006,19 @@ func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
|
||||
return aliases
|
||||
}
|
||||
|
||||
func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
|
||||
if len(s.Networks) > 0 {
|
||||
return s.Networks
|
||||
}
|
||||
if s.NetworkMode != "" {
|
||||
return nil
|
||||
}
|
||||
return map[string]*types.ServiceNetworkConfig{"default": nil}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
|
||||
_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
|
||||
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
|
||||
@@ -1092,18 +1097,24 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if volume.External.External {
|
||||
return fmt.Errorf("external volume %q not found", volume.Name)
|
||||
}
|
||||
err := s.createVolume(ctx, volume)
|
||||
return err
|
||||
}
|
||||
|
||||
// Volume exists with name, but let's double check this is the expected one
|
||||
// (better safe than sorry when it comes to user's data)
|
||||
if volume.External.External {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Volume exists with name, but let's double-check this is the expected one
|
||||
p, ok := inspected.Labels[api.ProjectLabel]
|
||||
if !ok {
|
||||
return fmt.Errorf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
||||
}
|
||||
if p != project {
|
||||
return fmt.Errorf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
|
||||
if ok && p != project {
|
||||
logrus.Warnf("volume %q already exists but was not created for project %q. Use `external: true` to use an existing volume", volume.Name, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -142,3 +142,83 @@ func TestBuildContainerMountOptions(t *testing.T) {
|
||||
assert.Equal(t, mounts[0].Target, "/var/myvolume1")
|
||||
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{
|
||||
Name: "myService",
|
||||
Networks: map[string]*composetypes.ServiceNetworkConfig{
|
||||
"myNetwork1": {
|
||||
Priority: 10,
|
||||
},
|
||||
"myNetwork2": {
|
||||
Priority: 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
|
||||
"myNetwork1": {
|
||||
Name: "myProject_myNetwork1",
|
||||
},
|
||||
"myNetwork2": {
|
||||
Name: "myProject_myNetwork2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_myNetwork2")
|
||||
})
|
||||
|
||||
t.Run("returns default network when service has no networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
|
||||
"myNetwork1": {
|
||||
Name: "myProject_myNetwork1",
|
||||
},
|
||||
"myNetwork2": {
|
||||
Name: "myProject_myNetwork2",
|
||||
},
|
||||
"default": {
|
||||
Name: "myProject_default",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_default")
|
||||
})
|
||||
|
||||
t.Run("returns none if project has no networks", func(t *testing.T) {
|
||||
service := composetypes.ServiceConfig{
|
||||
Name: "myService",
|
||||
}
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Services: []composetypes.ServiceConfig{
|
||||
service,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, getDefaultNetworkMode(&project, service), "none")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
for _, node := range nodes {
|
||||
// Don't start this service yet if all of its children have
|
||||
// not been started yet.
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Service, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
if len(traversalConfig.filterAdjacentByStatusFn(graph, node.Key, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex,
|
||||
return err
|
||||
}
|
||||
|
||||
graph.UpdateStatus(node.Service, traversalConfig.targetServiceStatus)
|
||||
graph.UpdateStatus(node.Key, traversalConfig.targetServiceStatus)
|
||||
|
||||
return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(node), traversalConfig, fn)
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
|
||||
func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
|
||||
builtFromResources := options.Project == nil
|
||||
w := progress.ContextWriter(ctx)
|
||||
resourceToRemove := false
|
||||
|
||||
@@ -50,8 +51,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Project == nil {
|
||||
options.Project = s.projectFromContainerLabels(containers.filter(isNotOneOff), projectName)
|
||||
if builtFromResources {
|
||||
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(containers) > 0 {
|
||||
@@ -85,11 +89,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
|
||||
if options.Volumes {
|
||||
rm, err := s.ensureVolumesDown(ctx, projectName, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ops = append(ops, rm...)
|
||||
ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
|
||||
}
|
||||
|
||||
if !resourceToRemove && len(ops) == 0 {
|
||||
@@ -103,19 +103,15 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) ensureVolumesDown(ctx context.Context, projectName string, w progress.Writer) ([]downOp, error) {
|
||||
func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.Project, w progress.Writer) []downOp {
|
||||
var ops []downOp
|
||||
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
return ops, err
|
||||
}
|
||||
for _, vol := range volumes.Volumes {
|
||||
id := vol.Name
|
||||
for _, vol := range project.Volumes {
|
||||
volumeName := vol.Name
|
||||
ops = append(ops, func() error {
|
||||
return s.removeVolume(ctx, id, w)
|
||||
return s.removeVolume(ctx, volumeName, w)
|
||||
})
|
||||
}
|
||||
return ops, nil
|
||||
return ops
|
||||
}
|
||||
|
||||
func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
|
||||
@@ -237,31 +233,21 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) *types.Project {
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
|
||||
containers = containers.filter(isNotOneOff)
|
||||
project, _ := s.projectFromName(containers, projectName)
|
||||
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return project
|
||||
}
|
||||
set := map[string]moby.Container{}
|
||||
for _, c := range containers {
|
||||
set[c.Labels[api.ServiceLabel]] = c
|
||||
}
|
||||
for s, c := range set {
|
||||
service := types.ServiceConfig{
|
||||
Name: s,
|
||||
Image: c.Image,
|
||||
Labels: c.Labels,
|
||||
|
||||
project.Volumes = types.Volumes{}
|
||||
for _, vol := range volumes.Volumes {
|
||||
project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
|
||||
Name: vol.Name,
|
||||
Driver: vol.Driver,
|
||||
Labels: vol.Labels,
|
||||
}
|
||||
dependencies := c.Labels[api.DependenciesLabel]
|
||||
if len(dependencies) > 0 {
|
||||
service.DependsOn = types.DependsOnConfig{}
|
||||
for _, d := range strings.Split(dependencies, ",") {
|
||||
service.DependsOn[d] = types.ServiceDependency{}
|
||||
}
|
||||
}
|
||||
project.Services = append(project.Services, service)
|
||||
}
|
||||
return project
|
||||
return project, nil
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ func TestDown(t *testing.T) {
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
|
||||
@@ -74,6 +76,8 @@ func TestDownRemoveOrphans(t *testing.T) {
|
||||
testContainer("service2", "789", false),
|
||||
testContainer("service_orphan", "321", true),
|
||||
}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{}, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
|
||||
@@ -100,13 +104,16 @@ func TestDownRemoveVolumes(t *testing.T) {
|
||||
|
||||
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
|
||||
[]moby.Container{testContainer("service1", "123", false)}, nil)
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).
|
||||
Return(volume.VolumeListOKBody{
|
||||
Volumes: []*moby.Volume{{Name: "myProject_volume"}},
|
||||
}, nil)
|
||||
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
|
||||
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)
|
||||
|
||||
api.EXPECT().NetworkList(gomock.Any(), moby.NetworkListOptions{Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)))}).Return(nil, nil)
|
||||
|
||||
api.EXPECT().VolumeList(gomock.Any(), filters.NewArgs(projectFilter(strings.ToLower(testProject)))).Return(volume.VolumeListOKBody{Volumes: []*moby.Volume{{Name: "myProject_volume"}}}, nil)
|
||||
api.EXPECT().VolumeRemove(gomock.Any(), "myProject_volume", true).Return(nil)
|
||||
|
||||
err := tested.Down(context.Background(), strings.ToLower(testProject), compose.DownOptions{Volumes: true})
|
||||
|
||||
@@ -96,7 +96,7 @@ func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOption
|
||||
}
|
||||
|
||||
in := streams.NewIn(opts.Stdin)
|
||||
if in.IsTerminal() {
|
||||
if in.IsTerminal() && opts.Tty {
|
||||
state, err := term.SetRawTerminal(in.FD())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
)
|
||||
|
||||
// ServiceHash compute configuration has for a service
|
||||
// TODO move this to compose-go
|
||||
func ServiceHash(o types.ServiceConfig) (string, error) {
|
||||
// remove the Build config when generating the service hash
|
||||
o.Build = nil
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
|
||||
func (s *composeService) Images(ctx context.Context, projectName string, options api.ImagesOptions) ([]api.ImageSummary, error) {
|
||||
allContainers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(projectFilter(projectName)),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -42,7 +42,7 @@ func (s *composeService) kill(ctx context.Context, project *types.Project, optio
|
||||
}
|
||||
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, false, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -45,7 +45,9 @@ func TestKillAll(t *testing.T) {
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService("service1"), testService("service2")}}
|
||||
|
||||
ctx := context.Background()
|
||||
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
|
||||
api.EXPECT().ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject))),
|
||||
}).Return(
|
||||
[]moby.Container{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
|
||||
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
|
||||
@@ -64,9 +66,7 @@ func TestKillSignal(t *testing.T) {
|
||||
|
||||
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService(serviceName)}}
|
||||
listOptions := moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)),
|
||||
serviceFilter(serviceName)),
|
||||
All: true,
|
||||
Filters: filters.NewArgs(projectFilter(strings.ToLower(testProject)), serviceFilter(serviceName)),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -34,25 +34,43 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
if options.Follow {
|
||||
printer := newLogPrinter(consumer)
|
||||
eg.Go(func() error {
|
||||
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
|
||||
return s.logContainers(ctx, consumer, c, options)
|
||||
})
|
||||
})
|
||||
eg.Go(func() error {
|
||||
_, err := printer.Run(ctx, false, "", nil)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
c := c
|
||||
eg.Go(func() error {
|
||||
return s.logContainers(ctx, consumer, c, options)
|
||||
})
|
||||
}
|
||||
|
||||
if options.Follow {
|
||||
printer := newLogPrinter(consumer)
|
||||
eg.Go(func() error {
|
||||
for _, c := range containers {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.watchContainers(ctx, projectName, options.Services, printer.HandleEvent, containers, func(c types.Container) error {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
return s.logContainers(ctx, consumer, c, options)
|
||||
})
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
_, err := printer.Run(ctx, false, "", nil)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@@ -46,15 +48,40 @@ func containersToStacks(containers []moby.Container) ([]api.Stack, error) {
|
||||
}
|
||||
var projects []api.Stack
|
||||
for _, project := range keys {
|
||||
configFiles, err := combinedConfigFiles(containersByLabel[project])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projects = append(projects, api.Stack{
|
||||
ID: project,
|
||||
Name: project,
|
||||
Status: combinedStatus(containerToState(containersByLabel[project])),
|
||||
ID: project,
|
||||
Name: project,
|
||||
Status: combinedStatus(containerToState(containersByLabel[project])),
|
||||
ConfigFiles: configFiles,
|
||||
})
|
||||
}
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func combinedConfigFiles(containers []moby.Container) (string, error) {
|
||||
configFiles := []string{}
|
||||
|
||||
for _, c := range containers {
|
||||
files, ok := c.Labels[api.ConfigFilesLabel]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
|
||||
}
|
||||
|
||||
for _, f := range strings.Split(files, ",") {
|
||||
if !utils.StringContains(configFiles, f) {
|
||||
configFiles = append(configFiles, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(configFiles, ","), nil
|
||||
}
|
||||
|
||||
func containerToState(containers []moby.Container) []string {
|
||||
statuses := []string{}
|
||||
for _, c := range containers {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -30,31 +31,33 @@ func TestContainersToStacks(t *testing.T) {
|
||||
{
|
||||
ID: "service1",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project1"},
|
||||
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
|
||||
},
|
||||
{
|
||||
ID: "service2",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project1"},
|
||||
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
|
||||
},
|
||||
{
|
||||
ID: "service3",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project2"},
|
||||
Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
|
||||
},
|
||||
}
|
||||
stacks, err := containersToStacks(containers)
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, stacks, []api.Stack{
|
||||
{
|
||||
ID: "project1",
|
||||
Name: "project1",
|
||||
Status: "running(2)",
|
||||
ID: "project1",
|
||||
Name: "project1",
|
||||
Status: "running(2)",
|
||||
ConfigFiles: "/home/docker-compose.yaml",
|
||||
},
|
||||
{
|
||||
ID: "project2",
|
||||
Name: "project2",
|
||||
Status: "running(1)",
|
||||
ID: "project2",
|
||||
Name: "project2",
|
||||
Status: "running(1)",
|
||||
ConfigFiles: "/home/project2-docker-compose.yaml",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -64,3 +67,56 @@ func TestStacksMixedStatus(t *testing.T) {
|
||||
assert.Equal(t, combinedStatus([]string{"running", "running", "running"}), "running(3)")
|
||||
assert.Equal(t, combinedStatus([]string{"running", "exited", "running"}), "exited(1), running(2)")
|
||||
}
|
||||
|
||||
func TestCombinedConfigFiles(t *testing.T) {
|
||||
containersByLabel := map[string][]moby.Container{
|
||||
"project1": {
|
||||
{
|
||||
ID: "service1",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
|
||||
},
|
||||
{
|
||||
ID: "service2",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project1", api.ConfigFilesLabel: "/home/docker-compose.yaml"},
|
||||
},
|
||||
},
|
||||
"project2": {
|
||||
{
|
||||
ID: "service3",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project2", api.ConfigFilesLabel: "/home/project2-docker-compose.yaml"},
|
||||
},
|
||||
},
|
||||
"project3": {
|
||||
{
|
||||
ID: "service4",
|
||||
State: "running",
|
||||
Labels: map[string]string{api.ProjectLabel: "project3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testData := map[string]struct {
|
||||
ConfigFiles string
|
||||
Error error
|
||||
}{
|
||||
"project1": {ConfigFiles: "/home/docker-compose.yaml", Error: nil},
|
||||
"project2": {ConfigFiles: "/home/project2-docker-compose.yaml", Error: nil},
|
||||
"project3": {ConfigFiles: "", Error: fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
|
||||
}
|
||||
|
||||
for project, containers := range containersByLabel {
|
||||
configFiles, err := combinedConfigFiles(containers)
|
||||
|
||||
expected := testData[project]
|
||||
|
||||
if expected.Error != nil {
|
||||
assert.Equal(t, err.Error(), expected.Error.Error())
|
||||
} else {
|
||||
assert.Equal(t, err, expected.Error)
|
||||
}
|
||||
assert.Equal(t, configFiles, expected.ConfigFiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *composeService) Pause(ctx context.Context, project string, options api.
|
||||
}
|
||||
|
||||
func (s *composeService) pause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (s *composeService) UnPause(ctx context.Context, project string, options ap
|
||||
}
|
||||
|
||||
func (s *composeService) unPause(ctx context.Context, project string, options api.PauseOptions) error {
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, true, options.Services...)
|
||||
containers, err := s.getContainers(ctx, project, oneOffExclude, false, options.Services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (p *printer) Run(ctx context.Context, cascadeStop bool, exitCodeFrom string
|
||||
}
|
||||
containers[container] = struct{}{}
|
||||
p.consumer.Register(container)
|
||||
case api.ContainerEventExit:
|
||||
case api.ContainerEventExit, api.ContainerEventStopped:
|
||||
if !event.Restarting {
|
||||
delete(containers, container)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
@@ -27,14 +26,20 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func (s *composeService) Restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
|
||||
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, project, options)
|
||||
return s.restart(ctx, projectName, options)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) restart(ctx context.Context, project *types.Project, options api.RestartOptions) error {
|
||||
observedState, err := s.getContainers(ctx, project.Name, oneOffInclude, true)
|
||||
func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {
|
||||
|
||||
observedState, err := s.getContainers(ctx, projectName, oneOffInclude, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err := s.projectFromName(observedState, projectName, options.Services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -153,10 +153,11 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
|
||||
if service.Deploy != nil {
|
||||
service.Deploy.RestartPolicy = nil
|
||||
}
|
||||
service.Labels = service.Labels.Add(api.SlugLabel, slug)
|
||||
service.Labels = service.Labels.Add(api.OneoffLabel, "True")
|
||||
service.CustomLabels = service.CustomLabels.
|
||||
Add(api.SlugLabel, slug).
|
||||
Add(api.OneoffLabel, "True")
|
||||
|
||||
if err := s.ensureImagesExists(ctx, project, false); err != nil { // all dependencies already checked, but might miss service img
|
||||
if err := s.ensureImagesExists(ctx, project, opts.QuietPull); err != nil { // all dependencies already checked, but might miss service img
|
||||
return "", err
|
||||
}
|
||||
if !opts.NoDeps {
|
||||
|
||||
@@ -28,15 +28,22 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
)
|
||||
|
||||
func (s *composeService) Start(ctx context.Context, project *types.Project, options api.StartOptions) error {
|
||||
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, project, options, nil)
|
||||
return s.start(ctx, projectName, options, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) start(ctx context.Context, project *types.Project, options api.StartOptions, listener api.ContainerEventListener) error {
|
||||
if len(options.AttachTo) == 0 {
|
||||
options.AttachTo = project.ServiceNames()
|
||||
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, err := s.projectFromName(containers, projectName, options.AttachTo...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
@@ -53,7 +60,7 @@ func (s *composeService) start(ctx context.Context, project *types.Project, opti
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
@@ -111,6 +118,21 @@ func (s *composeService) watchContainers(ctx context.Context, projectName string
|
||||
}
|
||||
name := getContainerNameWithoutProject(container)
|
||||
|
||||
if event.Status == "stop" {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventStopped,
|
||||
Container: name,
|
||||
Service: container.Labels[api.ServiceLabel],
|
||||
})
|
||||
|
||||
delete(watched, container.ID)
|
||||
if len(watched) == 0 {
|
||||
// all project containers stopped, we're done
|
||||
stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if event.Status == "die" {
|
||||
restarted := watched[container.ID]
|
||||
watched[container.ID] = restarted + 1
|
||||
|
||||
@@ -21,29 +21,37 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
)
|
||||
|
||||
func (s *composeService) Stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
|
||||
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, project, options)
|
||||
return s.stop(ctx, projectName, options)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *composeService) stop(ctx context.Context, project *types.Project, options api.StopOptions) error {
|
||||
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
services := options.Services
|
||||
if len(services) == 0 {
|
||||
services = project.ServiceNames()
|
||||
services = []string{}
|
||||
}
|
||||
|
||||
var containers Containers
|
||||
containers, err := s.getContainers(ctx, project.Name, oneOffInclude, true, services...)
|
||||
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err := s.projectFromName(containers, projectName, services...)
|
||||
if err != nil && !api.IsNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(services) > 0 {
|
||||
containers = containers.filter(isService(services...))
|
||||
}
|
||||
|
||||
return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
return s.stopContainers(ctx, w, containers.filter(isService(service)), options.Timeout)
|
||||
})
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
compose "github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/golang/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -50,13 +49,7 @@ func TestStopTimeout(t *testing.T) {
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "456", &timeout).Return(nil)
|
||||
api.EXPECT().ContainerStop(gomock.Any(), "789", &timeout).Return(nil)
|
||||
|
||||
err := tested.Stop(ctx, &types.Project{
|
||||
Name: strings.ToLower(testProject),
|
||||
Services: []types.ServiceConfig{
|
||||
{Name: "service1"},
|
||||
{Name: "service2"},
|
||||
},
|
||||
}, compose.StopOptions{
|
||||
err := tested.Stop(ctx, strings.ToLower(testProject), compose.StopOptions{
|
||||
Timeout: &timeout,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
return err
|
||||
}
|
||||
if options.Start.Attach == nil {
|
||||
return s.start(ctx, project, options.Start, nil)
|
||||
return s.start(ctx, project.Name, options.Start, nil)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -65,7 +65,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
})
|
||||
}()
|
||||
|
||||
return s.Stop(ctx, project, api.StopOptions{
|
||||
return s.Stop(ctx, project.Name, api.StopOptions{
|
||||
Services: options.Create.Services,
|
||||
})
|
||||
})
|
||||
@@ -85,7 +85,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
return err
|
||||
})
|
||||
|
||||
err = s.start(ctx, project, options.Start, printer.HandleEvent)
|
||||
err = s.start(ctx, project.Name, options.Start, printer.HandleEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestComposeCancel(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("metrics on cancel Compose build", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "ls")
|
||||
c.RunDockerComposeCmd("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.
|
||||
|
||||
@@ -45,6 +45,6 @@ func TestCascadeStop(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
|
||||
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "build")
|
||||
res := c.RunDockerComposeCmd("--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")
|
||||
@@ -46,7 +46,7 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
c.RunDockerOrExitError("rmi", "build-test_nginx")
|
||||
c.RunDockerOrExitError("rmi", "custom-nginx")
|
||||
|
||||
c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
|
||||
|
||||
res := c.RunDockerCmd("image", "inspect", "build-test_nginx")
|
||||
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
|
||||
@@ -66,13 +66,26 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
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")
|
||||
|
||||
icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
|
||||
})
|
||||
|
||||
res := c.RunDockerCmd("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")
|
||||
|
||||
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
res := c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "up", "-d")
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "down")
|
||||
})
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
|
||||
@@ -86,20 +99,20 @@ func TestLocalComposeBuild(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("no rebuild when up again", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "up", "-d")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--workdir", "fixtures/build-test", "up", "-d", "--build")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/build-test", "down")
|
||||
c.RunDockerCmd("rmi", "build-test_nginx")
|
||||
c.RunDockerCmd("rmi", "custom-nginx")
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestLocalComposeExec(t *testing.T) {
|
||||
|
||||
const projectName = "compose-e2e-exec"
|
||||
|
||||
c.RunDockerCmd("compose", "--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/simple-composefile", "--project-name", projectName, "up", "-d")
|
||||
|
||||
t.Run("exec true", func(t *testing.T) {
|
||||
res := c.RunDockerOrExitError("exec", "compose-e2e-exec-simple-1", "/bin/true")
|
||||
|
||||
@@ -29,11 +29,11 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
t.Run("compose run", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "back")
|
||||
res := c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello one more time")
|
||||
res = c.RunDockerComposeCmd("-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"))
|
||||
@@ -68,7 +68,7 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("compose run --rm", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo", "Hello again")
|
||||
res := c.RunDockerComposeCmd("-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())
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
@@ -85,7 +85,7 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
t.Run("compose run --volumes", func(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo")
|
||||
res := c.RunDockerComposeCmd("-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")
|
||||
@@ -93,13 +93,27 @@ func TestLocalComposeRun(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("compose run --publish", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
|
||||
res := c.RunDockerCmd("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")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "orphan"))
|
||||
|
||||
cmd := c.NewDockerCmd("compose", "-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")
|
||||
})
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/run-test/orphan.yaml", "down")
|
||||
res := c.RunDockerCmd("ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
|
||||
@@ -33,22 +33,17 @@ import (
|
||||
|
||||
var binDir string
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestLocalComposeUp(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
const projectName = "compose-e2e-demo"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check accessing running app", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
|
||||
endpoint := "http://localhost:90"
|
||||
@@ -60,7 +55,7 @@ func TestLocalComposeUp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("top", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-p", projectName, "top")
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "top")
|
||||
output := res.Stdout()
|
||||
head := []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}
|
||||
for _, h := range head {
|
||||
@@ -98,21 +93,21 @@ func TestLocalComposeUp(t *testing.T) {
|
||||
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.RunDockerCmd("compose", "-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd("-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-db-1 "docker-entrypoint.s…" db running 5432/tcp`})
|
||||
})
|
||||
|
||||
t.Run("images", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-p", projectName, "images")
|
||||
res := c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
t.Run("check containers after down", func(t *testing.T) {
|
||||
@@ -137,7 +132,6 @@ func TestComposePull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDownComposefileInParentFolder(t *testing.T) {
|
||||
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
|
||||
tmpFolder, err := ioutil.TempDir("fixtures/simple-composefile", "test-tmp")
|
||||
@@ -145,10 +139,10 @@ func TestDownComposefileInParentFolder(t *testing.T) {
|
||||
defer os.Remove(tmpFolder) // nolint: errcheck
|
||||
projectName := filepath.Base(tmpFolder)
|
||||
|
||||
res := c.RunDockerCmd("compose", "--project-directory", tmpFolder, "up", "-d")
|
||||
res := c.RunDockerComposeCmd("--project-directory", tmpFolder, "up", "-d")
|
||||
res.Assert(t, icmd.Expected{Err: "Started", ExitCode: 0})
|
||||
|
||||
res = c.RunDockerCmd("compose", "-p", projectName, "down")
|
||||
res = c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
|
||||
}
|
||||
|
||||
@@ -181,11 +175,11 @@ func TestRm(t *testing.T) {
|
||||
const projectName = "compose-e2e-rm"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("rm -sf", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm", "-sf", "simple")
|
||||
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
|
||||
})
|
||||
|
||||
@@ -195,7 +189,7 @@ func TestRm(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-p", projectName, "down")
|
||||
c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -205,7 +199,7 @@ func TestCompatibility(t *testing.T) {
|
||||
const projectName = "compose-e2e-compatibility"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check container names", func(t *testing.T) {
|
||||
@@ -216,7 +210,7 @@ func TestCompatibility(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-p", projectName, "down")
|
||||
c.RunDockerComposeCmd("-p", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -228,7 +222,7 @@ func TestConvert(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
|
||||
nginx:
|
||||
build:
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestCopy(t *testing.T) {
|
||||
const projectName = "copy_e2e"
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "down")
|
||||
c.RunDockerComposeCmd("-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,16 +39,16 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("start service", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "--project-name", projectName, "up", "--scale", "nginx=5", "-d")
|
||||
c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/default.txt")
|
||||
res := c.RunDockerComposeCmd("-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()
|
||||
@@ -59,7 +59,7 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy to container with a given index copies the file to the given container", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/indexed.txt")
|
||||
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.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
output := c.RunDockerCmd("exec", projectName+"-nginx-3", "cat", "/tmp/indexed.txt").Stdout()
|
||||
@@ -70,7 +70,7 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy to container with the all flag copies the file to all containers", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--all", "./fixtures/cp-test/cp-me.txt", "nginx:/tmp/all.txt")
|
||||
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()
|
||||
@@ -84,7 +84,7 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy from a container copies the file to the host from the first container by default", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/default.txt", "./fixtures/cp-test/from-default.txt")
|
||||
res := c.RunDockerComposeCmd("-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 +93,7 @@ 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.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "--index=3", "nginx:/tmp/indexed.txt", "./fixtures/cp-test/from-indexed.txt")
|
||||
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.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
|
||||
data, err := os.ReadFile("./fixtures/cp-test/from-indexed.txt")
|
||||
@@ -102,13 +102,13 @@ func TestCopy(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("copy to and from a container also work with folder", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "./fixtures/cp-test/cp-folder", "nginx:/tmp")
|
||||
res := c.RunDockerComposeCmd("-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()
|
||||
assert.Assert(t, strings.Contains(output, `hello world from folder`), output)
|
||||
|
||||
res = c.RunDockerCmd("compose", "-f", "./fixtures/cp-test/compose.yaml", "-p", projectName, "cp", "nginx:/tmp/cp-folder", "./fixtures/cp-test/cp-folder2")
|
||||
res = c.RunDockerComposeCmd("-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")
|
||||
|
||||
21
pkg/e2e/e2e_config_plugin.go
Normal file
21
pkg/e2e/e2e_config_plugin.go
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build !standalone
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
const composeStandaloneMode = false
|
||||
21
pkg/e2e/e2e_config_standalone.go
Normal file
21
pkg/e2e/e2e_config_standalone.go
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build standalone
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
const composeStandaloneMode = true
|
||||
19
pkg/e2e/fixtures/build-test/multi-args/Dockerfile
Normal file
19
pkg/e2e/fixtures/build-test/multi-args/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG IMAGE=666
|
||||
ARG TAG=666
|
||||
|
||||
FROM ${IMAGE}:${TAG}
|
||||
RUN echo "SUCCESS"
|
||||
9
pkg/e2e/fixtures/build-test/multi-args/compose.yaml
Normal file
9
pkg/e2e/fixtures/build-test/multi-args/compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
multiargs:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
IMAGE: alpine
|
||||
TAG: latest
|
||||
labels:
|
||||
- RESULT=SUCCESS
|
||||
5
pkg/e2e/fixtures/run-test/orphan.yaml
Normal file
5
pkg/e2e/fixtures/run-test/orphan.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
command: echo "Hi there!!"
|
||||
14
pkg/e2e/fixtures/start-fail/compose.yaml
Normal file
14
pkg/e2e/fixtures/start-fail/compose.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
fail:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
healthcheck:
|
||||
test: "false"
|
||||
interval: 1s
|
||||
retries: 3
|
||||
depends:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
fail:
|
||||
condition: service_healthy
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -39,11 +40,19 @@ import (
|
||||
var (
|
||||
// DockerExecutableName is the OS dependent Docker CLI binary name
|
||||
DockerExecutableName = "docker"
|
||||
|
||||
// DockerComposeExecutableName is the OS dependent Docker CLI binary name
|
||||
DockerComposeExecutableName = "docker-" + compose.PluginName
|
||||
|
||||
// DockerScanExecutableName is the OS dependent Docker CLI binary name
|
||||
DockerScanExecutableName = "docker-scan"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
DockerExecutableName = DockerExecutableName + ".exe"
|
||||
DockerComposeExecutableName = DockerComposeExecutableName + ".exe"
|
||||
DockerScanExecutableName = DockerScanExecutableName + ".exe"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,23 +87,17 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
})
|
||||
|
||||
_ = os.MkdirAll(filepath.Join(d, "cli-plugins"), 0755)
|
||||
composePluginFile := "docker-compose"
|
||||
scanPluginFile := "docker-scan"
|
||||
if runtime.GOOS == "windows" {
|
||||
composePluginFile += ".exe"
|
||||
scanPluginFile += ".exe"
|
||||
}
|
||||
composePlugin, err := findExecutable(composePluginFile, []string{"../../bin", "../../../bin"})
|
||||
composePlugin, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("WARNING: docker-compose cli-plugin not found")
|
||||
}
|
||||
if err == nil {
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", composePluginFile))
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerComposeExecutableName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// We don't need a functional scan plugin, but a valid plugin binary
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", scanPluginFile))
|
||||
err = CopyFile(composePlugin, filepath.Join(d, "cli-plugins", DockerScanExecutableName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -104,9 +107,9 @@ func newE2eCLI(t *testing.T, binDir string) *E2eCLI {
|
||||
}
|
||||
|
||||
func dirContents(dir string) []string {
|
||||
res := []string{}
|
||||
var res []string
|
||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
res = append(res, filepath.Join(dir, path))
|
||||
res = append(res, path)
|
||||
return nil
|
||||
})
|
||||
return res
|
||||
@@ -191,11 +194,29 @@ func (c *E2eCLI) RunCmd(args ...string) *icmd.Result {
|
||||
|
||||
// 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)
|
||||
return res
|
||||
}
|
||||
|
||||
// RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
|
||||
func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
|
||||
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
|
||||
}
|
||||
args = append([]string{"compose"}, args...)
|
||||
res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
|
||||
res.Assert(c.test, icmd.Success)
|
||||
return res
|
||||
}
|
||||
|
||||
// StdoutContains returns a predicate on command result expecting a string in stdout
|
||||
func StdoutContains(expected string) func(*icmd.Result) bool {
|
||||
return func(res *icmd.Result) bool {
|
||||
@@ -230,7 +251,7 @@ func (c *E2eCLI) WaitForCondition(predicate func() (bool, string), timeout time.
|
||||
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
|
||||
}
|
||||
|
||||
//Lines split output into lines
|
||||
// Lines split output into lines
|
||||
func Lines(output string) []string {
|
||||
return strings.Split(strings.TrimSpace(output), "\n")
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ func TestIPC(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check running project", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `shareable`})
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestIPC(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
t.Run("remove ipc mode container", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("rm", "-f", "ipc_mode_container")
|
||||
|
||||
@@ -31,28 +31,28 @@ func TestLocalComposeLogs(t *testing.T) {
|
||||
const projectName = "compose-e2e-logs"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/logs-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("logs", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "--project-name", projectName, "logs")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--project-name", projectName, "logs", "ping")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--project-name", projectName, "logs", "hello", "ping")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
27
pkg/e2e/main_test.go
Normal file
27
pkg/e2e/main_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func TestComposeMetrics(t *testing.T) {
|
||||
res = c.RunDockerOrExitError("compose", "-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.Assert(t, icmd.Expected{ExitCode: 14, Err: "can't find a suitable configuration file in this directory or any parent: not found"})
|
||||
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.Assert(t, icmd.Expected{ExitCode: 16, Err: "unknown shorthand flag: 'f' in -f"})
|
||||
res = c.RunDockerOrExitError("compose", "up", "--file", "fixtures/wrong-composefile/compose.yaml")
|
||||
|
||||
@@ -37,11 +37,11 @@ func TestNetworks(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/network-test/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check running project", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
|
||||
res := c.RunDockerComposeCmd("-p", projectName, "ps")
|
||||
res.Assert(t, icmd.Expected{Out: `web`})
|
||||
|
||||
endpoint := "http://localhost:80"
|
||||
@@ -54,12 +54,12 @@ func TestNetworks(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("port", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "--project-name", projectName, "port", "words", "8080")
|
||||
res := c.RunDockerComposeCmd("--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.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
t.Run("check networks after down", func(t *testing.T) {
|
||||
@@ -75,21 +75,21 @@ func TestNetworkAliassesAndLinks(t *testing.T) {
|
||||
const projectName = "network_alias_e2e"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("curl alias", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://alias-of-container2/")
|
||||
res := c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "-f", "./fixtures/network-alias/compose.yaml", "--project-name", projectName, "exec", "-T", "container1", "curl", "http://container/")
|
||||
res := c.RunDockerComposeCmd("-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.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestIPAMConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/ipam/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("ensure service get fixed IP assigned", func(t *testing.T) {
|
||||
@@ -112,7 +112,7 @@ func TestIPAMConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,12 +122,12 @@ func TestNetworkModes(t *testing.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", "mydb", "echo", "success")
|
||||
res := c.RunDockerOrExitError("compose", "-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.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,18 +36,18 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
|
||||
c.RunDockerOrExitError("scan", "--help")
|
||||
|
||||
t.Run("display on compose build", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-compose-build", "build")
|
||||
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-compose-build", "build")
|
||||
defer c.RunDockerOrExitError("rmi", "-f", "scan-msg-test-compose-build_nginx")
|
||||
res.Assert(t, icmd.Expected{Err: utils.ScanSuggestMsg})
|
||||
})
|
||||
|
||||
t.Run("do not display on compose build with quiet flag", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-quiet", "build", "--quiet")
|
||||
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-quiet", "build", "--quiet")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
|
||||
res = c.RunDockerCmd("rmi", "-f", "scan-msg-test-quiet_nginx")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "No such image"))
|
||||
|
||||
res = c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-q", "build", "-q")
|
||||
res = c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test-q", "build", "-q")
|
||||
defer c.RunDockerOrExitError("rmi", "-f", "scan-msg-test-q_nginx")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
|
||||
})
|
||||
@@ -55,13 +55,13 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
|
||||
_ = c.RunDockerOrExitError("rmi", "scan-msg-test_nginx")
|
||||
|
||||
t.Run("display on compose up if image is built", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
|
||||
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
|
||||
defer c.RunDockerOrExitError("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "down")
|
||||
res.Assert(t, icmd.Expected{Err: utils.ScanSuggestMsg})
|
||||
})
|
||||
|
||||
t.Run("do not display on compose up if no image built", func(t *testing.T) { // re-run the same Compose aproject
|
||||
res := c.RunDockerCmd("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
|
||||
res := c.RunDockerComposeCmd("-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "up", "-d")
|
||||
defer c.RunDockerOrExitError("compose", "-f", "fixtures/simple-build-test/compose.yaml", "-p", "scan-msg-test", "down", "--rmi", "all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
|
||||
})
|
||||
|
||||
33
pkg/e2e/start_fail_test.go
Normal file
33
pkg/e2e/start_fail_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 TestStartFail(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
const projectName = "e2e-start-fail"
|
||||
|
||||
res := c.RunDockerOrExitError("compose", "-f", "fixtures/start-fail/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container for service "fail" is unhealthy`})
|
||||
|
||||
c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
}
|
||||
@@ -42,56 +42,56 @@ func TestStartStop(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Up a project", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
res := c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-simple-1 Started"), res.Combined())
|
||||
|
||||
res = c.RunDockerCmd("compose", "ls", "--all")
|
||||
res = c.RunDockerComposeCmd("ls", "--all")
|
||||
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps")
|
||||
res = c.RunDockerComposeCmd("--project-name", projectName, "ps")
|
||||
testify.Regexp(t, getServiceRegx("simple", "running"), res.Stdout())
|
||||
testify.Regexp(t, getServiceRegx("another", "running"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("stop project", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop")
|
||||
|
||||
res := c.RunDockerCmd("compose", "ls")
|
||||
res := c.RunDockerComposeCmd("ls")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop"), res.Combined())
|
||||
|
||||
res = c.RunDockerCmd("compose", "ls", "--all")
|
||||
res = c.RunDockerComposeCmd("ls", "--all")
|
||||
testify.Regexp(t, getProjectRegx("exited"), res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps")
|
||||
res = c.RunDockerComposeCmd("--project-name", projectName, "ps")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-words-1"), res.Combined())
|
||||
|
||||
res = c.RunDockerCmd("compose", "--project-name", projectName, "ps", "--all")
|
||||
res = c.RunDockerComposeCmd("--project-name", projectName, "ps", "--all")
|
||||
testify.Regexp(t, getServiceRegx("simple", "exited"), res.Stdout())
|
||||
testify.Regexp(t, getServiceRegx("another", "exited"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("start project", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "start")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "start")
|
||||
|
||||
res := c.RunDockerCmd("compose", "ls")
|
||||
res := c.RunDockerComposeCmd("ls")
|
||||
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("pause project", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "pause")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "pause")
|
||||
|
||||
res := c.RunDockerCmd("compose", "ls", "--all")
|
||||
res := c.RunDockerComposeCmd("ls", "--all")
|
||||
testify.Regexp(t, getProjectRegx("paused"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("unpause project", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "unpause")
|
||||
c.RunDockerComposeCmd("-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "unpause")
|
||||
|
||||
res := c.RunDockerCmd("compose", "ls")
|
||||
res := c.RunDockerComposeCmd("ls")
|
||||
testify.Regexp(t, getProjectRegx("running"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
|
||||
_ = c.RunDockerComposeCmd("--project-name", projectName, "down")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestLocalComposeVolume(t *testing.T) {
|
||||
c.RunDockerOrExitError("rmi", "compose-e2e-volume_nginx")
|
||||
c.RunDockerOrExitError("volume", "rm", projectName+"_staticVol")
|
||||
c.RunDockerOrExitError("volume", "rm", "myvolume")
|
||||
c.RunDockerCmd("compose", "--project-directory", "fixtures/volume-test", "--project-name", projectName, "up", "-d")
|
||||
c.RunDockerComposeCmd("--project-directory", "fixtures/volume-test", "--project-name", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("access bind mount data", func(t *testing.T) {
|
||||
@@ -82,9 +82,9 @@ func TestLocalComposeVolume(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("cleanup volume project", func(t *testing.T) {
|
||||
c.RunDockerCmd("compose", "--project-name", projectName, "down", "--volumes")
|
||||
res := c.RunDockerCmd("volume", "ls")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), projectName+"_staticVol"))
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "myvolume"))
|
||||
c.RunDockerComposeCmd("--project-name", projectName, "down", "--volumes")
|
||||
ls := c.RunDockerCmd("volume", "ls").Stdout()
|
||||
assert.Assert(t, !strings.Contains(ls, projectName+"_staticVol"))
|
||||
assert.Assert(t, !strings.Contains(ls, "myvolume"))
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,21 @@ func StartedEvent(ID string) Event {
|
||||
return NewEvent(ID, Done, "Started")
|
||||
}
|
||||
|
||||
// Waiting creates a new waiting event
|
||||
func Waiting(ID string) Event {
|
||||
return NewEvent(ID, Working, "Waiting")
|
||||
}
|
||||
|
||||
// Healthy creates a new healthy event
|
||||
func Healthy(ID string) Event {
|
||||
return NewEvent(ID, Done, "Healthy")
|
||||
}
|
||||
|
||||
// Exited creates a new exited event
|
||||
func Exited(ID string) Event {
|
||||
return NewEvent(ID, Done, "Exited")
|
||||
}
|
||||
|
||||
// RestartingEvent creates a new Restarting in progress Event
|
||||
func RestartingEvent(ID string) Event {
|
||||
return NewEvent(ID, Working, "Restarting")
|
||||
|
||||
@@ -27,7 +27,10 @@ func (p *noopWriter) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *noopWriter) Event(e Event) {
|
||||
func (p *noopWriter) Event(Event) {
|
||||
}
|
||||
|
||||
func (p *noopWriter) Events([]Event) {
|
||||
}
|
||||
|
||||
func (p *noopWriter) TailMsgf(_ string, _ ...interface{}) {
|
||||
|
||||
@@ -40,6 +40,12 @@ func (p *plainWriter) Event(e Event) {
|
||||
fmt.Fprintln(p.out, e.ID, e.Text, e.StatusText)
|
||||
}
|
||||
|
||||
func (p *plainWriter) Events(events []Event) {
|
||||
for _, e := range events {
|
||||
p.Event(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *plainWriter) TailMsgf(m string, args ...interface{}) {
|
||||
fmt.Fprintln(p.out, append([]interface{}{m}, args...)...)
|
||||
}
|
||||
|
||||
@@ -95,6 +95,12 @@ func (w *ttyWriter) Event(e Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ttyWriter) Events(events []Event) {
|
||||
for _, e := range events {
|
||||
w.Event(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ttyWriter) TailMsgf(msg string, args ...interface{}) {
|
||||
w.mtx.Lock()
|
||||
defer w.mtx.Unlock()
|
||||
|
||||
@@ -31,6 +31,7 @@ type Writer interface {
|
||||
Start(context.Context) error
|
||||
Stop()
|
||||
Event(Event)
|
||||
Events([]Event)
|
||||
TailMsgf(string, ...interface{})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringContains check if an array contains a specific value
|
||||
func StringContains(array []string, needle string) bool {
|
||||
for _, val := range array {
|
||||
@@ -25,3 +30,9 @@ func StringContains(array []string, needle string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// StringToBool converts a string to a boolean ignoring errors
|
||||
func StringToBool(s string) bool {
|
||||
b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s)))
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user