diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7af40b02..f9a2ede2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,6 +195,12 @@ jobs: with: name: compose path: ${{ env.DESTDIR }} + - + name: Create checksums + working-directory: ${{ env.DESTDIR }} + run: | + find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# .*/# #' > checksums.txt + shasum -a 256 -U -c checksums.txt - name: License run: cp packaging/* ${{ env.DESTDIR }}/ diff --git a/Dockerfile b/Dockerfile index 856b6a0a6..bd78ad296 100644 --- a/Dockerfile +++ b/Dockerfile @@ -167,7 +167,6 @@ COPY --link --from=build /usr/bin/docker-compose /docker-compose.exe FROM binary-$TARGETOS AS binary FROM --platform=$BUILDPLATFORM alpine AS releaser -RUN apk add --no-cache file perl-utils WORKDIR /work ARG TARGETOS ARG TARGETARCH @@ -177,8 +176,7 @@ RUN --mount=from=binary \ # TODO: should just use standard arch TARGETARCH=$([ "$TARGETARCH" = "amd64" ] && echo "x86_64" || echo "$TARGETARCH"); \ TARGETARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "$TARGETARCH"); \ - cp docker-compose* "/out/docker-compose-${TARGETOS}-${TARGETARCH}${TARGETVARIANT}$(ls docker-compose* | sed -e 's/^docker-compose//')" && \ - (cd /out ; for f in *; do shasum --binary --algorithm 256 $f | tee -a /out/checksums.txt > $f.sha256; done) + cp docker-compose* "/out/docker-compose-${TARGETOS}-${TARGETARCH}${TARGETVARIANT}$(ls docker-compose* | sed -e 's/^docker-compose//')" FROM scratch AS release COPY --from=releaser /out/ / diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 10d110884..bc3799e6a 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -32,6 +32,8 @@ import ( dockercli "github.com/docker/cli/cli" "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/flags" + "github.com/docker/docker/client" "github.com/morikuni/aec" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -291,6 +293,18 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { if err != nil { return err } + + // Reset DockerCli and APIClient to get possible `DOCKER_HOST` and/or `DOCKER_CONTEXT` loaded from environment file. + err = dockerCli.Apply(func(cli *command.DockerCli) error { + return cli.Initialize(flags.NewClientOptions(), + command.WithInitializeClient(func(_ *command.DockerCli) (client.APIClient, error) { + return nil, nil + })) + }) + if err != nil { + return err + } + parent := cmd.Root() if parent != nil { parentPrerun := parent.PersistentPreRunE @@ -383,8 +397,10 @@ func setEnvWithDotEnv(prjOpts *projectOptions) error { return err } for k, v := range envFromFile { - if err := os.Setenv(k, v); err != nil { // overwrite the process env with merged OS + env file results - return err + if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env + if err := os.Setenv(k, v); err != nil { + return err + } } } return nil diff --git a/docker-bake.hcl b/docker-bake.hcl index 80d6b0546..8946f79be 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -100,8 +100,10 @@ target "binary-cross" { "linux/arm/v7", "linux/arm64", "linux/ppc64le", + "linux/riscv64", "linux/s390x", - "windows/amd64" + "windows/amd64", + "windows/arm64" ] } diff --git a/go.mod b/go.mod index 97b04f592..da18fccde 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.5 github.com/buger/goterm v1.0.4 github.com/cnabio/cnab-to-oci v0.3.6 - github.com/compose-spec/compose-go v1.4.0 + github.com/compose-spec/compose-go v1.5.0 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.6.8 github.com/distribution/distribution/v3 v3.0.0-20220729163034-26163d82560f @@ -19,7 +19,7 @@ require ( github.com/golang/mock v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.6.0 - github.com/mattn/go-isatty v0.0.14 + github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-shellwords v1.0.12 github.com/moby/buildkit v0.10.3 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 @@ -111,7 +111,7 @@ require ( golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect diff --git a/go.sum b/go.sum index 0e7dbbab5..8bf98b8d6 100644 --- a/go.sum +++ b/go.sum @@ -286,8 +286,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8= -github.com/compose-spec/compose-go v1.4.0 h1:zaYVAZ6lIByr7Jffi20AabfeUwcTrdXfH3X1R5HEm+g= -github.com/compose-spec/compose-go v1.4.0/go.mod h1:l7RUULbFFLzlQHuxtJr7SVLyWdqEpbJEGTWCgcu6Eqw= +github.com/compose-spec/compose-go v1.5.0 h1:yOmYpIm13pYt2o+oKVe/JAD6o2Tv+eUyOcRhf0qF4fA= +github.com/compose-spec/compose-go v1.5.0/go.mod h1:l7RUULbFFLzlQHuxtJr7SVLyWdqEpbJEGTWCgcu6Eqw= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -971,8 +971,9 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -1795,8 +1796,9 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/api/proxy.go b/pkg/api/proxy.go index 5dcfa0e7f..d023714bf 100644 --- a/pkg/api/proxy.go +++ b/pkg/api/proxy.go @@ -22,6 +22,8 @@ import ( "github.com/compose-spec/compose-go/types" ) +var _ Service = &ServiceProxy{} + // ServiceProxy implements Service by delegating to implementation functions. This allows lazy init and per-method overrides type ServiceProxy struct { BuildFn func(ctx context.Context, project *types.Project, options BuildOptions) error @@ -59,8 +61,6 @@ func NewServiceProxy() *ServiceProxy { // Interceptor allow to customize the compose types.Project before the actual Service method is executed type Interceptor func(ctx context.Context, project *types.Project) -var _ Service = &ServiceProxy{} - // WithService configure proxy to use specified Service as delegate func (s *ServiceProxy) WithService(service Service) *ServiceProxy { s.BuildFn = service.Build diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index a2e434817..78ef972a8 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -352,6 +352,12 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD return false, nil } if service, err := project.GetService(serviceName); err != nil { + for _, ds := range project.DisabledServices { + if ds.Name == serviceName { + // don't wait for disabled service (--no-deps) + return false, nil + } + } return false, err } else if service.Scale == 0 { // don't wait for the dependency which configured to have 0 containers running diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 3ee694b0e..74a2184ac 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -146,6 +146,9 @@ func prepareNetworks(project *types.Project) { } func prepareServicesDependsOn(p *types.Project) error { + allServices := types.Project{} + allServices.Services = p.AllServices() + for i, service := range p.Services { var dependencies []string networkDependency := getDependentServiceFromMode(service.NetworkMode) @@ -178,20 +181,24 @@ func prepareServicesDependsOn(p *types.Project) error { dependencies = append(dependencies, strings.Split(link, ":")[0]) } + for d := range service.DependsOn { + dependencies = append(dependencies, d) + } + if len(dependencies) == 0 { continue } + + // Verify dependencies exist in the project, whether disabled or not + deps, err := allServices.GetServices(dependencies...) + if err != nil { + return err + } + if service.DependsOn == nil { service.DependsOn = make(types.DependsOnConfig) } - // Verify dependencies exist in the project, whether disabled or not - projAllServices := types.Project{} - projAllServices.Services = p.AllServices() - deps, err := projAllServices.GetServices(dependencies...) - if err != nil { - return err - } for _, d := range deps { if _, ok := service.DependsOn[d.Name]; !ok { service.DependsOn[d.Name] = types.ServiceDependency{ diff --git a/pkg/e2e/compose_environment_test.go b/pkg/e2e/compose_environment_test.go index c57b58077..a0cfca863 100644 --- a/pkg/e2e/compose_environment_test.go +++ b/pkg/e2e/compose_environment_test.go @@ -34,7 +34,7 @@ func TestEnvPriority(t *testing.T) { }) // Full options activated - // 1. Command Line (docker compose run --env ) <-- Result expected (From environment patched by --env-file) + // 1. Command Line (docker compose run --env ) <-- Result expected (From OS Environment) // 2. Compose File (service::environment section) // 3. Compose File (service::env_file section file) // 4. Container Image ENV directive @@ -45,7 +45,7 @@ func TestEnvPriority(t *testing.T) { "run", "--rm", "-e", "WHEREAMI", "env-compose-priority") cmd.Env = append(cmd.Env, "WHEREAMI=shell") res := icmd.RunCmd(cmd) - assert.Equal(t, strings.TrimSpace(res.Stdout()), "override") + assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell") }) // Full options activated @@ -63,7 +63,7 @@ func TestEnvPriority(t *testing.T) { }) // No Compose file, all other options - // 1. Command Line (docker compose run --env ) <-- Result expected (From environment patched by --env-file) + // 1. Command Line (docker compose run --env ) <-- Result expected (From OS Environment) // 2. Compose File (service::environment section) // 3. Compose File (service::env_file section file) // 4. Container Image ENV directive @@ -74,7 +74,7 @@ func TestEnvPriority(t *testing.T) { "run", "--rm", "-e", "WHEREAMI", "env-compose-priority") cmd.Env = append(cmd.Env, "WHEREAMI=shell") res := icmd.RunCmd(cmd) - assert.Equal(t, strings.TrimSpace(res.Stdout()), "override") + assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell") }) // No Compose file, all other options with env variable from OS environment diff --git a/pkg/e2e/fixtures/dependencies/recreate-no-deps.yaml b/pkg/e2e/fixtures/dependencies/recreate-no-deps.yaml new file mode 100644 index 000000000..b69e6e0de --- /dev/null +++ b/pkg/e2e/fixtures/dependencies/recreate-no-deps.yaml @@ -0,0 +1,15 @@ +version: '3.8' +services: + my-service: + image: alpine + command: tail -f /dev/null + depends_on: + nginx: {condition: service_healthy} + + nginx: + image: nginx:alpine + healthcheck: + test: "echo | nc -w 5 localhost:80" + interval: 2s + timeout: 1s + retries: 10 diff --git a/pkg/e2e/recreate_no_deps_test.go b/pkg/e2e/recreate_no_deps_test.go new file mode 100644 index 000000000..2b32e0d5b --- /dev/null +++ b/pkg/e2e/recreate_no_deps_test.go @@ -0,0 +1,39 @@ +/* + Copyright 2022 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package e2e + +import ( + "testing" + + "gotest.tools/v3/icmd" +) + +func TestRecreateWithNoDeps(t *testing.T) { + c := NewParallelCLI(t, WithEnv( + "COMPOSE_PROJECT_NAME=recreate-no-deps", + )) + + res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/dependencies/recreate-no-deps.yaml", "up", "-d") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/dependencies/recreate-no-deps.yaml", "up", "-d", "--force-recreate", "--no-deps", "my-service") + res.Assert(t, icmd.Success) + + RequireServiceState(t, c, "my-service", "running") + + c.RunDockerComposeCmd(t, "down") +}