1
0
mirror of https://github.com/docker/compose.git synced 2025-04-08 17:05:13 +02:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Nicolas De Loof
ee33143026 capture git fetch output when debug output is enabled
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-04-08 12:03:41 +02:00
dependabot[bot]
cb0b5f6e27 build(deps): bump golang.org/x/sync from 0.12.0 to 0.13.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/sync/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 11:51:03 +02:00
dependabot[bot]
1384853538 build(deps): bump golang.org/x/sys from 0.31.0 to 0.32.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/sys/compare/v0.31.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 11:39:33 +02:00
Guillaume Lours
096b1e32d3 plugin provider support: check docker model runner status
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-04-08 11:19:01 +02:00
Guillaume Lours
bf71138df6 cleanup runPluging function
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-04-08 11:19:01 +02:00
sigi-glovebox
a1f673dcf5 Update secret detector to fix vulnerability https://github.com/golang-jwt/jwt/security/advisories/GHSA-mh63-6h87-95cp
Signed-off-by: sigi-glovebox <sigi@gloveboxapp.com>
2025-04-03 21:06:26 +02:00
Guillaume Lours
02c747a7de bump compose-go to custom version of v2.5.0
should be replace by v2.5.1 it will be released

Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-04-03 15:13:44 +02:00
Nicolas De Loof
88f4f265db communicate with plugin using json events
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-04-03 15:13:44 +02:00
Nicolas De Loof
e67348222f DRAFT external services plugin support
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-04-03 15:13:44 +02:00
Suleiman Dibirov
b543380708 feat(run): Add --quiet and --quiet-build options for the run command
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
2025-04-03 14:50:38 +02:00
Guillaume Lours
2e75185a07 bump golang to 1.23.8
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-04-02 16:31:43 +02:00
Guillaume Lours
7bedb5a02c bump golangci-lint to version v2.0.2
and apply migration script

Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2025-03-31 18:50:15 +02:00
Nicolas De Loof
f9cd4d0b1d bump docker,cli,buildx
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-31 11:05:19 +02:00
Nicolas De Loof
0badcf3c8d include implicit build dependencies in build command
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-28 10:25:41 +01:00
k-kbk
ec49db98d4 fix: replace docker-compose.yml with compose.yaml
Signed-off-by: k-kbk <kkbk0077@gmail.com>
2025-03-27 15:44:00 +01:00
k-kbk
e5a353b34d fix: replace docker-compose.yml with compose.yaml
Signed-off-by: k-kbk <kkbk0077@gmail.com>
2025-03-27 15:44:00 +01:00
Nicolas De Loof
43e456145c fix scale completion
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-26 17:44:54 +01:00
Nicolas De Loof
75368c7859 introduce build --print to dump equivalent bakefile
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-26 12:24:31 +01:00
Suleiman Dibirov
6e814eac35 fix(secrets): Reverted secrets file mode 440 -> 444
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
2025-03-25 07:00:24 +01:00
Nicolas De Loof
a0d1c3f944 introduce config --no-env-resolution
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-24 15:46:04 +01:00
Nicolas De Loof
0c5bd16da1 bake parses "${}" in DockerfileInline as a variable
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-24 11:36:42 +01:00
Remco Kranenburg
b0badf1eb0 Set watch option --prune=true as default
Signed-off-by: Remco Kranenburg <remco.kranenburg@crunchr.com>
2025-03-19 17:48:05 +01:00
Nicolas De Loof
342a2a9e71 Fix support for depends_on.restart in up and restart commands
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-19 15:29:50 +01:00
dependabot[bot]
7814e5798c build(deps): bump github.com/containerd/containerd/v2
Bumps [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 11:02:40 +01:00
dependabot[bot]
42b2e11094 build(deps): bump github.com/docker/buildx from 0.21.2 to 0.21.3
Bumps [github.com/docker/buildx](https://github.com/docker/buildx) from 0.21.2 to 0.21.3.
- [Release notes](https://github.com/docker/buildx/releases)
- [Commits](https://github.com/docker/buildx/compare/v0.21.2...v0.21.3)

---
updated-dependencies:
- dependency-name: github.com/docker/buildx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 10:45:25 +01:00
Nicolas De Loof
6a8c0988cf run only loads required service env_file and ignores others
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-18 09:39:15 +01:00
Matiboux
9129abe516 Fix edge-case bug path prefix check for watch & bind mounts
Signed-off-by: Matiboux <matiboux@gmail.com>
2025-03-17 17:40:40 +01:00
Nicolas De Loof
f38f3f754c PWD
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-17 17:26:45 +01:00
Nicolas De Loof
ea07ba8e2a fix support for secret set by env inside included file
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-03-14 16:21:45 +01:00
Max Proske
432ae23b0e Test commandName subcommand order
Signed-off-by: Max Proske <max@mproske.com>
2025-03-14 10:00:45 +01:00
58 changed files with 669 additions and 162 deletions

@ -1,9 +1,8 @@
version: "2"
run:
concurrency: 2
timeout: 10m
linters:
enable-all: false
disable-all: true
default: none
enable:
- copyloopvar
- depguard
@ -11,73 +10,74 @@ linters:
- errorlint
- gocritic
- gocyclo
- gofumpt
- goimports
- gomodguard
- revive
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- nolintlint
- revive
- staticcheck
- testifylint
- typecheck
- unconvert
- unparam
- unused
linters-settings:
revive:
rules:
- name: package-comments
disabled: true
depguard:
rules:
all:
deny:
- pkg: io/ioutil
desc: 'io/ioutil package has been deprecated'
- pkg: gopkg.in/yaml.v2
desc: 'compose-go uses yaml.v3'
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
- fmt
versions:
- github.com/distribution/distribution:
reason: "use distribution/reference"
- gotest.tools:
version: "< 3.0.0"
reason: "deprecated, pre-modules version"
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- diagnostic
- opinionated
- style
disabled-checks:
- paramTypeCombine
- unnamedResult
- whyNoLint
gocyclo:
min-complexity: 16
lll:
line-length: 200
settings:
depguard:
rules:
all:
deny:
- pkg: io/ioutil
desc: io/ioutil package has been deprecated
- pkg: gopkg.in/yaml.v2
desc: compose-go uses yaml.v3
gocritic:
disabled-checks:
- paramTypeCombine
- unnamedResult
- whyNoLint
enabled-tags:
- diagnostic
- opinionated
- style
gocyclo:
min-complexity: 16
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
- fmt
versions:
- github.com/distribution/distribution:
reason: use distribution/reference
- gotest.tools:
version: < 3.0.0
reason: deprecated, pre-modules version
lll:
line-length: 200
revive:
rules:
- name: package-comments
disabled: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# golangci hides some golint warnings (the warning about exported things
# without documentation for example), this will make it show them anyway.
exclude-use-default: false
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

@ -15,9 +15,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ARG GO_VERSION=1.23.6
ARG GO_VERSION=1.23.8
ARG XX_VERSION=1.6.1
ARG GOLANGCI_LINT_VERSION=v1.63.4
ARG GOLANGCI_LINT_VERSION=v2.0.2
ARG ADDLICENSE_VERSION=v1.0.0
ARG BUILD_TAGS="e2e"

@ -115,13 +115,14 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
}
}
// commandName returns the path components for a given command.
// commandName returns the path components for a given command,
// in reverse alphabetical order for consistent usage metrics.
//
// The root Compose command and anything before (i.e. "docker")
// are not included.
//
// For example:
// - docker compose alpha watch -> [alpha, watch]
// - docker compose alpha watch -> [watch, alpha]
// - docker-compose up -> [up]
func commandName(cmd *cobra.Command) []string {
var name []string

@ -20,6 +20,8 @@ import (
"reflect"
"testing"
commands "github.com/docker/compose/v2/cmd/compose"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
)
@ -61,3 +63,50 @@ func TestGetFlags(t *testing.T) {
})
}
}
func TestCommandName(t *testing.T) {
tests := []struct {
name string
setupCmd func() *cobra.Command
want []string
}{
{
name: "docker compose alpha watch -> [watch, alpha]",
setupCmd: func() *cobra.Command {
dockerCmd := &cobra.Command{Use: "docker"}
composeCmd := &cobra.Command{Use: commands.PluginName}
alphaCmd := &cobra.Command{Use: "alpha"}
watchCmd := &cobra.Command{Use: "watch"}
dockerCmd.AddCommand(composeCmd)
composeCmd.AddCommand(alphaCmd)
alphaCmd.AddCommand(watchCmd)
return watchCmd
},
want: []string{"watch", "alpha"},
},
{
name: "docker-compose up -> [up]",
setupCmd: func() *cobra.Command {
dockerComposeCmd := &cobra.Command{Use: commands.PluginName}
upCmd := &cobra.Command{Use: "up"}
dockerComposeCmd.AddCommand(upCmd)
return upCmd
},
want: []string{"up"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := tt.setupCmd()
got := commandName(cmd)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("commandName() = %v, want %v", got, tt.want)
}
})
}
}

