Compare commits

...

28 Commits

Author SHA1 Message Date
Ulysses Souza
e2f33af831 Merge and fix Convert function from docker/compose-switch
This also removes the vendoring of the repo
Note that it fixes the problem of double "compose" on calling
the compose plugin with "docker" root flags.
Like in "docker --context default compose version"

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-03 12:17:17 +01:00
Guillaume Lours
19b9fdf536 upgrade version of opencontainers/image-spec (security issue)
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-12-03 12:16:14 +01:00
Guillaume Lours
a842522f49 use filepath instead of path to check if the dockerfile path is abolute or not
Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
2021-12-03 12:15:22 +01:00
Ulysses Souza
bc1160de72 Turn external volume usage into a warning instead of erroring
This avoids a compatibility break since returning an error would avoid
the project to be started

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-12-02 13:11:20 +01:00
Ulysses Souza
f791bc8a42 Merge pull request #8984 from ndeloof/logs_restart
compose logs to notify printer about container lifecycle events
2021-12-02 09:27:11 +01:00
Nicolas De Loof
0d7567131a compose logs to notify printer about container lifecycle events
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-12-02 09:18:49 +01:00
Mathieu Champlon
7b84f2c2a5 Merge pull request #8974 from ulyssessouza/fix-links-resolution3
Return an error when failing to list containers
2021-11-29 10:34:00 +01:00
Ulysses Souza
cf7b1441d9 Return an error when failing to list containers
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-29 10:28:45 +01:00
Mathieu Champlon
32005b0bfe Merge pull request #8953 from ulyssessouza/test-multiargs
Add multiargs build e2e tests
2021-11-29 09:46:47 +01:00
Mathieu Champlon
025a72a417 Merge pull request #8972 from ulyssessouza/fix-links-resolution2
Refactoring variable name
2021-11-29 09:46:24 +01:00
Ulysses Souza
95c4502b81 Refactoring variable name
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-28 15:43:27 +01:00
Ulysses Souza
2290ce2c24 Merge pull request #8970 from ndeloof/external_volume_check
Don't check compose labels on external volumes
2021-11-28 15:36:48 +01:00
Nicolas De Loof
bac732837e Don't check compose labels on external volumes
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-27 16:34:42 +01:00
Ulysses Souza
b725c56c42 Fix links resolution
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-26 20:20:55 +01:00
Ulysses Souza
cfcc9533b3 Add multiargs build e2e tests
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2021-11-25 21:22:30 +01:00
Mathieu Champlon
cffdb69c5e Fix test config dir content
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-25 21:22:30 +01:00
Kyungsik Park
709190312c Fix to use Key instead of Service for graph updates
Signed-off-by: Kyungsik Park <kay.pak@naverlabs.com>
2021-11-24 08:02:50 +01:00
Mathieu Champlon
e1a38f984b Merge pull request #8947 from ndeloof/run_quietpull
introduce run —quiet-pull to align with up
2021-11-23 19:17:08 +01:00
Nicolas De Loof
4dafeb57a5 introduce run —quiet-pull to align with up
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-23 19:14:12 +01:00
Nicolas De Loof
45956c36fb introduce docker compose config --images
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-23 19:13:04 +01:00
Ulysses Souza
9eb69465b7 Merge pull request #8834 from akerouanton/fix-start-0-replicas
v2: Don't try to start services with 0 replicas
2021-11-23 14:53:08 +01:00
Albin Kerouanton
5f392258cb Don't try to start services with 0 replicas
When a service has 0 replicas to start, it don't try to create any
container. However `composeService.startService()` still gets executed.
This method checks if there's been containers created for that service
beforehand. If there's none, it returns the following error: `no
containers to start`.

This change checks if replicas == 0 and exits early from
`composeService.startService()` when that's the case.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2021-11-23 11:57:09 +01:00
Mathieu Champlon
382c1cd68e Merge pull request #8943 from ndeloof/config_no_normalize
don't normalize compose model in compatibility mode
2021-11-23 11:16:36 +01:00
Mathieu Champlon
5994050f51 Merge pull request #8956 from mat007/bump-compose-go
Bump compose-go to v1.0.8
2021-11-23 11:10:57 +01:00
Mathieu Champlon
28a00571ef Bump compose-go to v1.0.8
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-23 11:05:26 +01:00
Nicolas De Loof
d00eacbba0 don't normalize compose model in compatibility mode
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2021-11-22 21:02:44 +01:00
Ulysses Souza
a6c76a9c0f Merge pull request #8954 from mat007/bump-compose-go
Bump compose-go to v1.0.7
2021-11-22 18:18:41 +01:00
Mathieu Champlon
5754d6084c Bump compose-go to v1.0.7
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2021-11-22 18:13:07 +01:00
21 changed files with 517 additions and 46 deletions

View File

@@ -212,11 +212,11 @@ func (o *projectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
cli.WithName(o.ProjectName))...)
}
const pluginName = "compose"
const PluginName = "compose"
// RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone() bool {
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != pluginName
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
}
// RootCommand returns the compose command with its child commands
@@ -230,7 +230,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 {

View File

@@ -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,10 @@ 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))
if err != nil {
return err
}
@@ -207,3 +220,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
}

View File

@@ -54,6 +54,7 @@ type runOptions struct {
servicePorts bool
name string
noDeps bool
quietPull bool
}
func (opts runOptions) apply(project *types.Project) error {
@@ -153,6 +154,7 @@ 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.")
flags.SetNormalizeFunc(normalizeRunFlags)
flags.SetInterspersed(false)
@@ -215,6 +217,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
UseNetworkAliases: opts.useAliases,
NoDeps: opts.noDeps,
Index: 0,
QuietPull: opts.quietPull,
}
exitCode, err := backend.RunOneOffContainer(ctx, project, runOpts)
if exitCode != 0 {

96
cmd/convert.go Normal file
View File

@@ -0,0 +1,96 @@
/*
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 main
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",
}
}
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/convert_test.go Normal file
View 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 main
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)
})
}
}

View File

@@ -23,7 +23,6 @@ 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"
commands "github.com/docker/compose/v2/cmd/compose"
@@ -69,7 +68,7 @@ func pluginMain() {
func main() {
if commands.RunningAsStandalone() {
os.Args = append([]string{"docker"}, redirect.Convert(os.Args[1:])...)
os.Args = append([]string{"docker"}, convert(os.Args[1:])...)
}
pluginMain()
}

7
go.mod
View File

@@ -6,14 +6,13 @@ require (
github.com/AlecAivazis/survey/v2 v2.2.3
github.com/buger/goterm v1.0.0
github.com/cnabio/cnab-to-oci v0.3.1-beta1
github.com/compose-spec/compose-go v1.0.5
github.com/compose-spec/compose-go v1.0.8
github.com/containerd/console v1.0.2
github.com/containerd/containerd v1.5.8
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
github.com/docker/buildx v0.5.2-0.20210422185057-908a856079fc
github.com/docker/cli v20.10.7+incompatible
github.com/docker/cli-docs-tool v0.1.1
github.com/docker/compose-switch v1.0.2
github.com/docker/docker v20.10.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
@@ -26,7 +25,7 @@ require (
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
@@ -47,7 +46,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cnabio/cnab-go v0.10.0-beta1 // indirect
github.com/compose-spec/godotenv v1.1.0 // indirect
github.com/compose-spec/godotenv v1.1.1 // indirect
github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect

14
go.sum
View File

@@ -197,10 +197,10 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/compose-spec/compose-go v1.0.5 h1:WtfK7tJsk5C8h12iggum7p28kTxeXH7Xi5c/pLfnBwk=
github.com/compose-spec/compose-go v1.0.5/go.mod h1:LQ/JAjSIyh8bTu4RV6nkyf0Ow/Yf3qpvzrdEigxduiw=
github.com/compose-spec/godotenv v1.1.0 h1:wzShe5P6L/Aw3wsV357eWlZdMcPaOe2V2+3+qGwMEL4=
github.com/compose-spec/godotenv v1.1.0/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
github.com/compose-spec/compose-go v1.0.8 h1:fgT7mYYu5Sp37i2lUIAAvwJpkAHk6dP5ITHy/LlutUk=
github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
github.com/compose-spec/godotenv v1.1.1 h1:lp+WpAInnw06YN9sV/XLUOV/9z4C+6wjJdWlrdVac7o=
github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@@ -353,8 +353,6 @@ github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv
github.com/docker/cli-docs-tool v0.1.1 h1:c6vuTMvogCkSFQCXIr6Mb4gFgUpdZ+28YMbCBfaQLik=
github.com/docker/cli-docs-tool v0.1.1/go.mod h1:oMzPNt1wC3TcxuY22GMnOODNOxkwGH51gV3AhqAjFQ4=
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE=
github.com/docker/compose-switch v1.0.2 h1:chXFNNcnRvmtQYzwTaVsv/KSLRt8riSRAiSav89mLfk=
github.com/docker/compose-switch v1.0.2/go.mod h1:uyPj8S3oH1O9rSZ5QVozw28OIjdNIflSSYElC2P0plQ=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
@@ -828,8 +826,9 @@ github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
@@ -966,7 +965,6 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=

View File

@@ -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
}

View File

@@ -20,7 +20,6 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"github.com/compose-spec/compose-go/types"
@@ -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)

View File

@@ -261,6 +261,7 @@ func getContainerProgressName(container moby.Container) string {
return "Container " + getCanonicalContainerName(container)
}
// 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 {
@@ -448,7 +449,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 +480,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] {
@@ -559,6 +621,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 +645,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)

View File

@@ -17,10 +17,16 @@
package compose
import (
"context"
"fmt"
"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 +52,135 @@ 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")
})
}

View File

@@ -1092,18 +1092,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.External.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
}

View File

@@ -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)
})

View File

@@ -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()
}

View File

@@ -156,7 +156,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
service.Labels = service.Labels.Add(api.SlugLabel, slug)
service.Labels = service.Labels.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 {

View File

@@ -66,6 +66,19 @@ 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")

View 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"

View File

@@ -0,0 +1,9 @@
services:
multiargs:
build:
context: .
args:
IMAGE: alpine
TAG: latest
labels:
- RESULT=SUCCESS

View File

@@ -104,9 +104,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

View File

@@ -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")