@ -27,6 +27,7 @@ import (
"github.com/docker/cli/cli/command"
cliopts "github.com/docker/cli/opts"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
buildkit "github.com/moby/buildkit/util/progress/progressui"
"github.com/spf13/cobra"
@ -44,6 +45,7 @@ type buildOptions struct {
ssh string
builder string
deps bool
print bool
}
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
@ -76,6 +78,8 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
Quiet: opts.quiet,
Services: services,
Deps: opts.deps,
Memory: int64(opts.memory),
Print: opts.print,
SSHs: SSHKeys,
Builder: builderName,
}, nil
@ -131,6 +135,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
flags.MarkHidden("progress") //nolint:errcheck
flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
return cmd
}
@ -141,6 +146,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
return err
}
services = addBuildDependencies(services, project)
if err := applyPlatforms(project, false); err != nil {
return err
}
@ -150,6 +157,23 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
return err
}
apiBuildOptions.Memory = int64(opts.memory)
return backend.Build(ctx, project, apiBuildOptions)
}
func addBuildDependencies(services []string, project *types.Project) []string {
servicesWithDependencies := utils.NewSet(services...)
for _, service := range services {
build := project.Services[service].Build
if build != nil {
for _, target := range build.AdditionalContexts {
if s, found := strings.CutPrefix(target, types.ServicePrefix); found {
servicesWithDependencies.Add(s)
}
}
}
}
if len(servicesWithDependencies) > len(services) {
return addBuildDependencies(servicesWithDependencies.Elements(), project)
}
return servicesWithDependencies.Elements()
}

57
cmd/compose/build_test.go Normal file

@ -0,0 +1,57 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"slices"
"testing"
"github.com/compose-spec/compose-go/v2/types"
"gotest.tools/v3/assert"
)
func Test_addBuildDependencies(t *testing.T) {
project := &types.Project{Services: types.Services{
"test": types.ServiceConfig{
Build: &types.BuildConfig{
AdditionalContexts: map[string]string{
"foo": "service:foo",
"bar": "service:bar",
},
},
},
"foo": types.ServiceConfig{
Build: &types.BuildConfig{
AdditionalContexts: map[string]string{
"zot": "service:zot",
},
},
},
"bar": types.ServiceConfig{
Build: &types.BuildConfig{},
},
"zot": types.ServiceConfig{
Build: &types.BuildConfig{},
},
}}
services := addBuildDependencies([]string{"test"}, project)
expected := []string{"test", "foo", "bar", "zot"}
slices.Sort(services)
slices.Sort(expected)
assert.DeepEqual(t, services, expected)
}

@ -90,3 +90,13 @@ func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn
return values, cobra.ShellCompDirectiveNoFileComp
}
}
func completeScaleArgs(cli command.Cli, p *ProjectOptions) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
completions, directive := completeServiceNames(cli, p)(cmd, args, toComplete)
for i, completion := range completions {
completions[i] = completion + "="
}
return completions, directive
}
}

@ -72,17 +72,16 @@ const (
)
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(key string) (string, bool)) error {
lines, err := kvfile.ParseFromReader(r, lookup)
if err != nil {
return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err)
return fmt.Errorf("failed to parse env_file %s: %w", filename, err)
}
vars := types.Mapping{}
for _, line := range lines {
key, value, _ := strings.Cut(line, "=")
vars[key] = value
}
return vars, nil
return nil
}
func init() {
@ -383,11 +382,18 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL
}
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
return cli.NewProjectOptions(o.ConfigPaths,
append(po,
cli.WithWorkingDirectory(o.ProjectDir),
// First apply os.Environment, always win
cli.WithOsEnv,
// set PWD as this variable is not consistently supported on Windows
cli.WithEnv([]string{"PWD=" + pwd}),
// Load PWD/.env if present and no explicit --env-file has been set
cli.WithEnvFiles(o.EnvFiles...),
// read dot env file to populate project environment
@ -543,10 +549,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
}
composeCmd := cmd
for {
if composeCmd.Name() == PluginName {
break
}
for composeCmd.Name() != PluginName {
if !composeCmd.HasParent() {
return fmt.Errorf("error parsing command line, expected %q", PluginName)
}

@ -47,6 +47,7 @@ type configOptions struct {
noInterpolate bool
noNormalize bool
noResolvePath bool
noResolveEnv bool
services bool
volumes bool
profiles bool
@ -135,6 +136,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model")
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths")
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
flags.BoolVar(&opts.noResolveEnv, "no-env-resolution", false, "Don't resolve service env files")
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.")
@ -190,6 +192,13 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
}
}
if !opts.noResolveEnv {
project, err = project.WithServicesEnvironmentResolved(true)
if err != nil {
return nil, err
}
}
if !opts.noConsistency {
err := project.CheckContainerNameUnicity()
if err != nil {

@ -86,7 +86,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
if err != nil {
return err
}
projectOptions, err := opts.composeOptions.toProjectOptions()
projectOptions, err := opts.composeOptions.toProjectOptions() //nolint:staticcheck
if err != nil {
return err
}

@ -246,7 +246,7 @@ func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
}
func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
mainComposeFile := options.ProjectOptions.ConfigPaths[0]
mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
}
@ -258,8 +258,8 @@ func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYe
}
var remoteIncludes []string
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli)
for _, cf := range options.ProjectOptions.ConfigPaths {
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) //nolint:staticcheck
for _, cf := range options.ProjectOptions.ConfigPaths { //nolint:staticcheck
for _, loader := range remoteLoaders {
if loader.Accept(cf) {
remoteIncludes = append(remoteIncludes, cf)

@ -67,6 +67,7 @@ type runOptions struct {
noDeps bool
ignoreOrphans bool
removeOrphans bool
quiet bool
quietPull bool
}
@ -180,11 +181,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
options.noTty = !options.tty
}
}
if options.quiet {
progress.Mode = progress.ModeQuiet
devnull, err := os.Open(os.DevNull)
if err != nil {
return err
}
os.Stdout = devnull
}
createOpts.pullChanged = cmd.Flags().Changed("pull")
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithoutEnvironmentResolution)
if err != nil {
return err
}
project, err = project.WithServicesEnvironmentResolved(true)
if err != nil {
return err
}
@ -217,6 +231,8 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to")
flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host")
flags.StringVar(&createOpts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
flags.BoolVar(&buildOpts.quiet, "quiet-build", false, "Suppress progress output from the build process")
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
flags.BoolVar(&options.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")

@ -51,7 +51,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
ValidArgsFunction: completeScaleArgs(dockerCli, p),
}
flags := scaleCmd.Flags()
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")

@ -220,8 +220,8 @@ func TestRunTopCore(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
header, entries := collectTop([]api.ContainerProcSummary{summary})
assert.EqualValues(t, tc.header, header)
assert.EqualValues(t, tc.entries, entries)
assert.Equal(t, tc.header, header)
assert.Equal(t, tc.entries, entries)
var buf bytes.Buffer
err := topPrint(&buf, header, entries)
@ -233,7 +233,7 @@ func TestRunTopCore(t *testing.T) {
t.Run("all", func(t *testing.T) {
header, entries := collectTop(all)
assert.EqualValues(t, topHeader{
assert.Equal(t, topHeader{
"SERVICE": 0,
"#": 1,
"UID": 2,
@ -246,7 +246,7 @@ func TestRunTopCore(t *testing.T) {
"GID": 9,
"CMD": 10,
}, header)
assert.EqualValues(t, []topEntries{
assert.Equal(t, []topEntries{
{
"SERVICE": "simple",
"#": "1",

@ -59,7 +59,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
cmd.Flags().BoolVar(&watchOpts.prune, "prune", false, "Prune dangling images on rebuild")
cmd.Flags().BoolVar(&watchOpts.prune, "prune", true, "Prune dangling images on rebuild")
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
return cmd
}

@ -238,7 +238,7 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop")
err = fmt.Errorf("could not open Docker Desktop")
lk.keyboardError("View", err)
}
return err
@ -255,7 +255,7 @@ func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Proje
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
err = fmt.Errorf("could not open Docker Desktop Compose UI")
lk.keyboardError("View Config", err)
}
return err
@ -269,7 +269,7 @@ func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Proje
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
err := open.Run(link)
if err != nil {
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
err = fmt.Errorf("could not open Docker Desktop Compose UI")
lk.keyboardError("Watch Docs", err)
}
return err
@ -299,7 +299,7 @@ func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
func(ctx context.Context) error {
if options.Create.Build == nil {
err := fmt.Errorf("Cannot run watch mode with flag --no-build")
err := fmt.Errorf("cannot run watch mode with flag --no-build")
lk.keyboardError("Watch", err)
return err
}

@ -78,10 +78,10 @@ to their predecessors.
For example, consider this command line:
```console
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
```
The `docker-compose.yml` file might specify a `webapp` service.
The `compose.yaml` file might specify a `webapp` service.
```yaml
services:
@ -92,7 +92,7 @@ services:
volumes:
- "/data"
```
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
New values, add to the `webapp` service configuration.
```yaml
@ -207,4 +207,4 @@ $ docker compose --dry-run up --build -d
From the example above, you can see that the first step is to pull the image defined by `db` service, then build the `backend` service.
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.

@ -20,6 +20,7 @@ run `docker compose build` to rebuild it.
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
| `--no-cache` | `bool` | | Do not use cache when building the image |
| `--print` | `bool` | | Print equivalent bake file |
| `--pull` | `bool` | | Always attempt to pull a newer version of the image |
| `--push` | `bool` | | Push service images |
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |

@ -19,6 +19,7 @@ the canonical format.
| `--hash` | `string` | | Print the service config hash, one per line. |
| `--images` | `bool` | | Print the image names, one per line. |
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
| `--no-env-resolution` | `bool` | | Don't resolve service env files |
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
| `--no-normalize` | `bool` | | Don't normalize compose model |
| `--no-path-resolution` | `bool` | | Don't resolve file paths |

@ -74,6 +74,8 @@ specified in the service configuration.
| `--no-deps` | `bool` | | Don't start linked services |
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
| `--quiet-build` | `bool` | | Suppress progress output from the build process |
| `--quiet-pull` | `bool` | | Pull without printing progress information |
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
| `--rm` | `bool` | | Automatically remove the container when it exits |

@ -9,7 +9,7 @@ Watch build context for service and rebuild/refresh containers when files are up
|:------------|:-------|:--------|:----------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--no-up` | `bool` | | Do not build & start services before watching |
| `--prune` | `bool` | | Prune dangling images on rebuild |
| `--prune` | `bool` | `true` | Prune dangling images on rebuild |
| `--quiet` | `bool` | | hide build output |

@ -241,10 +241,10 @@ examples: |-
For example, consider this command line:
```console
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
```
The `docker-compose.yml` file might specify a `webapp` service.
The `compose.yaml` file might specify a `webapp` service.
```yaml
services:
@ -255,7 +255,7 @@ examples: |-
volumes:
- "/data"
```
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
New values, add to the `webapp` service configuration.
```yaml

@ -96,6 +96,16 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: print
value_type: bool
default_value: "false"
description: Print equivalent bake file
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: progress
value_type: string
default_value: auto

@ -59,6 +59,16 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: no-env-resolution
value_type: bool
default_value: "false"
description: Don't resolve service env files
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: no-interpolate
value_type: bool
default_value: "false"

@ -200,6 +200,27 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet
shorthand: q
value_type: bool
default_value: "false"
description: Don't print anything to STDOUT
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet-build
value_type: bool
default_value: "false"
description: Suppress progress output from the build process
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet-pull
value_type: bool
default_value: "false"

@ -19,7 +19,7 @@ options:
swarm: false
- option: prune
value_type: bool
default_value: "false"
default_value: "true"
description: Prune dangling images on rebuild
deprecated: false
hidden: false

26
go.mod

@ -1,22 +1,22 @@
module github.com/docker/compose/v2
go 1.23.6
go 1.23.8
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e
github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.4.9
github.com/containerd/containerd/v2 v2.0.3
github.com/compose-spec/compose-go/v2 v2.5.0
github.com/containerd/containerd/v2 v2.0.4
github.com/containerd/platforms v1.0.0-rc.1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/distribution/reference v0.6.0
github.com/docker/buildx v0.21.2
github.com/docker/cli v28.0.1+incompatible
github.com/docker/buildx v0.22.0
github.com/docker/cli v28.0.4+incompatible
github.com/docker/cli-docs-tool v0.9.0
github.com/docker/docker v28.0.1+incompatible
github.com/docker/docker v28.0.4+incompatible
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
@ -53,12 +53,12 @@ require (
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.5.0
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
google.golang.org/grpc v1.71.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
tags.cncf.io/container-device-interface v1.0.0
tags.cncf.io/container-device-interface v1.0.1
)
require (
@ -108,7 +108,7 @@ require (
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
@ -193,7 +193,7 @@ require (
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.31.2 // indirect
k8s.io/apimachinery v0.31.2 // indirect
@ -205,3 +205,5 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
replace github.com/compose-spec/compose-go/v2 => github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535

44
go.sum

@ -10,8 +10,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f h1:RTbUqLhPxejgK92ifVdMTIW9H23QLlscy8QXPDTfaL4=
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f/go.mod h1:2UjtD/G/Sy2FxoHpxKnzHTXMpRURecwYal8HgbxcvkY=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@ -83,16 +83,14 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.4.9 h1:2K4TDw+1ba2idiR6empXHKRXvWYpnvAKoNQy93/sSOs=
github.com/compose-spec/compose-go/v2 v2.4.9/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
github.com/containerd/containerd/v2 v2.0.3 h1:zBKgwgZsuu+LPCMzCLgA4sC4MiZzZ59ZT31XkmiISQM=
github.com/containerd/containerd/v2 v2.0.3/go.mod h1:5j9QUUaV/cy9ZeAx4S+8n9ffpf+iYnEj4jiExgcbuLY=
github.com/containerd/containerd/v2 v2.0.4 h1:+r7yJMwhTfMm3CDyiBjMBQO8a9CTBxL2Bg/JtqtIwB8=
github.com/containerd/containerd/v2 v2.0.4/go.mod h1:5j9QUUaV/cy9ZeAx4S+8n9ffpf+iYnEj4jiExgcbuLY=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@ -129,17 +127,17 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/buildx v0.21.2 h1:r09paH8q9nvAX2PR1ntRrc+C6FBH93bvKUsn1WOb/jU=
github.com/docker/buildx v0.21.2/go.mod h1:8V4UMnlKsaGYwz83BygmIbJIFEAYGHT6KAv8akDZmqo=
github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs=
github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/buildx v0.22.0 h1:pGTcGZa+kxpYUlM/6ACsp1hXhkEDulz++RNXPdE8Afk=
github.com/docker/buildx v0.22.0/go.mod h1:ThbnUe4kNiStlq6cLXruElyEdSTdPL3k/QerNUmPvHE=
github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A=
github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@ -169,6 +167,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535 h1:S/P6v3QxsMpkKn+2OSMPNkfSkadSjSHoMGAc/eBZgMU=
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -197,8 +197,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -576,8 +576,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -599,8 +599,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -648,8 +648,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@ -682,5 +682,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
tags.cncf.io/container-device-interface v1.0.0 h1:fbwPQiWZNpXUb9Os6t6JW52rsOppTFUbeJOpNtN1TmI=
tags.cncf.io/container-device-interface v1.0.0/go.mod h1:mmi2aRGmOjK/6NR3TXjLpEIarOJ9qwgZjQ3nTIRwAaA=
tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc=
tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0=

@ -155,6 +155,8 @@ type BuildOptions struct {
Memory int64
// Builder name passed in the command line
Builder string
// Print don't actually run builder but print equivalent build config
Print bool
}
// Apply mutates project according to build options

@ -105,7 +105,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if err != nil {
return nil, err
}
if bake {
if bake || options.Print {
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "bake"))
return s.doBuildBake(ctx, project, serviceToBuild, options)
}
@ -263,7 +263,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
for name, service := range project.Services {
if service.Image == "" && service.Build == nil {
if service.Provider == nil && service.Image == "" && service.Build == nil {
return fmt.Errorf("invalid service %q. Must specify either image or build", name)
}
}

@ -191,7 +191,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
Context: build.Context,
Contexts: additionalContexts(build.AdditionalContexts),
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
DockerfileInline: build.DockerfileInline,
DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"),
Args: args,
Labels: build.Labels,
Tags: append(build.Tags, image),
@ -219,6 +219,10 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
return nil, err
}
if options.Print {
_, err = fmt.Fprintln(s.stdinfo(), string(b))
return nil, err
}
logrus.Debugf("bake build config:\n%s", string(b))
metadata, err := os.CreateTemp(os.TempDir(), "compose")

@ -47,7 +47,7 @@ import (
const (
doubledContainerNameWarning = "WARNING: The %q service is using the custom container name %q. " +
"Docker requires each container to have a unique name. " +
"Remove the custom name to scale the service.\n"
"Remove the custom name to scale the service"
)
// convergence manages service's container lifecycle.
@ -110,6 +110,9 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
}
func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { //nolint:gocyclo
if service.Provider != nil {
return c.service.runPlugin(ctx, project, service, "up")
}
expected, err := getScale(service)
if err != nil {
return err
@ -225,7 +228,9 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
func (c *convergence) stopDependentContainers(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
// Stop dependent containers, so they will be restarted after service is re-created
dependents := project.GetDependentsForService(service)
dependents := project.GetDependentsForService(service, func(dependency types.ServiceDependency) bool {
return dependency.Restart
})
if len(dependents) == 0 {
return nil
}

@ -486,7 +486,7 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool,
if strings.Contains(opt, ":") {
con = strings.SplitN(opt, ":", 2)
} else {
return securityOpts, false, fmt.Errorf("Invalid security-opt: %q", opt)
return securityOpts, false, fmt.Errorf("invalid security-opt: %q", opt)
}
}
if con[0] == "seccomp" && con[1] != "unconfined" && con[1] != "builtin" {
@ -997,10 +997,10 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
}
if definedConfig.Driver != "" {
return nil, errors.New("Docker Compose does not support configs.*.driver")
return nil, errors.New("Docker Compose does not support configs.*.driver") //nolint:staticcheck
}
if definedConfig.TemplateDriver != "" {
return nil, errors.New("Docker Compose does not support configs.*.template_driver")
return nil, errors.New("Docker Compose does not support configs.*.template_driver") //nolint:staticcheck
}
if definedConfig.Environment != "" || definedConfig.Content != "" {
@ -1047,10 +1047,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
}
if definedSecret.Driver != "" {
return nil, errors.New("Docker Compose does not support secrets.*.driver")
return nil, errors.New("Docker Compose does not support secrets.*.driver") //nolint:staticcheck
}
if definedSecret.TemplateDriver != "" {
return nil, errors.New("Docker Compose does not support secrets.*.template_driver")
return nil, errors.New("Docker Compose does not support secrets.*.template_driver") //nolint:staticcheck
}
if definedSecret.Environment != "" {

@ -83,8 +83,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
}
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
serviceContainers := containers.filter(isService(service))
serv := project.Services[service]
if serv.Provider != nil {
return s.runPlugin(ctx, project, serv, "down")
}
serviceContainers := containers.filter(isService(service))
err := s.removeContainers(ctx, serviceContainers, &serv, options.Timeout, options.Volumes)
return err
}, WithRootNodesAndDown(options.Services))

@ -70,7 +70,7 @@ func combinedConfigFiles(containers []container.Summary) (string, error) {
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)
return "", fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
}
for _, f := range strings.Split(files, ",") {
@ -120,7 +120,7 @@ func groupContainerByLabel(containers []container.Summary, labelName string) (ma
for _, c := range containers {
label, ok := c.Labels[labelName]
if !ok {
return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
return nil, nil, fmt.Errorf("no label %q set on container %q of compose project", labelName, c.ID)
}
labelContainers, ok := containersByLabel[label]
if !ok {

@ -104,7 +104,7 @@ func TestCombinedConfigFiles(t *testing.T) {
}{
"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")},
"project3": {ConfigFiles: "", Error: fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
}
for project, containers := range containersByLabel {

179
pkg/compose/plugins.go Normal file

@ -0,0 +1,179 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/socket"
"github.com/docker/compose/v2/pkg/progress"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"golang.org/x/sync/errgroup"
)
type JsonMessage struct {
Type string `json:"type"`
Message string `json:"message"`
}
const (
ErrorType = "error"
InfoType = "info"
SetEnvType = "setenv"
)
func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error {
provider := *service.Provider
plugin, err := s.getPluginBinaryPath(provider.Type)
if err != nil {
return err
}
if err := s.checkPluginEnabledInDD(ctx, plugin); err != nil {
return err
}
cmd := s.setupPluginCommand(ctx, project, provider, plugin.Path, command)
eg := errgroup.Group{}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
eg.Go(cmd.Wait)
decoder := json.NewDecoder(stdout)
defer func() { _ = stdout.Close() }()
variables := types.Mapping{}
pw := progress.ContextWriter(ctx)
pw.Event(progress.CreatingEvent(service.Name))
for {
var msg JsonMessage
err = decoder.Decode(&msg)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
switch msg.Type {
case ErrorType:
pw.Event(progress.ErrorMessageEvent(service.Name, "error"))
return errors.New(msg.Message)
case InfoType:
pw.Event(progress.ErrorMessageEvent(service.Name, msg.Message))
case SetEnvType:
key, val, found := strings.Cut(msg.Message, "=")
if !found {
return fmt.Errorf("invalid response from plugin: %s", msg.Message)
}
variables[key] = val
default:
return fmt.Errorf("invalid response from plugin: %s", msg.Type)
}
}
err = eg.Wait()
if err != nil {
pw.Event(progress.ErrorMessageEvent(service.Name, err.Error()))
return fmt.Errorf("failed to create external service: %s", err.Error())
}
pw.Event(progress.CreatedEvent(service.Name))
prefix := strings.ToUpper(service.Name) + "_"
for name, s := range project.Services {
if _, ok := s.DependsOn[service.Name]; ok {
for key, val := range variables {
s.Environment[prefix+key] = &val
}
project.Services[name] = s
}
}
return nil
}
func (s *composeService) getPluginBinaryPath(providerType string) (*manager.Plugin, error) {
// Only support Docker CLI plugins for first iteration. Could support any binary from PATH
return manager.GetPlugin(providerType, s.dockerCli, &cobra.Command{})
}
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, provider types.ServiceProviderConfig, path, command string) *exec.Cmd {
args := []string{"compose", "--project-name", project.Name, command}
for k, v := range provider.Options {
args = append(args, fmt.Sprintf("--%s=%s", k, v))
}
cmd := exec.CommandContext(ctx, path, args...)
// Remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
// Use docker/cli mechanism to propagate termination signal to child process
server, err := socket.NewPluginServer(nil)
if err == nil {
defer server.Close() //nolint:errcheck
cmd.Cancel = server.Close
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
}
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
carrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, &carrier)
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
return cmd
}
func (s *composeService) checkPluginEnabledInDD(ctx context.Context, plugin *manager.Plugin) error {
if integrationEnabled := s.isDesktopIntegrationActive(); !integrationEnabled {
return fmt.Errorf("you should enable Docker Desktop integration to use %q provider services", plugin.Name)
}
// Until we support more use cases, check explicitly status of model runner
if plugin.Name == "model" {
cmd := exec.CommandContext(ctx, "docker", "model", "status")
_, err := cmd.CombinedOutput()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
return fmt.Errorf("you should enable model runner to use %q provider services: %s", plugin.Name, err.Error())
}
}
} else {
return fmt.Errorf("unsupported provider %q", plugin.Name)
}
return nil
}

@ -335,6 +335,9 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
}
func mustPull(service types.ServiceConfig, images map[string]api.ImageSummary) (bool, error) {
if service.Provider != nil {
return false, nil
}
if service.Image == "" {
return false, nil
}

@ -77,13 +77,19 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
w := progress.ContextWriter(ctx)
return InDependencyOrder(ctx, project, func(c context.Context, service string) error {
config := project.Services[service]
err = s.waitDependencies(ctx, project, service, config.DependsOn, containers, 0)
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
for _, ctr := range containers.filter(isService(service)) {
eg.Go(func() error {
eventName := getContainerProgressName(ctr)
w.Event(progress.RestartingEvent(eventName))
timeout := utils.DurationSecondToInt(options.Timeout)
err := s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
err = s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
if err != nil {
return err
}

@ -151,6 +151,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
v, ok := envResolver(project.Environment)(s)
return v, ok
}).RemoveEmpty()
if service.Environment == nil {
service.Environment = types.MappingWithEquals{}
}
service.Environment.OverrideBy(serviceOverrideEnv)
}
for k, v := range opts.Labels {

@ -45,11 +45,15 @@ func (s *composeService) injectSecrets(ctx context.Context, project *types.Proje
config.Target = "/run/secrets/" + config.Target
}
env, ok := project.Environment[file.Environment]
if !ok {
return fmt.Errorf("environment variable %q required by secret %q is not set", file.Environment, file.Name)
content := file.Content
if content == "" {
env, ok := project.Environment[file.Environment]
if !ok {
return fmt.Errorf("environment variable %q required by secret %q is not set", file.Environment, file.Name)
}
content = env
}
b, err := createTar(env, types.FileReferenceConfig(config))
b, err := createTar(content, types.FileReferenceConfig(config))
if err != nil {
return err
}
@ -106,7 +110,7 @@ func createTar(env string, config types.FileReferenceConfig) (bytes.Buffer, erro
value := []byte(env)
b := bytes.Buffer{}
tarWriter := tar.NewWriter(&b)
mode := types.FileMode(0o440)
mode := types.FileMode(0o444)
if config.Mode != nil {
mode = *config.Mode
}

@ -353,8 +353,11 @@ func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project)
func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool {
for _, volume := range volumes {
if volume.Bind != nil && strings.HasPrefix(watchPath, volume.Source) {
return true
if volume.Bind != nil {
relPath, err := filepath.Rel(volume.Source, watchPath)
if err == nil && !strings.HasPrefix(relPath, "..") {
return true
}
}
}
return false
@ -751,7 +754,7 @@ func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Pr
return time.Now(), err
}
if len(containers) == 0 {
return time.Now(), fmt.Errorf("Could not get created time for service's image")
return time.Now(), fmt.Errorf("could not get created time for service's image")
}
img, err := s.apiClient().ImageInspect(ctx, containers[0].ImageID)

@ -21,6 +21,7 @@ import (
"testing"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
)
func TestRawEnvFile(t *testing.T) {
@ -37,3 +38,11 @@ func TestUnusedMissingEnvFile(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "up", "-d", "serviceA")
}
func TestRunEnvFile(t *testing.T) {
c := NewParallelCLI(t)
defer c.cleanupWithDown(t, "run_dotenv")
res := c.RunDockerComposeCmd(t, "--project-directory", "./fixtures/env_file", "run", "serviceC", "env")
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
}

@ -0,0 +1,10 @@
services:
included:
image: alpine
secrets:
- my-secret
command: cat /run/secrets/my-secret
secrets:
my-secret:
environment: 'MY_SECRET'

@ -1,3 +1,8 @@
include:
- path: child/compose.yaml
env_file:
- secret.env
services:
foo:
image: alpine

@ -0,0 +1 @@
MY_SECRET='this-is-secret'

@ -5,4 +5,10 @@ services:
serviceB:
image: nginx:latest
env_file:
- /doesnotexist/.env
- /doesnotexist/.env
serviceC:
profiles: ["test"]
image: alpine
env_file: test.env

@ -0,0 +1 @@
FOO=BAR

@ -1,13 +1,13 @@
services:
with-restart:
image: alpine
image: nginx:alpine
init: true
command: tail -f /dev/null
depends_on:
nginx: {condition: service_healthy, restart: true}
no-restart:
image: alpine
image: nginx:alpine
init: true
command: tail -f /dev/null
depends_on:
@ -15,6 +15,8 @@ services:
nginx:
image: nginx:alpine
labels:
TEST: ${LABEL:-test}
healthcheck:
test: "echo | nc -w 5 localhost:80"
interval: 2s

@ -37,4 +37,7 @@ services:
RUN mkdir -p /app/config
init: true
command: sleep infinity
volumes:
- ./dat:/app/dat
- ./data-logs:/app/data-logs
develop: *x-dev

@ -0,0 +1 @@
i am a wannabe cat

@ -0,0 +1 @@
[INFO] Server started successfully on port 8080

@ -65,7 +65,7 @@ func TestRestart(t *testing.T) {
}
func TestRestartWithDependencies(t *testing.T) {
c := NewParallelCLI(t, WithEnv(
c := NewCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-restart-deps",
))
baseService := "nginx"
@ -80,9 +80,22 @@ func TestRestartWithDependencies(t *testing.T) {
res := c.RunDockerComposeCmd(t, "restart", baseService)
out := res.Combined()
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", baseService)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Restarting", baseService)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Healthy", baseService)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), out)
assert.Assert(t, !strings.Contains(out, depNoRestart), out)
c = NewParallelCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-restart-deps",
"LABEL=recreate",
))
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/restart-test/compose-depends-on.yaml", "up", "-d")
out = res.Combined()
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Stopped", depWithRestart)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Recreated", baseService)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Healthy", baseService)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), out)
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Running", depNoRestart)), out)
}
func TestRestartWithProfiles(t *testing.T) {

@ -41,3 +41,13 @@ func TestSecretFromEnv(t *testing.T) {
res.Assert(t, icmd.Expected{Out: "-r--r----- 1 1005 1005"})
})
}
func TestSecretFromInclude(t *testing.T) {
c := NewParallelCLI(t)
defer c.cleanupWithDown(t, "env-secret-include")
t.Run("compose run", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/env-secret/compose.yaml", "run", "included")
res.Assert(t, icmd.Expected{Out: "this-is-secret"})
})
}

@ -98,7 +98,7 @@ func TestUpDependenciesNotStopped(t *testing.T) {
if exitErr.ExitCode() == -1 {
t.Fatalf("`compose up` was killed: %v", err)
}
require.EqualValues(t, 130, exitErr.ExitCode())
require.Equal(t, 130, exitErr.ExitCode())
}
RequireServiceState(t, c, "app", "exited")

@ -17,6 +17,8 @@
package remote
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
@ -31,6 +33,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/moby/buildkit/util/gitutil"
"github.com/sirupsen/logrus"
)
const GIT_REMOTE_ENABLED = "COMPOSE_EXPERIMENTAL_GIT_REMOTE"
@ -169,7 +172,8 @@ func (g gitRemoteLoader) checkout(ctx context.Context, path string, ref *gitutil
cmd = exec.CommandContext(ctx, "git", "fetch", "--depth=1", "origin", ref.Commit)
cmd.Env = g.gitCommandEnv()
cmd.Dir = path
err = cmd.Run()
err = g.run(cmd)
if err != nil {
return err
}
@ -183,6 +187,19 @@ func (g gitRemoteLoader) checkout(ctx context.Context, path string, ref *gitutil
return nil
}
func (g gitRemoteLoader) run(cmd *exec.Cmd) error {
if logrus.IsLevelEnabled(logrus.DebugLevel) {
output, err := cmd.CombinedOutput()
scanner := bufio.NewScanner(bytes.NewBuffer(output))
for scanner.Scan() {
line := scanner.Text()
logrus.Debug(line)
}
return err
}
return cmd.Run()
}
func (g gitRemoteLoader) gitCommandEnv() []string {
env := types.NewMapping(os.Environ())
if env["GIT_TERMINAL_PROMPT"] == "" {

@ -149,7 +149,7 @@ func TestGitBranchSwitch(t *testing.T) {
f.assertEvents(path)
// Make sure there are no errors in the out stream
assert.Equal(t, "", f.out.String())
assert.Empty(t, f.out.String())
}
func TestWatchesAreRecursive(t *testing.T) {

@ -274,7 +274,7 @@ func newWatcher(paths []string) (Notify, error) {
fsw, err := fsnotify.NewWatcher()
if err != nil {
if strings.Contains(err.Error(), "too many open files") && runtime.GOOS == "linux" {
return nil, fmt.Errorf("Hit OS limits creating a watcher.\n" +
return nil, fmt.Errorf("hit OS limits creating a watcher.\n" +
"Run 'sysctl fs.inotify.max_user_instances' to check your inotify limits.\n" +
"To raise them, run 'sudo sysctl fs.inotify.max_user_instances=1024'")
}
@ -317,7 +317,7 @@ func greatestExistingAncestors(paths []string) ([]string, error) {
for _, p := range paths {
newP, err := greatestExistingAncestor(p)
if err != nil {
return nil, fmt.Errorf("Finding ancestor of %s: %w", p, err)
return nil, fmt.Errorf("finding ancestor of %s: %w", p, err)
}
result = append(result, newP)
}