diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e645d59b7f..c3637bd466 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -129,7 +129,12 @@ jobs:
name: coverage-data-unit
path: bin/coverage/unit/
if-no-files-found: error
-
+ -
+ name: Unit Test Summary
+ uses: test-summary/action@v2
+ with:
+ paths: bin/coverage/unit/report.xml
+ if: always()
e2e:
runs-on: ubuntu-latest
strategy:
@@ -138,10 +143,20 @@ jobs:
mode:
- plugin
- standalone
+ engine:
+ - 24.0.9
+ - 25.0.3
steps:
-
name: Checkout
uses: actions/checkout@v3
+ - name: Install Docker ${{ matrix.engine }}
+ run: |
+ sudo apt-get install curl
+ curl -fsSL https://get.docker.com -o get-docker.sh
+ sudo sh ./get-docker.sh --version ${{ matrix.engine }}
+ - name: Check Docker Version
+ run: docker --version
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
@@ -152,11 +167,6 @@ jobs:
go-version-file: 'go.mod'
check-latest: true
cache: true
- -
- name: Setup docker CLI
- run: |
- curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz
- sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
-
name: Build
uses: docker/bake-action@v2
@@ -197,7 +207,12 @@ jobs:
rm -f /usr/local/bin/docker-compose
cp bin/build/docker-compose /usr/local/bin
make e2e-compose-standalone
-
+ -
+ name: e2e Test Summary
+ uses: test-summary/action@v2
+ with:
+ paths: /tmp/report/report.xml
+ if: always()
coverage:
runs-on: ubuntu-22.04
needs:
diff --git a/Dockerfile b/Dockerfile
index 51e4a52ffe..a6f74d07d2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-ARG GO_VERSION=1.21.6
+ARG GO_VERSION=1.21.8
ARG XX_VERSION=1.2.1
ARG GOLANGCI_LINT_VERSION=v1.55.2
ARG ADDLICENSE_VERSION=v1.0.0
@@ -106,11 +106,14 @@ RUN --mount=type=bind,target=. \
--mount=type=cache,target=/go/pkg/mod \
rm -rf /tmp/coverage && \
mkdir -p /tmp/coverage && \
- go test -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \
+ rm -rf /tmp/report && \
+ mkdir -p /tmp/report && \
+ go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \
go tool covdata percent -i=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test --link /tmp/coverage /
+COPY --from=test --link /tmp/report /
FROM base AS license-set
ARG LICENSE_FILES
diff --git a/Makefile b/Makefile
index 2bce970904..df30dc8600 100644
--- a/Makefile
+++ b/Makefile
@@ -75,11 +75,11 @@ install: binary
.PHONY: e2e-compose
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
- go test -v $(TEST_FLAGS) -count=1 ./pkg/e2e
+ go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
.PHONY: e2e-compose-standalone
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
- go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
+ go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
.PHONY: build-and-e2e-compose
build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
diff --git a/cmd/compose/attach.go b/cmd/compose/attach.go
index 1899edef11..a4504a0aba 100644
--- a/cmd/compose/attach.go
+++ b/cmd/compose/attach.go
@@ -43,7 +43,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
runCmd := &cobra.Command{
Use: "attach [OPTIONS] SERVICE",
- Short: "Attach local standard input, output, and error streams to a service's running container.",
+ Short: "Attach local standard input, output, and error streams to a service's running container",
Args: cobra.MinimumNArgs(1),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
opts.service = args[0]
@@ -64,7 +64,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error {
- projectName, err := opts.toProjectName(dockerCli)
+ projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/build.go b/cmd/compose/build.go
index 5f855c9220..a204c37a00 100644
--- a/cmd/compose/build.go
+++ b/cmd/compose/build.go
@@ -111,13 +111,13 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
- flags.BoolVar(&opts.push, "push", false, "Push service images.")
+ flags.BoolVar(&opts.push, "push", false, "Push service images")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
- flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
- flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
+ flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
+ flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services")
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
- flags.StringVar(&opts.builder, "builder", "", "Set builder to use.")
- flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively).")
+ flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
+ flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
flags.MarkHidden("parallel") //nolint:errcheck
@@ -136,7 +136,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
- project, err := opts.ToProject(dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
+ project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go
index 83b233f1e2..d9c56b15da 100644
--- a/cmd/compose/completion.go
+++ b/cmd/compose/completion.go
@@ -37,17 +37,18 @@ func noCompletion() validArgsFn {
func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
p.Offline = true
- project, err := p.ToProject(dockerCli, nil)
+ project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
+ var values []string
serviceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
for _, s := range serviceNames {
if toComplete == "" || strings.HasPrefix(s, toComplete) {
- serviceNames = append(serviceNames, s)
+ values = append(values, s)
}
}
- return serviceNames, cobra.ShellCompDirectiveNoFileComp
+ return values, cobra.ShellCompDirectiveNoFileComp
}
}
@@ -72,7 +73,7 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s
func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
p.Offline = true
- project, err := p.ToProject(dockerCli, nil)
+ project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go
index 4921450b1f..8af123e517 100644
--- a/cmd/compose/compose.go
+++ b/cmd/compose/compose.go
@@ -29,24 +29,26 @@ import (
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/dotenv"
+ "github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types"
composegoutils "github.com/compose-spec/compose-go/v2/utils"
"github.com/docker/buildx/util/logutil"
dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command"
+ "github.com/docker/compose/v2/cmd/formatter"
+ "github.com/docker/compose/v2/internal/desktop"
+ "github.com/docker/compose/v2/internal/tracing"
+ "github.com/docker/compose/v2/pkg/api"
+ "github.com/docker/compose/v2/pkg/compose"
+ ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/remote"
+ "github.com/docker/compose/v2/pkg/utils"
buildkit "github.com/moby/buildkit/util/progress/progressui"
"github.com/morikuni/aec"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
-
- "github.com/docker/compose/v2/cmd/formatter"
- "github.com/docker/compose/v2/pkg/api"
- "github.com/docker/compose/v2/pkg/compose"
- ui "github.com/docker/compose/v2/pkg/progress"
- "github.com/docker/compose/v2/pkg/utils"
)
const (
@@ -140,14 +142,15 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
options := []cli.ProjectOptionsFn{
cli.WithResolvedPaths(true),
cli.WithDiscardEnvFile,
- cli.WithContext(ctx),
}
- project, err := o.ToProject(dockerCli, args, options...)
+ project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
if err != nil {
return err
}
+ ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
+
return fn(ctx, project, args)
})
}
@@ -156,7 +159,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
- f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file.")
+ f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file")
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
@@ -164,11 +167,11 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
_ = f.MarkHidden("workdir")
}
-func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string) (*types.Project, string, error) {
+func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
name := o.ProjectName
var project *types.Project
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
- p, err := o.ToProject(dockerCli, services, cli.WithDiscardEnvFile)
+ p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile)
if err != nil {
envProjectName := os.Getenv(ComposeProjectName)
if envProjectName != "" {
@@ -182,7 +185,7 @@ func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string
return project, name, nil
}
-func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
+func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cli) (string, error) {
if o.ProjectName != "" {
return o.ProjectName, nil
}
@@ -192,39 +195,83 @@ func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
return envProjectName, nil
}
- project, err := o.ToProject(dockerCli, nil)
+ project, _, err := o.ToProject(ctx, dockerCli, nil)
if err != nil {
return "", err
}
return project.Name, nil
}
-func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
- if !o.Offline {
- po = o.configureRemoteLoaders(dockerCli, po)
+func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
+ remotes := o.remoteLoaders(dockerCli)
+ for _, r := range remotes {
+ po = append(po, cli.WithResourceLoader(r))
}
options, err := o.toProjectOptions(po...)
if err != nil {
- return nil, compose.WrapComposeError(err)
+ return nil, err
}
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
api.Separator = "_"
}
- project, err := cli.ProjectFromOptions(options)
+ return options.LoadModel(ctx)
+}
+
+func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) {
+ var metrics tracing.Metrics
+
+ remotes := o.remoteLoaders(dockerCli)
+ for _, r := range remotes {
+ po = append(po, cli.WithResourceLoader(r))
+ }
+
+ options, err := o.toProjectOptions(po...)
+ if err != nil {
+ return nil, metrics, compose.WrapComposeError(err)
+ }
+
+ options.WithListeners(func(event string, metadata map[string]any) {
+ switch event {
+ case "extends":
+ metrics.CountExtends++
+ case "include":
+ paths := metadata["path"].(types.StringList)
+ for _, path := range paths {
+ var isRemote bool
+ for _, r := range remotes {
+ if r.Accept(path) {
+ isRemote = true
+ break
+ }
+ }
+ if isRemote {
+ metrics.CountIncludesRemote++
+ } else {
+ metrics.CountIncludesLocal++
+ }
+ }
+ }
+ })
+
+ if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
+ api.Separator = "_"
+ }
+
+ project, err := options.LoadProject(ctx)
if err != nil {
- return nil, compose.WrapComposeError(err)
+ return nil, metrics, compose.WrapComposeError(err)
}
if project.Name == "" {
- return nil, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
+ return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
}
project, err = project.WithServicesEnabled(services...)
if err != nil {
- return nil, err
+ return nil, metrics, err
}
for name, s := range project.Services {
@@ -245,15 +292,16 @@ func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po
project = project.WithoutUnnecessaryResources()
project, err = project.WithSelectedServices(services)
- return project, err
+ return project, metrics, err
}
-func (o *ProjectOptions) configureRemoteLoaders(dockerCli command.Cli, po []cli.ProjectOptionsFn) []cli.ProjectOptionsFn {
+func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader {
+ if o.Offline {
+ return nil
+ }
git := remote.NewGitRemoteLoader(o.Offline)
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
-
- po = append(po, cli.WithResourceLoader(git), cli.WithResourceLoader(oci))
- return po
+ return []loader.ResourceLoader{git, oci}
}
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
@@ -261,10 +309,10 @@ func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
append(po,
cli.WithWorkingDirectory(o.ProjectDir),
cli.WithOsEnv,
- cli.WithEnvFiles(o.EnvFiles...),
- cli.WithDotEnv,
cli.WithConfigFileEnv,
cli.WithDefaultConfigPath,
+ cli.WithEnvFiles(o.EnvFiles...),
+ cli.WithDotEnv,
cli.WithDefaultProfiles(o.Profiles...),
cli.WithName(o.ProjectName))...)
}
@@ -300,7 +348,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
)
c := &cobra.Command{
Short: "Docker Compose",
- Long: "Define and run multi-container applications with Docker.",
+ Long: "Define and run multi-container applications with Docker",
Use: PluginName,
TraverseChildren: true,
// By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
@@ -318,11 +366,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
}
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+
+ // (1) process env vars
err := setEnvWithDotEnv(&opts)
if err != nil {
return err
}
parent := cmd.Root()
+
+ // (2) call parent pre-run
+ // TODO(milas): this seems incorrect, remove or document
if parent != nil {
parentPrerun := parent.PersistentPreRunE
if parentPrerun != nil {
@@ -332,6 +386,11 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
}
}
}
+
+ // (3) set up display/output
+ if verbose {
+ logrus.SetLevel(logrus.TraceLevel)
+ }
if noAnsi {
if ansi != "auto" {
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
@@ -339,14 +398,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
ansi = "never"
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
}
- if verbose {
- logrus.SetLevel(logrus.TraceLevel)
- }
-
if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") {
ansi = v
}
-
formatter.SetANSIMode(dockerCli, ansi)
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
@@ -364,6 +418,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
switch opts.Progress {
case ui.ModeAuto:
ui.Mode = ui.ModeAuto
+ if ansi == "never" {
+ ui.Mode = ui.ModePlain
+ }
case ui.ModeTTY:
if ansi == "never" {
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
@@ -380,6 +437,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
return fmt.Errorf("unsupported --progress value %q", opts.Progress)
}
+ // (4) options validation / normalization
if opts.WorkDir != "" {
if opts.ProjectDir != "" {
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
@@ -416,13 +474,26 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
parallel = i
}
if parallel > 0 {
+ logrus.Debugf("Limiting max concurrency to %d jobs", parallel)
backend.MaxConcurrency(parallel)
}
- ctx, err := backend.DryRunMode(cmd.Context(), dryRun)
+
+ // (5) dry run detection
+ ctx, err = backend.DryRunMode(ctx, dryRun)
if err != nil {
return err
}
cmd.SetContext(ctx)
+
+ // (6) Desktop integration
+ if db, ok := backend.(desktop.IntegrationService); ok {
+ if err := db.MaybeEnableDesktopIntegration(ctx); err != nil {
+ // not fatal, Compose will still work but behave as though
+ // it's not running as part of Docker Desktop
+ logrus.Debugf("failed to enable Docker Desktop integration: %v", err)
+ }
+ }
+
return nil
},
}
@@ -436,7 +507,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
psCommand(&opts, dockerCli, backend),
listCommand(dockerCli, backend),
logsCommand(&opts, dockerCli, backend),
- configCommand(&opts, dockerCli, backend),
+ configCommand(&opts, dockerCli),
killCommand(&opts, dockerCli, backend),
runCommand(&opts, dockerCli, backend),
removeCommand(&opts, dockerCli, backend),
diff --git a/cmd/compose/config.go b/cmd/compose/config.go
index cf26832cfd..b06e620b11 100644
--- a/cmd/compose/config.go
+++ b/cmd/compose/config.go
@@ -19,6 +19,7 @@ package compose
import (
"bytes"
"context"
+ "encoding/json"
"fmt"
"os"
"sort"
@@ -28,6 +29,7 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
+ "gopkg.in/yaml.v3"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
@@ -51,18 +53,28 @@ type configOptions struct {
}
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
- po = append(po,
+ po = append(po, o.ToProjectOptions()...)
+ project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
+ return project, err
+}
+
+func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
+ po = append(po, o.ToProjectOptions()...)
+ return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...)
+}
+
+func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn {
+ return []cli.ProjectOptionsFn{
cli.WithInterpolation(!o.noInterpolate),
cli.WithResolvedPaths(!o.noResolvePath),
cli.WithNormalization(!o.noNormalize),
cli.WithConsistency(!o.noConsistency),
cli.WithDefaultProfiles(o.Profiles...),
cli.WithDiscardEnvFile,
- cli.WithContext(ctx))
- return o.ProjectOptions.ToProject(dockerCli, services, po...)
+ }
}
-func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
+func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
opts := configOptions{
ProjectOptions: p,
}
@@ -100,43 +112,58 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
return runConfigImages(ctx, dockerCli, opts, args)
}
- return runConfig(ctx, dockerCli, backend, opts, args)
+ return runConfig(ctx, dockerCli, opts, args)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
- 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.noResolvePath, "no-path-resolution", false, "Don't resolve file paths.")
+ 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.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.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.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)")
return cmd
}
-func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error {
+func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
var content []byte
- project, err := opts.ToProject(ctx, dockerCli, services)
- if err != nil {
- return err
- }
+ if opts.noInterpolate {
+ // we can't use ToProject, so the model we render here is only partially resolved
+ model, err := opts.ToModel(ctx, dockerCli, services)
+ if err != nil {
+ return err
+ }
- content, err = backend.Config(ctx, project, api.ConfigOptions{
- Format: opts.Format,
- Output: opts.Output,
- ResolveImageDigests: opts.resolveImageDigests,
- })
- if err != nil {
- return err
+ if opts.resolveImageDigests {
+ err = resolveImageDigests(ctx, dockerCli, model)
+ if err != nil {
+ return err
+ }
+ }
+
+ content, err = formatModel(model, opts.Format)
+ if err != nil {
+ return err
+ }
+ } else {
+ project, err := opts.ToProject(ctx, dockerCli, services)
+ if err != nil {
+ return err
+ }
+ content, err = project.MarshalYAML()
+ if err != nil {
+ return err
+ }
}
if !opts.noInterpolate {
@@ -150,10 +177,59 @@ func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service,
if opts.Output != "" && len(content) > 0 {
return os.WriteFile(opts.Output, content, 0o666)
}
- _, err = fmt.Fprint(dockerCli.Out(), string(content))
+ _, err := fmt.Fprint(dockerCli.Out(), string(content))
return err
}
+func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[string]any) (err error) {
+ // create a pseudo-project so we can rely on WithImagesResolved to resolve images
+ p := &types.Project{
+ Services: types.Services{},
+ }
+ services := model["services"].(map[string]any)
+ for name, s := range services {
+ service := s.(map[string]any)
+ if image, ok := service["image"]; ok {
+ p.Services[name] = types.ServiceConfig{
+ Image: image.(string),
+ }
+ }
+ }
+
+ p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
+ if err != nil {
+ return err
+ }
+
+ // Collect image resolved with digest and update model accordingly
+ for name, s := range services {
+ service := s.(map[string]any)
+ config := p.Services[name]
+ if config.Image != "" {
+ service["image"] = config.Image
+ }
+ services[name] = service
+ }
+ model["services"] = services
+ return nil
+}
+
+func formatModel(model map[string]any, format string) (content []byte, err error) {
+ switch format {
+ case "json":
+ content, err = json.MarshalIndent(model, "", " ")
+ case "yaml":
+ buf := bytes.NewBuffer([]byte{})
+ encoder := yaml.NewEncoder(buf)
+ encoder.SetIndent(2)
+ err = encoder.Encode(model)
+ content = buf.Bytes()
+ default:
+ return nil, fmt.Errorf("unsupported format %q", format)
+ }
+ return
+}
+
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go
index 4c346b2a7b..9cd07e5f77 100644
--- a/cmd/compose/cp.go
+++ b/cmd/compose/cp.go
@@ -65,10 +65,10 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
flags := copyCmd.Flags()
- flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
- flags.BoolVar(&opts.all, "all", false, "copy to all the containers of the service.")
- flags.MarkHidden("all") //nolint:errcheck
- flags.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
+ flags.IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
+ flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service")
+ flags.MarkHidden("all") //nolint:errcheck
+ flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied") //nolint:errcheck
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
@@ -76,7 +76,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error {
- name, err := opts.toProjectName(dockerCli)
+ name, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/create.go b/cmd/compose/create.go
index a7731bae59..bf21ee7afc 100644
--- a/cmd/compose/create.go
+++ b/cmd/compose/create.go
@@ -55,7 +55,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
cmd := &cobra.Command{
Use: "create [OPTIONS] [SERVICE...]",
- Short: "Creates containers for a service.",
+ Short: "Creates containers for a service",
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
opts.pullChanged = cmd.Flags().Changed("pull")
if opts.Build && opts.noBuild {
@@ -72,12 +72,13 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
- flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
- flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
+ flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers")
+ flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy")
flags.StringVar(&opts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never"|"build")`)
- flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
+ flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information")
+ flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed")
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
- flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
+ flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
return cmd
}
@@ -105,7 +106,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
RecreateDependencies: createOpts.dependenciesRecreateStrategy(),
Inherit: !createOpts.noInherit,
Timeout: createOpts.GetTimeout(),
- QuietPull: false,
+ QuietPull: createOpts.quietPull,
})
}
diff --git a/cmd/compose/down.go b/cmd/compose/down.go
index 97215a7857..d3080ed2df 100644
--- a/cmd/compose/down.go
+++ b/cmd/compose/down.go
@@ -63,9 +63,9 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
flags := downCmd.Flags()
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
- flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
+ flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file")
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
- flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers.`)
+ flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers`)
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
if name == "volume" {
@@ -78,7 +78,7 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/events.go b/cmd/compose/events.go
index 10ef5b4427..cade77a7fd 100644
--- a/cmd/compose/events.go
+++ b/cmd/compose/events.go
@@ -40,7 +40,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
cmd := &cobra.Command{
Use: "events [OPTIONS] [SERVICE...]",
- Short: "Receive real time events from containers.",
+ Short: "Receive real time events from containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runEvents(ctx, dockerCli, backend, opts, args)
}),
@@ -52,7 +52,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}
func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error {
- name, err := opts.toProjectName(dockerCli)
+ name, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go
index 12ebc70050..aa6774d5d5 100644
--- a/cmd/compose/exec.go
+++ b/cmd/compose/exec.go
@@ -51,7 +51,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
runCmd := &cobra.Command{
Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
- Short: "Execute a command in a running container.",
+ Short: "Execute a command in a running container",
Args: cobra.MinimumNArgs(2),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
opts.service = args[0]
@@ -64,17 +64,17 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
- runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
+ runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background")
runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
- runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
- runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
- runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
+ runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
+ runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process")
+ runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user")
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
- runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
+ runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command")
- runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
+ runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
- runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
+ runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY")
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
runCmd.Flags().SetInterspersed(false)
@@ -82,7 +82,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error {
- projectName, err := opts.toProjectName(dockerCli)
+ projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/images.go b/cmd/compose/images.go
index d10700767a..8c68a34af3 100644
--- a/cmd/compose/images.go
+++ b/cmd/compose/images.go
@@ -51,13 +51,13 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
- imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].")
+ imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
return imgCmd
}
func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error {
- projectName, err := opts.toProjectName(dockerCli)
+ projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go
index d38201cbc9..c0faa75c60 100644
--- a/cmd/compose/kill.go
+++ b/cmd/compose/kill.go
@@ -39,7 +39,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
cmd := &cobra.Command{
Use: "kill [OPTIONS] [SERVICE...]",
- Short: "Force stop service containers.",
+ Short: "Force stop service containers",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runKill(ctx, dockerCli, backend, opts, args)
}),
@@ -48,14 +48,14 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
flags := cmd.Flags()
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
- flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
- flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
+ flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file")
+ flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container")
return cmd
}
func runKill(ctx context.Context, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/list.go b/cmd/compose/list.go
index e54bddb88e..bb1faeb12c 100644
--- a/cmd/compose/list.go
+++ b/cmd/compose/list.go
@@ -49,9 +49,9 @@ func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion(),
}
- lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json].")
- lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.")
- lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.")
+ lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json]")
+ lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs")
+ lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided")
lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects")
return lsCmd
diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go
index 2df7bb59f1..7661b02aed 100644
--- a/cmd/compose/logs.go
+++ b/cmd/compose/logs.go
@@ -59,22 +59,32 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := logsCmd.Flags()
- flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
+ flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
- flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.")
- flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
- flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps.")
- flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container.")
+ flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output")
+ flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs")
+ flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
+ flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container")
return logsCmd
}
func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
+
+ // exclude services configured to ignore output (attach: false), until explicitly selected
+ if project != nil && len(services) == 0 {
+ for n, service := range project.Services {
+ if service.Attach == nil || *service.Attach {
+ services = append(services, n)
+ }
+ }
+ }
+
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
return backend.Logs(ctx, name, consumer, api.LogOptions{
Project: project,
diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go
index acfc0dec51..6f34577192 100644
--- a/cmd/compose/pause.go
+++ b/cmd/compose/pause.go
@@ -45,7 +45,7 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
@@ -76,7 +76,7 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
}
func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/port.go b/cmd/compose/port.go
index 0baa875c3c..59ea8ef1ce 100644
--- a/cmd/compose/port.go
+++ b/cmd/compose/port.go
@@ -41,7 +41,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
cmd := &cobra.Command{
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
- Short: "Print the public port for a port binding.",
+ Short: "Print the public port for a port binding",
Args: cobra.MinimumNArgs(2),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
port, err := strconv.ParseUint(args[1], 10, 16)
@@ -58,12 +58,12 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
- cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
+ cmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
return cmd
}
func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error {
- projectName, err := opts.toProjectName(dockerCli)
+ projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go
index 8b3f3d34d6..b1338837a7 100644
--- a/cmd/compose/ps.go
+++ b/cmd/compose/ps.go
@@ -81,7 +81,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
}
flags := psCmd.Flags()
flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
- flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
+ flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status)")
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
flags.BoolVar(&opts.Services, "services", false, "Display services")
@@ -92,7 +92,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
}
func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
@@ -113,7 +113,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
containers, err := backend.Ps(ctx, name, api.PsOptions{
Project: project,
- All: opts.All,
+ All: opts.All || len(opts.Status) != 0,
Services: services,
})
if err != nil {
diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go
index 2ef14aabf0..42b0140b58 100644
--- a/cmd/compose/publish.go
+++ b/cmd/compose/publish.go
@@ -44,13 +44,13 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
Args: cobra.ExactArgs(1),
}
flags := cmd.Flags()
- flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
+ flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)")
return cmd
}
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
- project, err := opts.ToProject(dockerCli, nil)
+ project, _, err := opts.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}
diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go
index d6ce561be7..f003fc19e9 100644
--- a/cmd/compose/pull.go
+++ b/cmd/compose/pull.go
@@ -60,15 +60,15 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
- flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information.")
- cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies.")
- cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel.")
+ flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
+ cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies")
+ cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel")
flags.MarkHidden("parallel") //nolint:errcheck
- cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.")
+ cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling")
flags.MarkHidden("no-parallel") //nolint:errcheck
- cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures.")
- cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built.")
- cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always").`)
+ cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures")
+ cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built")
+ cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always")`)
return cmd
}
@@ -94,7 +94,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types
}
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
- project, err := opts.ToProject(dockerCli, services)
+ project, _, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}
diff --git a/cmd/compose/push.go b/cmd/compose/push.go
index ccc0e59917..177f9f2ec7 100644
--- a/cmd/compose/push.go
+++ b/cmd/compose/push.go
@@ -54,7 +54,7 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error {
- project, err := opts.ToProject(dockerCli, services)
+ project, _, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}
diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go
index 46ac7b13fe..adcd3663c3 100644
--- a/cmd/compose/remove.go
+++ b/cmd/compose/remove.go
@@ -60,7 +60,7 @@ Any data which is not in a volume will be lost.`,
}
func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go
index 9fe65f4615..718e15196f 100644
--- a/cmd/compose/restart.go
+++ b/cmd/compose/restart.go
@@ -50,13 +50,13 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
}
flags := restartCmd.Flags()
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
- flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services.")
+ flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services")
return restartCmd
}
func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli)
+ project, name, err := opts.projectOrName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/run.go b/cmd/compose/run.go
index c2649d5f12..b4a5da365f 100644
--- a/cmd/compose/run.go
+++ b/cmd/compose/run.go
@@ -129,7 +129,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
}
cmd := &cobra.Command{
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
- Short: "Run a one-off command on a service.",
+ Short: "Run a one-off command on a service",
Args: cobra.MinimumNArgs(1),
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
options.Service = args[0]
@@ -156,7 +156,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
- project, err := p.ToProject(dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
+ project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
if err != nil {
return err
}
@@ -175,24 +175,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
- flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
+ flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected)")
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities")
flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities")
- flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.")
- flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.")
- flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
- 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.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information.")
- flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
- flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
-
- cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
- cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
+ flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services")
+ flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume")
+ flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host")
+ 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.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
+ flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
+ flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
+
+ cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
+ cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY")
cmd.Flags().MarkHidden("tty") //nolint:errcheck
flags.SetNormalizeFunc(normalizeRunFlags)
@@ -231,7 +231,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
}
buildForDeps = &bo
}
- return startDependencies(ctx, backend, *project, buildForDeps, options.Service, options.ignoreOrphans)
+ return startDependencies(ctx, backend, *project, buildForDeps, options)
}, dockerCli.Err())
if err != nil {
return err
@@ -298,11 +298,11 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
return err
}
-func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error {
+func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error {
dependencies := types.Services{}
var requestedService types.ServiceConfig
for name, service := range project.Services {
- if name != requestedServiceName {
+ if name != options.Service {
dependencies[name] = service
} else {
requestedService = service
@@ -310,10 +310,11 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
}
project.Services = dependencies
- project.DisabledServices[requestedServiceName] = requestedService
+ project.DisabledServices[options.Service] = requestedService
err := backend.Create(ctx, &project, api.CreateOptions{
Build: buildOpts,
- IgnoreOrphans: ignoreOrphans,
+ IgnoreOrphans: options.ignoreOrphans,
+ QuietPull: options.quietPull,
})
if err != nil {
return err
diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go
index 2ab4351277..22e8e71dd4 100644
--- a/cmd/compose/scale.go
+++ b/cmd/compose/scale.go
@@ -54,14 +54,14 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := scaleCmd.Flags()
- flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
+ flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")
return scaleCmd
}
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
services := maps.Keys(serviceReplicaTuples)
- project, err := opts.ToProject(dockerCli, services)
+ project, _, err := opts.ToProject(ctx, dockerCli, services)
if err != nil {
return err
}
diff --git a/cmd/compose/start.go b/cmd/compose/start.go
index 682a789065..6bde4b104b 100644
--- a/cmd/compose/start.go
+++ b/cmd/compose/start.go
@@ -44,7 +44,7 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/stats.go b/cmd/compose/stats.go
index c6c2f7fed0..2e503fe5db 100644
--- a/cmd/compose/stats.go
+++ b/cmd/compose/stats.go
@@ -63,7 +63,7 @@ Refer to https://docs.docker.com/go/formatting/ for more information about forma
}
func runStats(ctx context.Context, dockerCli command.Cli, opts statsOptions, service []string) error {
- name, err := opts.ProjectOptions.toProjectName(dockerCli)
+ name, err := opts.ProjectOptions.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go
index 2818ef4f33..d06cf6f229 100644
--- a/cmd/compose/stop.go
+++ b/cmd/compose/stop.go
@@ -54,7 +54,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error {
- project, name, err := opts.projectOrName(dockerCli, services...)
+ project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
diff --git a/cmd/compose/top.go b/cmd/compose/top.go
index fb7dd30d41..9d84c57a95 100644
--- a/cmd/compose/top.go
+++ b/cmd/compose/top.go
@@ -50,7 +50,7 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
}
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
- projectName, err := opts.toProjectName(dockerCli)
+ projectName, err := opts.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
diff --git a/cmd/compose/up.go b/cmd/compose/up.go
index c4919ca80e..084e010fad 100644
--- a/cmd/compose/up.go
+++ b/cmd/compose/up.go
@@ -20,14 +20,14 @@ import (
"context"
"errors"
"fmt"
+ "os"
"strings"
"time"
- xprogress "github.com/moby/buildkit/util/progress/progressui"
-
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/formatter"
+ xprogress "github.com/moby/buildkit/util/progress/progressui"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/pkg/api"
@@ -54,6 +54,7 @@ type upOptions struct {
timestamp bool
wait bool
waitTimeout int
+ watch bool
}
func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
@@ -101,29 +102,31 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
}
flags := upCmd.Flags()
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
- flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
- flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
+ flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
+ flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
- flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
+ removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
+ flags.BoolVar(&create.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file")
flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
- flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
- flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
- flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
+ flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output")
+ flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs")
+ flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed")
flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
- flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
+ flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them")
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
- flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
- flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps.")
- flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
+ flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running")
+ flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps")
+ flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services")
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
- flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
- flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
+ flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers")
+ flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information")
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
- flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services.")
- flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services.")
+ flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services")
+ flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services")
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
- flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy.")
+ flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy")
+ flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
return upCmd
}
@@ -255,6 +258,7 @@ func runUp(
CascadeStop: upOptions.cascadeStop,
Wait: upOptions.wait,
WaitTimeout: timeout,
+ Watch: upOptions.watch,
Services: services,
},
})
diff --git a/cmd/compose/version.go b/cmd/compose/version.go
index 8ca1ed57b0..e044043478 100644
--- a/cmd/compose/version.go
+++ b/cmd/compose/version.go
@@ -52,7 +52,7 @@ func versionCommand(dockerCli command.Cli) *cobra.Command {
// define flags for backward compatibility with com.docker.cli
flags := cmd.Flags()
flags.StringVarP(&opts.format, "format", "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
- flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number.")
+ flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number")
return cmd
}
diff --git a/cmd/compose/viz.go b/cmd/compose/viz.go
index 2b4d650739..d97504e382 100644
--- a/cmd/compose/viz.go
+++ b/cmd/compose/viz.go
@@ -65,7 +65,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error {
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
- project, err := opts.ToProject(dockerCli, nil)
+ project, _, err := opts.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}
diff --git a/cmd/compose/wait.go b/cmd/compose/wait.go
index e95818b959..88c9391400 100644
--- a/cmd/compose/wait.go
+++ b/cmd/compose/wait.go
@@ -61,7 +61,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) {
- _, name, err := opts.projectOrName(dockerCli)
+ _, name, err := opts.projectOrName(ctx, dockerCli)
if err != nil {
return 0, err
}
diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go
index 24d704570e..880711a205 100644
--- a/cmd/compose/watch.go
+++ b/cmd/compose/watch.go
@@ -21,6 +21,7 @@ import (
"fmt"
"github.com/compose-spec/compose-go/v2/types"
+ "github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/internal/locker"
@@ -31,8 +32,7 @@ import (
type watchOptions struct {
*ProjectOptions
- quiet bool
- noUp bool
+ noUp bool
}
func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
@@ -57,13 +57,13 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
- cmd.Flags().BoolVar(&watchOpts.quiet, "quiet", false, "hide build output")
+ cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
return cmd
}
func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
- project, err := watchOpts.ToProject(dockerCli, nil)
+ project, _, err := watchOpts.ToProject(ctx, dockerCli, nil)
if err != nil {
return err
}
@@ -101,7 +101,7 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
Recreate: api.RecreateDiverged,
RecreateDependencies: api.RecreateNever,
Inherit: true,
- QuietPull: watchOpts.quiet,
+ QuietPull: buildOpts.quiet,
},
Start: api.StartOptions{
Project: project,
@@ -114,7 +114,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
return err
}
}
+
+ consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
return backend.Watch(ctx, project, services, api.WatchOptions{
- Build: build,
+ Build: &build,
+ LogTo: consumer,
})
}
diff --git a/cmd/formatter/logs.go b/cmd/formatter/logs.go
index c6b8baa320..465aa229a9 100644
--- a/cmd/formatter/logs.go
+++ b/cmd/formatter/logs.go
@@ -62,7 +62,11 @@ func (l *logConsumer) Register(name string) {
func (l *logConsumer) register(name string) *presenter {
cf := monochrome
if l.color {
- cf = nextColor()
+ if name == api.WatchLogger {
+ cf = makeColorFunc("92")
+ } else {
+ cf = nextColor()
+ }
}
p := &presenter{
colors: cf,
@@ -138,5 +142,9 @@ type presenter struct {
}
func (p *presenter) setPrefix(width int) {
+ if p.name == api.WatchLogger {
+ p.prefix = p.colors(strings.Repeat(" ", width) + " ⦿ ")
+ return
+ }
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
}
diff --git a/cmd/main.go b/cmd/main.go
index d8c011e4d0..038d07cf56 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -25,6 +25,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/cmdtrace"
"github.com/docker/docker/client"
+ "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/docker/compose/v2/cmd/compatibility"
@@ -37,7 +38,7 @@ func pluginMain() {
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
backend := compose.NewComposeService(dockerCli)
cmd := commands.RootCommand(dockerCli, backend)
- originalPreRun := cmd.PersistentPreRunE
+ originalPreRunE := cmd.PersistentPreRunE
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// initialize the dockerCli instance
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
@@ -46,12 +47,12 @@ func pluginMain() {
// compose-specific initialization
dockerCliPostInitialize(dockerCli)
- // TODO(milas): add an env var to enable logging from the
- // OTel components for debugging purposes
- _ = cmdtrace.Setup(cmd, dockerCli, os.Args[1:])
+ if err := cmdtrace.Setup(cmd, dockerCli, os.Args[1:]); err != nil {
+ logrus.Debugf("failed to enable tracing: %v", err)
+ }
- if originalPreRun != nil {
- return originalPreRun(cmd, args)
+ if originalPreRunE != nil {
+ return originalPreRunE(cmd, args)
}
return nil
}
diff --git a/docs/reference/compose.md b/docs/reference/compose.md
index 3728a76d48..ce6c214d0d 100644
--- a/docs/reference/compose.md
+++ b/docs/reference/compose.md
@@ -1,42 +1,42 @@
# docker compose
-Define and run multi-container applications with Docker.
+Define and run multi-container applications with Docker
### Subcommands
-| Name | Description |
-|:--------------------------------|:-----------------------------------------------------------------------------------------|
-| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container. |
-| [`build`](compose_build.md) | Build or rebuild services |
-| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
-| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
-| [`create`](compose_create.md) | Creates containers for a service. |
-| [`down`](compose_down.md) | Stop and remove containers, networks |
-| [`events`](compose_events.md) | Receive real time events from containers. |
-| [`exec`](compose_exec.md) | Execute a command in a running container. |
-| [`images`](compose_images.md) | List images used by the created containers |
-| [`kill`](compose_kill.md) | Force stop service containers. |
-| [`logs`](compose_logs.md) | View output from containers |
-| [`ls`](compose_ls.md) | List running compose projects |
-| [`pause`](compose_pause.md) | Pause services |
-| [`port`](compose_port.md) | Print the public port for a port binding. |
-| [`ps`](compose_ps.md) | List containers |
-| [`pull`](compose_pull.md) | Pull service images |
-| [`push`](compose_push.md) | Push service images |
-| [`restart`](compose_restart.md) | Restart service containers |
-| [`rm`](compose_rm.md) | Removes stopped service containers |
-| [`run`](compose_run.md) | Run a one-off command on a service. |
-| [`scale`](compose_scale.md) | Scale services |
-| [`start`](compose_start.md) | Start services |
-| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics |
-| [`stop`](compose_stop.md) | Stop services |
-| [`top`](compose_top.md) | Display the running processes |
-| [`unpause`](compose_unpause.md) | Unpause services |
-| [`up`](compose_up.md) | Create and start containers |
-| [`version`](compose_version.md) | Show the Docker Compose version information |
-| [`wait`](compose_wait.md) | Block until the first service container stops |
-| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
+| Name | Description |
+|:--------------------------------|:----------------------------------------------------------------------------------------|
+| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
+| [`build`](compose_build.md) | Build or rebuild services |
+| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
+| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
+| [`create`](compose_create.md) | Creates containers for a service |
+| [`down`](compose_down.md) | Stop and remove containers, networks |
+| [`events`](compose_events.md) | Receive real time events from containers |
+| [`exec`](compose_exec.md) | Execute a command in a running container |
+| [`images`](compose_images.md) | List images used by the created containers |
+| [`kill`](compose_kill.md) | Force stop service containers |
+| [`logs`](compose_logs.md) | View output from containers |
+| [`ls`](compose_ls.md) | List running compose projects |
+| [`pause`](compose_pause.md) | Pause services |
+| [`port`](compose_port.md) | Print the public port for a port binding |
+| [`ps`](compose_ps.md) | List containers |
+| [`pull`](compose_pull.md) | Pull service images |
+| [`push`](compose_push.md) | Push service images |
+| [`restart`](compose_restart.md) | Restart service containers |
+| [`rm`](compose_rm.md) | Removes stopped service containers |
+| [`run`](compose_run.md) | Run a one-off command on a service |
+| [`scale`](compose_scale.md) | Scale services |
+| [`start`](compose_start.md) | Start services |
+| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics |
+| [`stop`](compose_stop.md) | Stop services |
+| [`top`](compose_top.md) | Display the running processes |
+| [`unpause`](compose_unpause.md) | Unpause services |
+| [`up`](compose_up.md) | Create and start containers |
+| [`version`](compose_version.md) | Show the Docker Compose version information |
+| [`wait`](compose_wait.md) | Block until the first service container stops |
+| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
### Options
@@ -46,7 +46,7 @@ Define and run multi-container applications with Docker.
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
| `--compatibility` | | | Run compose in backward compatibility mode |
| `--dry-run` | | | Execute command in dry run mode |
-| `--env-file` | `stringArray` | | Specify an alternate environment file. |
+| `--env-file` | `stringArray` | | Specify an alternate environment file |
| `-f`, `--file` | `stringArray` | | Compose configuration files |
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
| `--profile` | `stringArray` | | Specify a profile to enable |
diff --git a/docs/reference/compose_alpha_dry-run.md b/docs/reference/compose_alpha_dry-run.md
index 9e8350e2a0..7c68d94d66 100644
--- a/docs/reference/compose_alpha_dry-run.md
+++ b/docs/reference/compose_alpha_dry-run.md
@@ -1,7 +1,7 @@
# docker compose alpha dry-run
-Dry run command allows you to test a command without applying changes.
+Dry run command allows you to test a command without applying changes
diff --git a/docs/reference/compose_alpha_publish.md b/docs/reference/compose_alpha_publish.md
index 8424d7fbce..02516d968b 100644
--- a/docs/reference/compose_alpha_publish.md
+++ b/docs/reference/compose_alpha_publish.md
@@ -9,7 +9,7 @@ Publish compose application
|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) |
-| `--resolve-image-digests` | | | Pin image tags to digests. |
+| `--resolve-image-digests` | | | Pin image tags to digests |
diff --git a/docs/reference/compose_alpha_scale.md b/docs/reference/compose_alpha_scale.md
index 15536b359c..f783f3335c 100644
--- a/docs/reference/compose_alpha_scale.md
+++ b/docs/reference/compose_alpha_scale.md
@@ -1,14 +1,14 @@
# docker compose alpha scale
-Scale services.
+Scale services
### Options
| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
-| `--no-deps` | | | Don't start linked services |
+| `--no-deps` | | | Don't start linked services |
diff --git a/docs/reference/compose_attach.md b/docs/reference/compose_attach.md
index 405fed98ad..5a03388192 100644
--- a/docs/reference/compose_attach.md
+++ b/docs/reference/compose_attach.md
@@ -1,7 +1,7 @@
# docker compose attach
-Attach local standard input, output, and error streams to a service's running container.
+Attach local standard input, output, and error streams to a service's running container
### Options
@@ -15,4 +15,3 @@ Attach local standard input, output, and error streams to a service's running co
-
diff --git a/docs/reference/compose_build.md b/docs/reference/compose_build.md
index 46bcedbbe7..a0ceb050c0 100644
--- a/docs/reference/compose_build.md
+++ b/docs/reference/compose_build.md
@@ -7,16 +7,16 @@ Build or rebuild services
| Name | Type | Default | Description |
|:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
-| `--build-arg` | `stringArray` | | Set build-time variables for services. |
-| `--builder` | `string` | | Set builder to use. |
+| `--build-arg` | `stringArray` | | Set build-time variables for services |
+| `--builder` | `string` | | Set builder to use |
| `--dry-run` | | | Execute command in dry run mode |
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
| `--no-cache` | | | Do not use cache when building the image |
-| `--pull` | | | Always attempt to pull a newer version of the image. |
-| `--push` | | | Push service images. |
+| `--pull` | | | Always attempt to pull a newer version of the image |
+| `--push` | | | Push service images |
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
-| `--with-dependencies` | | | Also build dependencies (transitively). |
+| `--with-dependencies` | | | Also build dependencies (transitively) |
diff --git a/docs/reference/compose_config.md b/docs/reference/compose_config.md
index 2639cd735e..fd213b4c38 100644
--- a/docs/reference/compose_config.md
+++ b/docs/reference/compose_config.md
@@ -13,18 +13,18 @@ Parse, resolve and render compose file in canonical format
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
-| `--hash` | `string` | | Print the service config hash, one per line. |
-| `--images` | | | Print the image names, one per line. |
+| `--hash` | `string` | | Print the service config hash, one per line |
+| `--images` | | | Print the image names, one per line |
| `--no-consistency` | | | Don't check model consistency - warning: may produce invalid Compose output |
-| `--no-interpolate` | | | Don't interpolate environment variables. |
-| `--no-normalize` | | | Don't normalize compose model. |
-| `--no-path-resolution` | | | Don't resolve file paths. |
+| `--no-interpolate` | | | Don't interpolate environment variables |
+| `--no-normalize` | | | Don't normalize compose model |
+| `--no-path-resolution` | | | Don't resolve file paths |
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
-| `--profiles` | | | Print the profile names, one per line. |
-| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
-| `--resolve-image-digests` | | | Pin image tags to digests. |
-| `--services` | | | Print the service names, one per line. |
-| `--volumes` | | | Print the volume names, one per line. |
+| `--profiles` | | | Print the profile names, one per line |
+| `-q`, `--quiet` | | | Only validate the configuration, don't print anything |
+| `--resolve-image-digests` | | | Pin image tags to digests |
+| `--services` | | | Print the service names, one per line |
+| `--volumes` | | | Print the volume names, one per line |
diff --git a/docs/reference/compose_cp.md b/docs/reference/compose_cp.md
index 9be79443af..a60388e306 100644
--- a/docs/reference/compose_cp.md
+++ b/docs/reference/compose_cp.md
@@ -10,7 +10,7 @@ Copy files/folders between a service container and the local filesystem
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
| `--dry-run` | | | Execute command in dry run mode |
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
-| `--index` | `int` | `0` | index of the container if service has multiple replicas |
+| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
diff --git a/docs/reference/compose_create.md b/docs/reference/compose_create.md
index 149ed6fe33..06293625a1 100644
--- a/docs/reference/compose_create.md
+++ b/docs/reference/compose_create.md
@@ -1,19 +1,20 @@
# docker compose create
-Creates containers for a service.
+Creates containers for a service
### Options
| Name | Type | Default | Description |
|:-------------------|:--------------|:---------|:----------------------------------------------------------------------------------------------|
-| `--build` | | | Build images before starting containers. |
+| `--build` | | | Build images before starting containers |
| `--dry-run` | | | Execute command in dry run mode |
-| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
-| `--no-build` | | | Don't build an image, even if it's policy. |
+| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
+| `--no-build` | | | Don't build an image, even if it's policy |
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") |
-| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
+| `--quiet-pull` | | | Pull without printing progress information |
+| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
diff --git a/docs/reference/compose_down.md b/docs/reference/compose_down.md
index 4012b70ca4..2d7cf57271 100644
--- a/docs/reference/compose_down.md
+++ b/docs/reference/compose_down.md
@@ -5,13 +5,13 @@ Stop and remove containers, networks
### Options
-| Name | Type | Default | Description |
-|:-------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------|
-| `--dry-run` | | | Execute command in dry run mode |
-| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
-| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
-| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
-| `-v`, `--volumes` | | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers. |
+| Name | Type | Default | Description |
+|:-------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------------|
+| `--dry-run` | | | Execute command in dry run mode |
+| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
+| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
+| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
+| `-v`, `--volumes` | | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers |
diff --git a/docs/reference/compose_events.md b/docs/reference/compose_events.md
index 86c492e843..0a97a46bc7 100644
--- a/docs/reference/compose_events.md
+++ b/docs/reference/compose_events.md
@@ -1,7 +1,7 @@
# docker compose events
-Receive real time events from containers.
+Receive real time events from containers
### Options
@@ -33,4 +33,4 @@ With the `--json` flag, a json object is printed one per line with the format:
}
```
-The events that can be received using this can be seen [here](https://docs.docker.com/engine/reference/commandline/system_events/#object-types).
+The events that can be received using this can be seen [here](https://docs.docker.com/reference/cli/docker/system/events/#object-types).
diff --git a/docs/reference/compose_exec.md b/docs/reference/compose_exec.md
index 7c5f7d6a4f..fab8221782 100644
--- a/docs/reference/compose_exec.md
+++ b/docs/reference/compose_exec.md
@@ -1,20 +1,20 @@
# docker compose exec
-Execute a command in a running container.
+Execute a command in a running container
### Options
| Name | Type | Default | Description |
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
-| `-d`, `--detach` | | | Detached mode: Run command in the background. |
+| `-d`, `--detach` | | | Detached mode: Run command in the background |
| `--dry-run` | | | Execute command in dry run mode |
| `-e`, `--env` | `stringArray` | | Set environment variables |
-| `--index` | `int` | `0` | index of the container if service has multiple replicas |
+| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
-| `--privileged` | | | Give extended privileges to the process. |
-| `-u`, `--user` | `string` | | Run the command as this user. |
-| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
+| `--privileged` | | | Give extended privileges to the process |
+| `-u`, `--user` | `string` | | Run the command as this user |
+| `-w`, `--workdir` | `string` | | Path to workdir directory for this command |
diff --git a/docs/reference/compose_images.md b/docs/reference/compose_images.md
index 02a8f57ec4..a29af42f34 100644
--- a/docs/reference/compose_images.md
+++ b/docs/reference/compose_images.md
@@ -5,11 +5,11 @@ List images used by the created containers
### Options
-| Name | Type | Default | Description |
-|:----------------|:---------|:--------|:--------------------------------------------|
-| `--dry-run` | | | Execute command in dry run mode |
-| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
-| `-q`, `--quiet` | | | Only display IDs |
+| Name | Type | Default | Description |
+|:----------------|:---------|:--------|:-------------------------------------------|
+| `--dry-run` | | | Execute command in dry run mode |
+| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
+| `-q`, `--quiet` | | | Only display IDs |
diff --git a/docs/reference/compose_kill.md b/docs/reference/compose_kill.md
index 2e79d806e9..a10ce55bef 100644
--- a/docs/reference/compose_kill.md
+++ b/docs/reference/compose_kill.md
@@ -1,15 +1,15 @@
# docker compose kill
-Force stop service containers.
+Force stop service containers
### Options
-| Name | Type | Default | Description |
-|:-------------------|:---------|:----------|:----------------------------------------------------------------|
-| `--dry-run` | | | Execute command in dry run mode |
-| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
-| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
+| Name | Type | Default | Description |
+|:-------------------|:---------|:----------|:---------------------------------------------------------------|
+| `--dry-run` | | | Execute command in dry run mode |
+| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
+| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container |
diff --git a/docs/reference/compose_logs.md b/docs/reference/compose_logs.md
index b6c705bab2..15291f71d9 100644
--- a/docs/reference/compose_logs.md
+++ b/docs/reference/compose_logs.md
@@ -8,13 +8,13 @@ View output from containers
| Name | Type | Default | Description |
|:---------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
-| `-f`, `--follow` | | | Follow log output. |
+| `-f`, `--follow` | | | Follow log output |
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
-| `--no-color` | | | Produce monochrome output. |
-| `--no-log-prefix` | | | Don't print prefix in logs. |
+| `--no-color` | | | Produce monochrome output |
+| `--no-log-prefix` | | | Don't print prefix in logs |
| `--since` | `string` | | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
-| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. |
-| `-t`, `--timestamps` | | | Show timestamps. |
+| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container |
+| `-t`, `--timestamps` | | | Show timestamps |
| `--until` | `string` | | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
@@ -22,4 +22,4 @@ View output from containers
## Description
-Displays log output from services.
\ No newline at end of file
+Displays log output from services
diff --git a/docs/reference/compose_ls.md b/docs/reference/compose_ls.md
index 50a13d96f0..a1148a1675 100644
--- a/docs/reference/compose_ls.md
+++ b/docs/reference/compose_ls.md
@@ -5,17 +5,17 @@ List running compose projects
### Options
-| Name | Type | Default | Description |
-|:----------------|:---------|:--------|:--------------------------------------------|
-| `-a`, `--all` | | | Show all stopped Compose projects |
-| `--dry-run` | | | Execute command in dry run mode |
-| `--filter` | `filter` | | Filter output based on conditions provided. |
-| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
-| `-q`, `--quiet` | | | Only display IDs. |
+| Name | Type | Default | Description |
+|:----------------|:---------|:--------|:-------------------------------------------|
+| `-a`, `--all` | | | Show all stopped Compose projects |
+| `--dry-run` | | | Execute command in dry run mode |
+| `--filter` | `filter` | | Filter output based on conditions provided |
+| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
+| `-q`, `--quiet` | | | Only display IDs |
## Description
-Lists running Compose projects.
\ No newline at end of file
+Lists running Compose projects
diff --git a/docs/reference/compose_port.md b/docs/reference/compose_port.md
index ffd3d8eb53..5e70b35329 100644
--- a/docs/reference/compose_port.md
+++ b/docs/reference/compose_port.md
@@ -1,14 +1,14 @@
# docker compose port
-Print the public port for a port binding.
+Print the public port for a port binding
### Options
| Name | Type | Default | Description |
|:-------------|:---------|:--------|:--------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
-| `--index` | `int` | `0` | index of the container if service has multiple replicas |
+| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
| `--protocol` | `string` | `tcp` | tcp or udp |
@@ -16,4 +16,4 @@ Print the public port for a port binding.
## Description
-Prints the public port for a port binding.
\ No newline at end of file
+Prints the public port for a port binding
diff --git a/docs/reference/compose_ps.md b/docs/reference/compose_ps.md
index 81ef255098..f0c1a25762 100644
--- a/docs/reference/compose_ps.md
+++ b/docs/reference/compose_ps.md
@@ -9,7 +9,7 @@ List containers
|:----------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
| `--dry-run` | | | Execute command in dry run mode |
-| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
+| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status) |
| [`--format`](#format) | `string` | `table` | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
| `--no-trunc` | | | Don't truncate output |
| `--orphans` | | | Include orphaned services (not declared by project) |
diff --git a/docs/reference/compose_pull.md b/docs/reference/compose_pull.md
index 03be4de05e..2c29052fd8 100644
--- a/docs/reference/compose_pull.md
+++ b/docs/reference/compose_pull.md
@@ -5,22 +5,21 @@ Pull service images
### Options
-| Name | Type | Default | Description |
-|:-------------------------|:---------|:--------|:--------------------------------------------------------|
-| `--dry-run` | | | Execute command in dry run mode |
-| `--ignore-buildable` | | | Ignore images that can be built. |
-| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures. |
-| `--include-deps` | | | Also pull services declared as dependencies. |
-| `--policy` | `string` | | Apply pull policy ("missing"\|"always"). |
-| `-q`, `--quiet` | | | Pull without printing progress information. |
+| Name | Type | Default | Description |
+|:-------------------------|:---------|:--------|:-------------------------------------------------------|
+| `--dry-run` | | | Execute command in dry run mode |
+| `--ignore-buildable` | | | Ignore images that can be built |
+| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures |
+| `--include-deps` | | | Also pull services declared as dependencies |
+| `--policy` | `string` | | Apply pull policy ("missing"\|"always") |
+| `-q`, `--quiet` | | | Pull without printing progress information |
## Description
-Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
-those images.
+Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
## Examples
diff --git a/docs/reference/compose_restart.md b/docs/reference/compose_restart.md
index 1e7bdf3c8c..d77b461a85 100644
--- a/docs/reference/compose_restart.md
+++ b/docs/reference/compose_restart.md
@@ -8,7 +8,7 @@ Restart service containers
| Name | Type | Default | Description |
|:------------------|:------|:--------|:--------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
-| `--no-deps` | | | Don't restart dependent services. |
+| `--no-deps` | | | Don't restart dependent services |
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
diff --git a/docs/reference/compose_run.md b/docs/reference/compose_run.md
index 033ac5e78d..185b4ad073 100644
--- a/docs/reference/compose_run.md
+++ b/docs/reference/compose_run.md
@@ -1,33 +1,33 @@
# docker compose run
-Run a one-off command on a service.
+Run a one-off command on a service
### Options
-| Name | Type | Default | Description |
-|:------------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
-| `--build` | | | Build image before starting container. |
-| `--cap-add` | `list` | | Add Linux capabilities |
-| `--cap-drop` | `list` | | Drop Linux capabilities |
-| `-d`, `--detach` | | | Run container in background and print container ID |
-| `--dry-run` | | | Execute command in dry run mode |
-| `--entrypoint` | `string` | | Override the entrypoint of the image |
-| `-e`, `--env` | `stringArray` | | Set environment variables |
-| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
-| `-l`, `--label` | `stringArray` | | Add or override a label |
-| `--name` | `string` | | Assign a name to the container |
-| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
-| `--no-deps` | | | Don't start linked services. |
-| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
-| `--quiet-pull` | | | Pull without printing progress information. |
-| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
-| `--rm` | | | Automatically remove the container when it exits |
-| `-P`, `--service-ports` | | | Run command with all service's ports enabled and mapped to the host. |
-| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to. |
-| `-u`, `--user` | `string` | | Run as specified username or uid |
-| `-v`, `--volume` | `stringArray` | | Bind mount a volume. |
-| `-w`, `--workdir` | `string` | | Working directory inside the container |
+| Name | Type | Default | Description |
+|:------------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
+| `--build` | | | Build image before starting container |
+| `--cap-add` | `list` | | Add Linux capabilities |
+| `--cap-drop` | `list` | | Drop Linux capabilities |
+| `-d`, `--detach` | | | Run container in background and print container ID |
+| `--dry-run` | | | Execute command in dry run mode |
+| `--entrypoint` | `string` | | Override the entrypoint of the image |
+| `-e`, `--env` | `stringArray` | | Set environment variables |
+| `-i`, `--interactive` | | | Keep STDIN open even if not attached |
+| `-l`, `--label` | `stringArray` | | Add or override a label |
+| `--name` | `string` | | Assign a name to the container |
+| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected) |
+| `--no-deps` | | | Don't start linked services |
+| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
+| `--quiet-pull` | | | Pull without printing progress information |
+| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
+| `--rm` | | | Automatically remove the container when it exits |
+| `-P`, `--service-ports` | | | Run command with all service's ports enabled and mapped to the host |
+| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to |
+| `-u`, `--user` | `string` | | Run as specified username or uid |
+| `-v`, `--volume` | `stringArray` | | Bind mount a volume |
+| `-w`, `--workdir` | `string` | | Working directory inside the container |
diff --git a/docs/reference/compose_scale.md b/docs/reference/compose_scale.md
index 5cf5830e28..e30508328a 100644
--- a/docs/reference/compose_scale.md
+++ b/docs/reference/compose_scale.md
@@ -8,7 +8,7 @@ Scale services
| Name | Type | Default | Description |
|:------------|:-----|:--------|:--------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
-| `--no-deps` | | | Don't start linked services. |
+| `--no-deps` | | | Don't start linked services |
diff --git a/docs/reference/compose_start.md b/docs/reference/compose_start.md
index b525347f7e..4ea26e9b59 100644
--- a/docs/reference/compose_start.md
+++ b/docs/reference/compose_start.md
@@ -14,4 +14,4 @@ Start services
## Description
-Starts existing containers for a service.
+Starts existing containers for a service
diff --git a/docs/reference/compose_top.md b/docs/reference/compose_top.md
index 9aff0ce71f..a23373832a 100644
--- a/docs/reference/compose_top.md
+++ b/docs/reference/compose_top.md
@@ -14,7 +14,7 @@ Display the running processes
## Description
-Displays the running processes.
+Displays the running processes
## Examples
diff --git a/docs/reference/compose_unpause.md b/docs/reference/compose_unpause.md
index 0df10a9924..9d810e4710 100644
--- a/docs/reference/compose_unpause.md
+++ b/docs/reference/compose_unpause.md
@@ -14,4 +14,4 @@ Unpause services
## Description
-Unpauses paused containers of a service.
+Unpauses paused containers of a service
diff --git a/docs/reference/compose_up.md b/docs/reference/compose_up.md
index 49fc17225a..a34766c484 100644
--- a/docs/reference/compose_up.md
+++ b/docs/reference/compose_up.md
@@ -5,33 +5,34 @@ Create and start containers
### Options
-| Name | Type | Default | Description |
-|:-----------------------------|:--------------|:---------|:---------------------------------------------------------------------------------------------------------|
-| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
-| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
-| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
-| `--attach-dependencies` | | | Automatically attach to log output of dependent services. |
-| `--build` | | | Build images before starting containers. |
-| `-d`, `--detach` | | | Detached mode: Run containers in the background |
-| `--dry-run` | | | Execute command in dry run mode |
-| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
-| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
-| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services. |
-| `--no-build` | | | Don't build an image, even if it's policy. |
-| `--no-color` | | | Produce monochrome output. |
-| `--no-deps` | | | Don't start linked services. |
-| `--no-log-prefix` | | | Don't print prefix in logs. |
-| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
-| `--no-start` | | | Don't start the services after creating them. |
-| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
-| `--quiet-pull` | | | Pull without printing progress information. |
-| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
-| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. |
-| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
-| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running. |
-| `--timestamps` | | | Show timestamps. |
-| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
-| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy. |
+| Name | Type | Default | Description |
+|:-----------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------|
+| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d |
+| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. |
+| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. |
+| `--attach-dependencies` | | | Automatically attach to log output of dependent services |
+| `--build` | | | Build images before starting containers |
+| `-d`, `--detach` | | | Detached mode: Run containers in the background |
+| `--dry-run` | | | Execute command in dry run mode |
+| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit |
+| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed |
+| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services |
+| `--no-build` | | | Don't build an image, even if it's policy |
+| `--no-color` | | | Produce monochrome output |
+| `--no-deps` | | | Don't start linked services |
+| `--no-log-prefix` | | | Don't print prefix in logs |
+| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
+| `--no-start` | | | Don't start the services after creating them |
+| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
+| `--quiet-pull` | | | Pull without printing progress information |
+| `--remove-orphans` | | | Remove containers for services not defined in the Compose file |
+| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers |
+| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
+| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
+| `--timestamps` | | | Show timestamps |
+| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. |
+| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy |
+| `-w`, `--watch` | | | Watch source code and rebuild/refresh containers when files are updated. |
diff --git a/docs/reference/compose_version.md b/docs/reference/compose_version.md
index 66081d65ec..9284d8e935 100644
--- a/docs/reference/compose_version.md
+++ b/docs/reference/compose_version.md
@@ -9,7 +9,7 @@ Show the Docker Compose version information
|:-----------------|:---------|:--------|:---------------------------------------------------------------|
| `--dry-run` | | | Execute command in dry run mode |
| `-f`, `--format` | `string` | | Format the output. Values: [pretty \| json]. (Default: pretty) |
-| `--short` | | | Shows only Compose's version number. |
+| `--short` | | | Shows only Compose's version number |
diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml
index 83f555b6c8..acdd391261 100644
--- a/docs/reference/docker_compose.yaml
+++ b/docs/reference/docker_compose.yaml
@@ -242,7 +242,7 @@ options:
- option: env-file
value_type: stringArray
default_value: '[]'
- description: Specify an alternate environment file.
+ description: Specify an alternate environment file
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_alpha_publish.yaml b/docs/reference/docker_compose_alpha_publish.yaml
index f2237f2ae4..38868104a5 100644
--- a/docs/reference/docker_compose_alpha_publish.yaml
+++ b/docs/reference/docker_compose_alpha_publish.yaml
@@ -18,7 +18,7 @@ options:
- option: resolve-image-digests
value_type: bool
default_value: "false"
- description: Pin image tags to digests.
+ description: Pin image tags to digests
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_attach.yaml b/docs/reference/docker_compose_attach.yaml
index 14c584f411..8fd6957ca1 100644
--- a/docs/reference/docker_compose_attach.yaml
+++ b/docs/reference/docker_compose_attach.yaml
@@ -1,8 +1,8 @@
command: docker compose attach
short: |
- Attach local standard input, output, and error streams to a service's running container.
+ Attach local standard input, output, and error streams to a service's running container
long: |
- Attach local standard input, output, and error streams to a service's running container.
+ Attach local standard input, output, and error streams to a service's running container
usage: docker compose attach [OPTIONS] SERVICE
pname: docker compose
plink: docker_compose.yaml
diff --git a/docs/reference/docker_compose_build.yaml b/docs/reference/docker_compose_build.yaml
index d6085d0d15..34175696fb 100644
--- a/docs/reference/docker_compose_build.yaml
+++ b/docs/reference/docker_compose_build.yaml
@@ -17,7 +17,7 @@ options:
- option: build-arg
value_type: stringArray
default_value: '[]'
- description: Set build-time variables for services.
+ description: Set build-time variables for services
deprecated: false
hidden: false
experimental: false
@@ -26,7 +26,7 @@ options:
swarm: false
- option: builder
value_type: string
- description: Set builder to use.
+ description: Set builder to use
deprecated: false
hidden: false
experimental: false
@@ -109,7 +109,7 @@ options:
- option: pull
value_type: bool
default_value: "false"
- description: Always attempt to pull a newer version of the image.
+ description: Always attempt to pull a newer version of the image
deprecated: false
hidden: false
experimental: false
@@ -119,7 +119,7 @@ options:
- option: push
value_type: bool
default_value: "false"
- description: Push service images.
+ description: Push service images
deprecated: false
hidden: false
experimental: false
@@ -150,7 +150,7 @@ options:
- option: with-dependencies
value_type: bool
default_value: "false"
- description: Also build dependencies (transitively).
+ description: Also build dependencies (transitively)
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_config.yaml b/docs/reference/docker_compose_config.yaml
index d180c8d143..ea7669b0d9 100644
--- a/docs/reference/docker_compose_config.yaml
+++ b/docs/reference/docker_compose_config.yaml
@@ -21,7 +21,7 @@ options:
swarm: false
- option: hash
value_type: string
- description: Print the service config hash, one per line.
+ description: Print the service config hash, one per line
deprecated: false
hidden: false
experimental: false
@@ -31,7 +31,7 @@ options:
- option: images
value_type: bool
default_value: "false"
- description: Print the image names, one per line.
+ description: Print the image names, one per line
deprecated: false
hidden: false
experimental: false
@@ -52,7 +52,7 @@ options:
- option: no-interpolate
value_type: bool
default_value: "false"
- description: Don't interpolate environment variables.
+ description: Don't interpolate environment variables
deprecated: false
hidden: false
experimental: false
@@ -62,7 +62,7 @@ options:
- option: no-normalize
value_type: bool
default_value: "false"
- description: Don't normalize compose model.
+ description: Don't normalize compose model
deprecated: false
hidden: false
experimental: false
@@ -72,7 +72,7 @@ options:
- option: no-path-resolution
value_type: bool
default_value: "false"
- description: Don't resolve file paths.
+ description: Don't resolve file paths
deprecated: false
hidden: false
experimental: false
@@ -92,7 +92,7 @@ options:
- option: profiles
value_type: bool
default_value: "false"
- description: Print the profile names, one per line.
+ description: Print the profile names, one per line
deprecated: false
hidden: false
experimental: false
@@ -103,7 +103,7 @@ options:
shorthand: q
value_type: bool
default_value: "false"
- description: Only validate the configuration, don't print anything.
+ description: Only validate the configuration, don't print anything
deprecated: false
hidden: false
experimental: false
@@ -113,7 +113,7 @@ options:
- option: resolve-image-digests
value_type: bool
default_value: "false"
- description: Pin image tags to digests.
+ description: Pin image tags to digests
deprecated: false
hidden: false
experimental: false
@@ -123,7 +123,7 @@ options:
- option: services
value_type: bool
default_value: "false"
- description: Print the service names, one per line.
+ description: Print the service names, one per line
deprecated: false
hidden: false
experimental: false
@@ -133,7 +133,7 @@ options:
- option: volumes
value_type: bool
default_value: "false"
- description: Print the volume names, one per line.
+ description: Print the volume names, one per line
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_cp.yaml b/docs/reference/docker_compose_cp.yaml
index 4551c13c64..8ff3cf37e0 100644
--- a/docs/reference/docker_compose_cp.yaml
+++ b/docs/reference/docker_compose_cp.yaml
@@ -10,7 +10,7 @@ options:
- option: all
value_type: bool
default_value: "false"
- description: copy to all the containers of the service.
+ description: Copy to all the containers of the service
deprecated: true
hidden: true
experimental: false
@@ -42,7 +42,7 @@ options:
- option: index
value_type: int
default_value: "0"
- description: index of the container if service has multiple replicas
+ description: Index of the container if service has multiple replicas
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_create.yaml b/docs/reference/docker_compose_create.yaml
index 05a4a51437..a07e1c88cc 100644
--- a/docs/reference/docker_compose_create.yaml
+++ b/docs/reference/docker_compose_create.yaml
@@ -1,6 +1,6 @@
command: docker compose create
-short: Creates containers for a service.
-long: Creates containers for a service.
+short: Creates containers for a service
+long: Creates containers for a service
usage: docker compose create [OPTIONS] [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
@@ -8,7 +8,7 @@ options:
- option: build
value_type: bool
default_value: "false"
- description: Build images before starting containers.
+ description: Build images before starting containers
deprecated: false
hidden: false
experimental: false
@@ -19,7 +19,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Recreate containers even if their configuration and image haven't changed.
+ Recreate containers even if their configuration and image haven't changed
deprecated: false
hidden: false
experimental: false
@@ -29,7 +29,7 @@ options:
- option: no-build
value_type: bool
default_value: "false"
- description: Don't build an image, even if it's policy.
+ description: Don't build an image, even if it's policy
deprecated: false
hidden: false
experimental: false
@@ -57,10 +57,20 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
+ - option: quiet-pull
+ value_type: bool
+ default_value: "false"
+ description: Pull without printing progress information
+ deprecated: false
+ hidden: false
+ experimental: false
+ experimentalcli: false
+ kubernetes: false
+ swarm: false
- option: remove-orphans
value_type: bool
default_value: "false"
- description: Remove containers for services not defined in the Compose file.
+ description: Remove containers for services not defined in the Compose file
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_down.yaml b/docs/reference/docker_compose_down.yaml
index 7964dd49c3..77bf526289 100644
--- a/docs/reference/docker_compose_down.yaml
+++ b/docs/reference/docker_compose_down.yaml
@@ -21,7 +21,7 @@ options:
- option: remove-orphans
value_type: bool
default_value: "false"
- description: Remove containers for services not defined in the Compose file.
+ description: Remove containers for services not defined in the Compose file
deprecated: false
hidden: false
experimental: false
@@ -54,7 +54,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers.
+ Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_events.yaml b/docs/reference/docker_compose_events.yaml
index cd51372f72..fe6d4216ce 100644
--- a/docs/reference/docker_compose_events.yaml
+++ b/docs/reference/docker_compose_events.yaml
@@ -1,5 +1,5 @@
command: docker compose events
-short: Receive real time events from containers.
+short: Receive real time events from containers
long: |-
Stream container events for every container in the project.
@@ -19,7 +19,7 @@ long: |-
}
```
- The events that can be received using this can be seen [here](/engine/reference/commandline/system_events/#object-types).
+ The events that can be received using this can be seen [here](/reference/cli/docker/system/events/#object-types).
usage: docker compose events [OPTIONS] [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
diff --git a/docs/reference/docker_compose_exec.yaml b/docs/reference/docker_compose_exec.yaml
index ce6faeda6d..b2a1cf2068 100644
--- a/docs/reference/docker_compose_exec.yaml
+++ b/docs/reference/docker_compose_exec.yaml
@@ -1,5 +1,5 @@
command: docker compose exec
-short: Execute a command in a running container.
+short: Execute a command in a running container
long: |-
This is the equivalent of `docker exec` targeting a Compose service.
@@ -13,7 +13,7 @@ options:
shorthand: d
value_type: bool
default_value: "false"
- description: 'Detached mode: Run command in the background.'
+ description: 'Detached mode: Run command in the background'
deprecated: false
hidden: false
experimental: false
@@ -34,7 +34,7 @@ options:
- option: index
value_type: int
default_value: "0"
- description: index of the container if service has multiple replicas
+ description: Index of the container if service has multiple replicas
deprecated: false
hidden: false
experimental: false
@@ -45,7 +45,7 @@ options:
shorthand: i
value_type: bool
default_value: "true"
- description: Keep STDIN open even if not attached.
+ description: Keep STDIN open even if not attached
deprecated: false
hidden: true
experimental: false
@@ -67,7 +67,7 @@ options:
- option: privileged
value_type: bool
default_value: "false"
- description: Give extended privileges to the process.
+ description: Give extended privileges to the process
deprecated: false
hidden: false
experimental: false
@@ -78,7 +78,7 @@ options:
shorthand: t
value_type: bool
default_value: "true"
- description: Allocate a pseudo-TTY.
+ description: Allocate a pseudo-TTY
deprecated: false
hidden: true
experimental: false
@@ -88,7 +88,7 @@ options:
- option: user
shorthand: u
value_type: string
- description: Run the command as this user.
+ description: Run the command as this user
deprecated: false
hidden: false
experimental: false
@@ -98,7 +98,7 @@ options:
- option: workdir
shorthand: w
value_type: string
- description: Path to workdir directory for this command.
+ description: Path to workdir directory for this command
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_images.yaml b/docs/reference/docker_compose_images.yaml
index 4719590af5..33187df42d 100644
--- a/docs/reference/docker_compose_images.yaml
+++ b/docs/reference/docker_compose_images.yaml
@@ -8,7 +8,7 @@ options:
- option: format
value_type: string
default_value: table
- description: 'Format the output. Values: [table | json].'
+ description: 'Format the output. Values: [table | json]'
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_kill.yaml b/docs/reference/docker_compose_kill.yaml
index 2134227641..b6d5334827 100644
--- a/docs/reference/docker_compose_kill.yaml
+++ b/docs/reference/docker_compose_kill.yaml
@@ -1,5 +1,5 @@
command: docker compose kill
-short: Force stop service containers.
+short: Force stop service containers
long: |-
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example:
@@ -13,7 +13,7 @@ options:
- option: remove-orphans
value_type: bool
default_value: "false"
- description: Remove containers for services not defined in the Compose file.
+ description: Remove containers for services not defined in the Compose file
deprecated: false
hidden: false
experimental: false
@@ -24,7 +24,7 @@ options:
shorthand: s
value_type: string
default_value: SIGKILL
- description: SIGNAL to send to the container.
+ description: SIGNAL to send to the container
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_logs.yaml b/docs/reference/docker_compose_logs.yaml
index fe6bbdb424..92d94dd108 100644
--- a/docs/reference/docker_compose_logs.yaml
+++ b/docs/reference/docker_compose_logs.yaml
@@ -1,6 +1,6 @@
command: docker compose logs
short: View output from containers
-long: Displays log output from services.
+long: Displays log output from services
usage: docker compose logs [OPTIONS] [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
@@ -9,7 +9,7 @@ options:
shorthand: f
value_type: bool
default_value: "false"
- description: Follow log output.
+ description: Follow log output
deprecated: false
hidden: false
experimental: false
@@ -29,7 +29,7 @@ options:
- option: no-color
value_type: bool
default_value: "false"
- description: Produce monochrome output.
+ description: Produce monochrome output
deprecated: false
hidden: false
experimental: false
@@ -39,7 +39,7 @@ options:
- option: no-log-prefix
value_type: bool
default_value: "false"
- description: Don't print prefix in logs.
+ description: Don't print prefix in logs
deprecated: false
hidden: false
experimental: false
@@ -61,7 +61,7 @@ options:
value_type: string
default_value: all
description: |
- Number of lines to show from the end of the logs for each container.
+ Number of lines to show from the end of the logs for each container
deprecated: false
hidden: false
experimental: false
@@ -72,7 +72,7 @@ options:
shorthand: t
value_type: bool
default_value: "false"
- description: Show timestamps.
+ description: Show timestamps
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_ls.yaml b/docs/reference/docker_compose_ls.yaml
index c4b6d5f7c1..a2efac2a88 100644
--- a/docs/reference/docker_compose_ls.yaml
+++ b/docs/reference/docker_compose_ls.yaml
@@ -1,6 +1,6 @@
command: docker compose ls
short: List running compose projects
-long: Lists running Compose projects.
+long: Lists running Compose projects
usage: docker compose ls [OPTIONS]
pname: docker compose
plink: docker_compose.yaml
@@ -18,7 +18,7 @@ options:
swarm: false
- option: filter
value_type: filter
- description: Filter output based on conditions provided.
+ description: Filter output based on conditions provided
deprecated: false
hidden: false
experimental: false
@@ -28,7 +28,7 @@ options:
- option: format
value_type: string
default_value: table
- description: 'Format the output. Values: [table | json].'
+ description: 'Format the output. Values: [table | json]'
deprecated: false
hidden: false
experimental: false
@@ -39,7 +39,7 @@ options:
shorthand: q
value_type: bool
default_value: "false"
- description: Only display IDs.
+ description: Only display IDs
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_port.yaml b/docs/reference/docker_compose_port.yaml
index 7c8dee90ce..8a07f31ea5 100644
--- a/docs/reference/docker_compose_port.yaml
+++ b/docs/reference/docker_compose_port.yaml
@@ -1,6 +1,6 @@
command: docker compose port
-short: Print the public port for a port binding.
-long: Prints the public port for a port binding.
+short: Print the public port for a port binding
+long: Prints the public port for a port binding
usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT
pname: docker compose
plink: docker_compose.yaml
@@ -8,7 +8,7 @@ options:
- option: index
value_type: int
default_value: "0"
- description: index of the container if service has multiple replicas
+ description: Index of the container if service has multiple replicas
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_ps.yaml b/docs/reference/docker_compose_ps.yaml
index 78842a9990..d037027569 100644
--- a/docs/reference/docker_compose_ps.yaml
+++ b/docs/reference/docker_compose_ps.yaml
@@ -35,7 +35,7 @@ options:
swarm: false
- option: filter
value_type: string
- description: 'Filter services by a property (supported filters: status).'
+ description: 'Filter services by a property (supported filters: status)'
details_url: '#filter'
deprecated: false
hidden: false
diff --git a/docs/reference/docker_compose_pull.yaml b/docs/reference/docker_compose_pull.yaml
index 46a4711624..5b1316df13 100644
--- a/docs/reference/docker_compose_pull.yaml
+++ b/docs/reference/docker_compose_pull.yaml
@@ -1,8 +1,7 @@
command: docker compose pull
short: Pull service images
-long: |-
- Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
- those images.
+long: |
+ Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
usage: docker compose pull [OPTIONS] [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
@@ -10,7 +9,7 @@ options:
- option: ignore-buildable
value_type: bool
default_value: "false"
- description: Ignore images that can be built.
+ description: Ignore images that can be built
deprecated: false
hidden: false
experimental: false
@@ -20,7 +19,7 @@ options:
- option: ignore-pull-failures
value_type: bool
default_value: "false"
- description: Pull what it can and ignores images with pull failures.
+ description: Pull what it can and ignores images with pull failures
deprecated: false
hidden: false
experimental: false
@@ -30,7 +29,7 @@ options:
- option: include-deps
value_type: bool
default_value: "false"
- description: Also pull services declared as dependencies.
+ description: Also pull services declared as dependencies
deprecated: false
hidden: false
experimental: false
@@ -40,7 +39,7 @@ options:
- option: no-parallel
value_type: bool
default_value: "true"
- description: DEPRECATED disable parallel pulling.
+ description: DEPRECATED disable parallel pulling
deprecated: false
hidden: true
experimental: false
@@ -50,7 +49,7 @@ options:
- option: parallel
value_type: bool
default_value: "true"
- description: DEPRECATED pull multiple images in parallel.
+ description: DEPRECATED pull multiple images in parallel
deprecated: false
hidden: true
experimental: false
@@ -59,7 +58,7 @@ options:
swarm: false
- option: policy
value_type: string
- description: Apply pull policy ("missing"|"always").
+ description: Apply pull policy ("missing"|"always")
deprecated: false
hidden: false
experimental: false
@@ -70,7 +69,7 @@ options:
shorthand: q
value_type: bool
default_value: "false"
- description: Pull without printing progress information.
+ description: Pull without printing progress information
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_restart.yaml b/docs/reference/docker_compose_restart.yaml
index 3126eb005a..3b2a4bddd1 100644
--- a/docs/reference/docker_compose_restart.yaml
+++ b/docs/reference/docker_compose_restart.yaml
@@ -18,7 +18,7 @@ options:
- option: no-deps
value_type: bool
default_value: "false"
- description: Don't restart dependent services.
+ description: Don't restart dependent services
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_run.yaml b/docs/reference/docker_compose_run.yaml
index 12abafc3b6..0584bdd070 100644
--- a/docs/reference/docker_compose_run.yaml
+++ b/docs/reference/docker_compose_run.yaml
@@ -1,5 +1,5 @@
command: docker compose run
-short: Run a one-off command on a service.
+short: Run a one-off command on a service
long: |-
Runs a one-time command against a service.
@@ -61,7 +61,7 @@ options:
- option: build
value_type: bool
default_value: "false"
- description: Build image before starting container.
+ description: Build image before starting container
deprecated: false
hidden: false
experimental: false
@@ -121,7 +121,7 @@ options:
shorthand: i
value_type: bool
default_value: "true"
- description: Keep STDIN open even if not attached.
+ description: Keep STDIN open even if not attached
deprecated: false
hidden: false
experimental: false
@@ -152,7 +152,7 @@ options:
shorthand: T
value_type: bool
default_value: "true"
- description: 'Disable pseudo-TTY allocation (default: auto-detected).'
+ description: 'Disable pseudo-TTY allocation (default: auto-detected)'
deprecated: false
hidden: false
experimental: false
@@ -162,7 +162,7 @@ options:
- option: no-deps
value_type: bool
default_value: "false"
- description: Don't start linked services.
+ description: Don't start linked services
deprecated: false
hidden: false
experimental: false
@@ -173,7 +173,7 @@ options:
shorthand: p
value_type: stringArray
default_value: '[]'
- description: Publish a container's port(s) to the host.
+ description: Publish a container's port(s) to the host
deprecated: false
hidden: false
experimental: false
@@ -183,7 +183,7 @@ options:
- option: quiet-pull
value_type: bool
default_value: "false"
- description: Pull without printing progress information.
+ description: Pull without printing progress information
deprecated: false
hidden: false
experimental: false
@@ -193,7 +193,7 @@ options:
- option: remove-orphans
value_type: bool
default_value: "false"
- description: Remove containers for services not defined in the Compose file.
+ description: Remove containers for services not defined in the Compose file
deprecated: false
hidden: false
experimental: false
@@ -215,7 +215,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Run command with all service's ports enabled and mapped to the host.
+ Run command with all service's ports enabled and mapped to the host
deprecated: false
hidden: false
experimental: false
@@ -226,7 +226,7 @@ options:
shorthand: t
value_type: bool
default_value: "true"
- description: Allocate a pseudo-TTY.
+ description: Allocate a pseudo-TTY
deprecated: false
hidden: true
experimental: false
@@ -237,7 +237,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Use the service's network useAliases in the network(s) the container connects to.
+ Use the service's network useAliases in the network(s) the container connects to
deprecated: false
hidden: false
experimental: false
@@ -258,7 +258,7 @@ options:
shorthand: v
value_type: stringArray
default_value: '[]'
- description: Bind mount a volume.
+ description: Bind mount a volume
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_scale.yaml b/docs/reference/docker_compose_scale.yaml
index 9391441f26..f840a51b4e 100644
--- a/docs/reference/docker_compose_scale.yaml
+++ b/docs/reference/docker_compose_scale.yaml
@@ -8,7 +8,7 @@ options:
- option: no-deps
value_type: bool
default_value: "false"
- description: Don't start linked services.
+ description: Don't start linked services
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_start.yaml b/docs/reference/docker_compose_start.yaml
index 9a7fa379be..902b688d3e 100644
--- a/docs/reference/docker_compose_start.yaml
+++ b/docs/reference/docker_compose_start.yaml
@@ -1,6 +1,6 @@
command: docker compose start
short: Start services
-long: Starts existing containers for a service.
+long: Starts existing containers for a service
usage: docker compose start [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
diff --git a/docs/reference/docker_compose_top.yaml b/docs/reference/docker_compose_top.yaml
index 7d87f7ccab..17cdf7e381 100644
--- a/docs/reference/docker_compose_top.yaml
+++ b/docs/reference/docker_compose_top.yaml
@@ -1,6 +1,6 @@
command: docker compose top
short: Display the running processes
-long: Displays the running processes.
+long: Displays the running processes
usage: docker compose top [SERVICES...]
pname: docker compose
plink: docker_compose.yaml
diff --git a/docs/reference/docker_compose_unpause.yaml b/docs/reference/docker_compose_unpause.yaml
index 2679f53f69..e2047720b8 100644
--- a/docs/reference/docker_compose_unpause.yaml
+++ b/docs/reference/docker_compose_unpause.yaml
@@ -1,6 +1,6 @@
command: docker compose unpause
short: Unpause services
-long: Unpauses paused containers of a service.
+long: Unpauses paused containers of a service
usage: docker compose unpause [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
diff --git a/docs/reference/docker_compose_up.yaml b/docs/reference/docker_compose_up.yaml
index 2ee4d199c6..ec269c8b85 100644
--- a/docs/reference/docker_compose_up.yaml
+++ b/docs/reference/docker_compose_up.yaml
@@ -59,7 +59,7 @@ options:
- option: attach-dependencies
value_type: bool
default_value: "false"
- description: Automatically attach to log output of dependent services.
+ description: Automatically attach to log output of dependent services
deprecated: false
hidden: false
experimental: false
@@ -69,7 +69,7 @@ options:
- option: build
value_type: bool
default_value: "false"
- description: Build images before starting containers.
+ description: Build images before starting containers
deprecated: false
hidden: false
experimental: false
@@ -101,7 +101,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Recreate containers even if their configuration and image haven't changed.
+ Recreate containers even if their configuration and image haven't changed
deprecated: false
hidden: false
experimental: false
@@ -111,7 +111,7 @@ options:
- option: no-attach
value_type: stringArray
default_value: '[]'
- description: Do not attach (stream logs) to the specified services.
+ description: Do not attach (stream logs) to the specified services
deprecated: false
hidden: false
experimental: false
@@ -121,7 +121,7 @@ options:
- option: no-build
value_type: bool
default_value: "false"
- description: Don't build an image, even if it's policy.
+ description: Don't build an image, even if it's policy
deprecated: false
hidden: false
experimental: false
@@ -131,7 +131,7 @@ options:
- option: no-color
value_type: bool
default_value: "false"
- description: Produce monochrome output.
+ description: Produce monochrome output
deprecated: false
hidden: false
experimental: false
@@ -141,7 +141,7 @@ options:
- option: no-deps
value_type: bool
default_value: "false"
- description: Don't start linked services.
+ description: Don't start linked services
deprecated: false
hidden: false
experimental: false
@@ -151,7 +151,7 @@ options:
- option: no-log-prefix
value_type: bool
default_value: "false"
- description: Don't print prefix in logs.
+ description: Don't print prefix in logs
deprecated: false
hidden: false
experimental: false
@@ -172,7 +172,7 @@ options:
- option: no-start
value_type: bool
default_value: "false"
- description: Don't start the services after creating them.
+ description: Don't start the services after creating them
deprecated: false
hidden: false
experimental: false
@@ -192,7 +192,7 @@ options:
- option: quiet-pull
value_type: bool
default_value: "false"
- description: Pull without printing progress information.
+ description: Pull without printing progress information
deprecated: false
hidden: false
experimental: false
@@ -202,7 +202,7 @@ options:
- option: remove-orphans
value_type: bool
default_value: "false"
- description: Remove containers for services not defined in the Compose file.
+ description: Remove containers for services not defined in the Compose file
deprecated: false
hidden: false
experimental: false
@@ -214,7 +214,7 @@ options:
value_type: bool
default_value: "false"
description: |
- Recreate anonymous volumes instead of retrieving data from the previous containers.
+ Recreate anonymous volumes instead of retrieving data from the previous containers
deprecated: false
hidden: false
experimental: false
@@ -237,7 +237,7 @@ options:
value_type: int
default_value: "0"
description: |
- Use this timeout in seconds for container shutdown when attached or when containers are already running.
+ Use this timeout in seconds for container shutdown when attached or when containers are already running
deprecated: false
hidden: false
experimental: false
@@ -247,7 +247,7 @@ options:
- option: timestamps
value_type: bool
default_value: "false"
- description: Show timestamps.
+ description: Show timestamps
deprecated: false
hidden: false
experimental: false
@@ -267,7 +267,19 @@ options:
- option: wait-timeout
value_type: int
default_value: "0"
- description: Maximum duration to wait for the project to be running|healthy.
+ description: Maximum duration to wait for the project to be running|healthy
+ deprecated: false
+ hidden: false
+ experimental: false
+ experimentalcli: false
+ kubernetes: false
+ swarm: false
+ - option: watch
+ shorthand: w
+ value_type: bool
+ default_value: "false"
+ description: |
+ Watch source code and rebuild/refresh containers when files are updated.
deprecated: false
hidden: false
experimental: false
diff --git a/docs/reference/docker_compose_version.yaml b/docs/reference/docker_compose_version.yaml
index b06c4b1508..789e94818e 100644
--- a/docs/reference/docker_compose_version.yaml
+++ b/docs/reference/docker_compose_version.yaml
@@ -18,7 +18,7 @@ options:
- option: short
value_type: bool
default_value: "false"
- description: Shows only Compose's version number.
+ description: Shows only Compose's version number
deprecated: false
hidden: false
experimental: false
diff --git a/go.mod b/go.mod
index b5219731b3..890fa14167 100644
--- a/go.mod
+++ b/go.mod
@@ -6,15 +6,15 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Microsoft/go-winio v0.6.1
github.com/buger/goterm v1.0.4
- github.com/compose-spec/compose-go/v2 v2.0.0-rc.3
+ github.com/compose-spec/compose-go/v2 v2.0.0
github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.7.12
github.com/davecgh/go-spew v1.1.1
github.com/distribution/reference v0.5.0
github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315
- github.com/docker/cli v25.0.1+incompatible
+ github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible
github.com/docker/cli-docs-tool v0.6.0
- github.com/docker/docker v25.0.1+incompatible
+ github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0
github.com/fsnotify/fsevents v0.1.1
@@ -23,13 +23,14 @@ require (
github.com/hashicorp/go-version v1.6.0
github.com/jonboulle/clockwork v0.4.0
github.com/mattn/go-shellwords v1.0.12
+ github.com/mitchellh/go-ps v1.0.0
github.com/mitchellh/mapstructure v1.5.0
github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991
github.com/moby/patternmatcher v0.6.0
github.com/moby/term v0.5.0
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
- github.com/opencontainers/image-spec v1.1.0-rc5
+ github.com/opencontainers/image-spec v1.1.0-rc6
github.com/otiai10/copy v1.14.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
@@ -37,6 +38,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/theupdateframework/notary v0.7.0
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0
go.opentelemetry.io/otel v1.19.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0
@@ -44,10 +46,11 @@ require (
go.opentelemetry.io/otel/trace v1.19.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.4.0
- golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
+ golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
golang.org/x/sync v0.6.0
golang.org/x/sys v0.16.0
google.golang.org/grpc v1.59.0
+ gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.1
)
@@ -145,7 +148,6 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.45.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 // indirect
@@ -154,14 +156,14 @@ require (
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
- golang.org/x/crypto v0.17.0 // indirect
- golang.org/x/mod v0.11.0 // indirect
- golang.org/x/net v0.17.0 // indirect
+ golang.org/x/crypto v0.18.0 // indirect
+ golang.org/x/mod v0.14.0 // indirect
+ golang.org/x/net v0.20.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
- golang.org/x/term v0.15.0 // indirect
+ golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
- golang.org/x/tools v0.10.0 // indirect
+ golang.org/x/tools v0.17.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
@@ -169,7 +171,6 @@ require (
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.26.7 // indirect
k8s.io/apimachinery v0.26.7 // indirect
k8s.io/apiserver v0.26.7 // indirect
@@ -183,6 +184,8 @@ require (
tags.cncf.io/container-device-interface v0.6.2 // indirect
)
-// Fix an issue with fsutil v0.0.0-20230825212630-f09800878302 on Windows
-// See https://github.com/docker/buildx/issues/2207#issuecomment-1908460460
-replace github.com/tonistiigi/fsutil v0.0.0-20230825212630-f09800878302 => github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f
+replace (
+ // reverts https://github.com/moby/buildkit/pull/4094 to fix fsutil issues on Windows
+ github.com/moby/buildkit => github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124 // compose-957cb50df991
+ github.com/tonistiigi/fsutil => github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb
+)
diff --git a/go.sum b/go.sum
index d31a81011d..c23181627e 100644
--- a/go.sum
+++ b/go.sum
@@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
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.0.0-rc.3 h1:t0qajSNkH3zR4HEN2CM+GVU7GBx5AwqiYJk5w800M7w=
-github.com/compose-spec/compose-go/v2 v2.0.0-rc.3/go.mod h1:r7CJHU0GaLtRVLm2ch8RCNkJh3GHyaqqc2rSti7VP44=
+github.com/compose-spec/compose-go/v2 v2.0.0 h1:RLI8GmNxRLg759CzZITh/kGYZTYhEak121FaVYdXTC8=
+github.com/compose-spec/compose-go/v2 v2.0.0/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
@@ -110,8 +110,8 @@ github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f h1:f+sk5oEeSYL/2tjWraiDlR/JHJVwtqKYpGtMkfJ7MTc=
-github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f/go.mod h1:9kMVqMyQ/Sx2df5LtnGG+nbrmiZzCS7V6gjW3oGHsvI=
+github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124 h1:rTTqpfm06GSf2gjt8oo9LfUm2iGiYtx1VUDPfTHXqs4=
+github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124/go.mod h1:eFZFY7VaoWWKmJLwkqmcWR6x0j8q+gXcngg3E4k0558=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@@ -124,15 +124,15 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 h1:UZxx9xBADdf/9UmSdEUi+pdJoPKpgcf9QUAY5gEIYmY=
github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315/go.mod h1:X8ZHhuW6ncwtoJ36TlU+gyaROTcBkTE01VHYmTStQCE=
-github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU=
-github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible h1:xhVCHXq+P5LhT31+RuDuk0xXEbEnd50Fr37J1bGuyWg=
+github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA=
github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o=
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 v25.0.1+incompatible h1:k5TYd5rIVQRSqcTwCID+cyVA0yRg86+Pcrz1ls0/frA=
-github.com/docker/docker v25.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible h1:sCE9u4l5Kr3Z0pvUEAC6XKe/wnH6Q4O19I/0Mcqlxz8=
+github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@@ -331,13 +331,13 @@ github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
+github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
+github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
-github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991 h1:r80LLQ91uOLxU1ElAvrB1o8oBsph51lPzVnr7t2b200=
-github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991/go.mod h1:6MddWPSL5jxy+W8eMMHWDOfZzzRRKWXPZqajw72YHBc=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
@@ -382,8 +382,8 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1
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.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
-github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
+github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
+github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
@@ -472,6 +472,8 @@ github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4D
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
+github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb h1:uUe8rNyVXM8moActoBol6Xf6xX2GMr7SosR2EywMvGg=
+github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb/go.mod h1:SxX/oNQ/ag6Vaoli547ipFK9J7BZn5JqJG0JE8lf8bA=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs=
@@ -537,19 +539,19 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
-golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
+golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
-golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -564,8 +566,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
@@ -606,8 +608,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
+golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -626,8 +628,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
-golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/desktop/client.go b/internal/desktop/client.go
new file mode 100644
index 0000000000..e43a10a096
--- /dev/null
+++ b/internal/desktop/client.go
@@ -0,0 +1,93 @@
+/*
+ Copyright 2024 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 desktop
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+
+ "github.com/docker/compose/v2/internal/memnet"
+ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+)
+
+// Client for integration with Docker Desktop features.
+type Client struct {
+ client *http.Client
+}
+
+// NewClient creates a Desktop integration client for the provided in-memory
+// socket address (AF_UNIX or named pipe).
+func NewClient(apiEndpoint string) *Client {
+ var transport http.RoundTripper = &http.Transport{
+ DisableCompression: true,
+ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
+ return memnet.DialEndpoint(ctx, apiEndpoint)
+ },
+ }
+ transport = otelhttp.NewTransport(transport)
+
+ c := &Client{
+ client: &http.Client{Transport: transport},
+ }
+ return c
+}
+
+// Close releases any open connections.
+func (c *Client) Close() error {
+ c.client.CloseIdleConnections()
+ return nil
+}
+
+type PingResponse struct {
+ ServerTime int64 `json:"serverTime"`
+}
+
+// Ping is a minimal API used to ensure that the server is available.
+func (c *Client) Ping(ctx context.Context) (*PingResponse, error) {
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/ping"), http.NoBody)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := c.client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ _ = resp.Body.Close()
+ }()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
+ }
+
+ var ret PingResponse
+ if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil {
+ return nil, err
+ }
+ return &ret, nil
+}
+
+// backendURL generates a URL for the given API path.
+//
+// NOTE: Custom transport handles communication. The host is to create a valid
+// URL for the Go http.Client that is also descriptive in error/logs.
+func backendURL(path string) string {
+ return "http://docker-desktop/" + strings.TrimPrefix(path, "/")
+}
diff --git a/internal/desktop/integration.go b/internal/desktop/integration.go
new file mode 100644
index 0000000000..62dd4b9315
--- /dev/null
+++ b/internal/desktop/integration.go
@@ -0,0 +1,25 @@
+/*
+ Copyright 2024 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 desktop
+
+import (
+ "context"
+)
+
+type IntegrationService interface {
+ MaybeEnableDesktopIntegration(ctx context.Context) error
+}
diff --git a/internal/locker/pidfile.go b/internal/locker/pidfile.go
index ea688fb66f..08dcea1f3a 100644
--- a/internal/locker/pidfile.go
+++ b/internal/locker/pidfile.go
@@ -18,10 +18,7 @@ package locker
import (
"fmt"
- "os"
"path/filepath"
-
- "github.com/docker/docker/pkg/pidfile"
)
type Pidfile struct {
@@ -36,7 +33,3 @@ func NewPidfile(projectName string) (*Pidfile, error) {
path := filepath.Join(run, fmt.Sprintf("%s.pid", projectName))
return &Pidfile{path: path}, nil
}
-
-func (f *Pidfile) Lock() error {
- return pidfile.Write(f.path, os.Getpid())
-}
diff --git a/internal/locker/pidfile_unix.go b/internal/locker/pidfile_unix.go
new file mode 100644
index 0000000000..484b65d825
--- /dev/null
+++ b/internal/locker/pidfile_unix.go
@@ -0,0 +1,29 @@
+//go:build !windows
+
+/*
+ Copyright 2023 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 locker
+
+import (
+ "os"
+
+ "github.com/docker/docker/pkg/pidfile"
+)
+
+func (f *Pidfile) Lock() error {
+ return pidfile.Write(f.path, os.Getpid())
+}
diff --git a/internal/locker/pidfile_windows.go b/internal/locker/pidfile_windows.go
new file mode 100644
index 0000000000..9f8d4c3ee4
--- /dev/null
+++ b/internal/locker/pidfile_windows.go
@@ -0,0 +1,47 @@
+//go:build windows
+
+/*
+ Copyright 2023 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 locker
+
+import (
+ "github.com/docker/docker/pkg/pidfile"
+ "github.com/mitchellh/go-ps"
+ "os"
+)
+
+func (f *Pidfile) Lock() error {
+ newPID := os.Getpid()
+ err := pidfile.Write(f.path, newPID)
+ if err != nil {
+ // Get PID registered in the file
+ pid, errPid := pidfile.Read(f.path)
+ if errPid != nil {
+ return err
+ }
+ // Some users faced issues on Windows where the process written in the pidfile was identified as still existing
+ // So we used a 2nd process library to verify if this not a false positive feedback
+ // Check if the process exists
+ process, errPid := ps.FindProcess(pid)
+ if process == nil && errPid == nil {
+ // If the process does not exist, remove the pidfile and try to lock again
+ _ = os.Remove(f.path)
+ return pidfile.Write(f.path, newPID)
+ }
+ }
+ return err
+}
diff --git a/internal/memnet/conn.go b/internal/memnet/conn.go
new file mode 100644
index 0000000000..224bec7883
--- /dev/null
+++ b/internal/memnet/conn.go
@@ -0,0 +1,50 @@
+/*
+ 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 memnet
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "strings"
+)
+
+func DialEndpoint(ctx context.Context, endpoint string) (net.Conn, error) {
+ if addr, ok := strings.CutPrefix(endpoint, "unix://"); ok {
+ return Dial(ctx, "unix", addr)
+ }
+ if addr, ok := strings.CutPrefix(endpoint, "npipe://"); ok {
+ return Dial(ctx, "npipe", addr)
+ }
+ return nil, fmt.Errorf("unsupported protocol for address: %s", endpoint)
+}
+
+func Dial(ctx context.Context, network, addr string) (net.Conn, error) {
+ var d net.Dialer
+ switch network {
+ case "unix":
+ if err := validateSocketPath(addr); err != nil {
+ return nil, err
+ }
+ return d.DialContext(ctx, "unix", addr)
+ case "npipe":
+ // N.B. this will return an error on non-Windows
+ return dialNamedPipe(ctx, addr)
+ default:
+ return nil, fmt.Errorf("unsupported network: %s", network)
+ }
+}
diff --git a/internal/tracing/conn_unix.go b/internal/memnet/conn_unix.go
similarity index 64%
rename from internal/tracing/conn_unix.go
rename to internal/memnet/conn_unix.go
index 78294f4beb..e151984848 100644
--- a/internal/tracing/conn_unix.go
+++ b/internal/memnet/conn_unix.go
@@ -16,29 +16,24 @@
limitations under the License.
*/
-package tracing
+package memnet
import (
"context"
"fmt"
"net"
- "strings"
"syscall"
)
const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
-func DialInMemory(ctx context.Context, addr string) (net.Conn, error) {
- if !strings.HasPrefix(addr, "unix://") {
- return nil, fmt.Errorf("not a Unix socket address: %s", addr)
- }
- addr = strings.TrimPrefix(addr, "unix://")
+func dialNamedPipe(_ context.Context, _ string) (net.Conn, error) {
+ return nil, fmt.Errorf("named pipes are only available on Windows")
+}
+func validateSocketPath(addr string) error {
if len(addr) > maxUnixSocketPathSize {
- //goland:noinspection GoErrorStringFormat
- return nil, fmt.Errorf("Unix socket address is too long: %s", addr)
+ return fmt.Errorf("socket address is too long: %s", addr)
}
-
- var d net.Dialer
- return d.DialContext(ctx, "unix", addr)
+ return nil
}
diff --git a/internal/tracing/conn_windows.go b/internal/memnet/conn_windows.go
similarity index 73%
rename from internal/tracing/conn_windows.go
rename to internal/memnet/conn_windows.go
index 30deaa464d..b7f7d9ea8f 100644
--- a/internal/tracing/conn_windows.go
+++ b/internal/memnet/conn_windows.go
@@ -14,22 +14,20 @@
limitations under the License.
*/
-package tracing
+package memnet
import (
"context"
- "fmt"
"net"
- "strings"
"github.com/Microsoft/go-winio"
)
-func DialInMemory(ctx context.Context, addr string) (net.Conn, error) {
- if !strings.HasPrefix(addr, "npipe://") {
- return nil, fmt.Errorf("not a named pipe address: %s", addr)
- }
- addr = strings.TrimPrefix(addr, "npipe://")
-
+func dialNamedPipe(ctx context.Context, addr string) (net.Conn, error) {
return winio.DialPipeContext(ctx, addr)
}
+
+func validateSocketPath(addr string) error {
+ // AF_UNIX sockets do not have strict path limits on Windows
+ return nil
+}
diff --git a/internal/sync/docker_cp.go b/internal/sync/docker_cp.go
deleted file mode 100644
index 47077b404e..0000000000
--- a/internal/sync/docker_cp.go
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- Copyright 2023 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 sync
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "io/fs"
- "os"
-
- "github.com/compose-spec/compose-go/v2/types"
- "github.com/docker/compose/v2/pkg/api"
- "github.com/sirupsen/logrus"
-)
-
-type ComposeClient interface {
- Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error)
-
- Copy(ctx context.Context, projectName string, options api.CopyOptions) error
-}
-
-type DockerCopy struct {
- client ComposeClient
-
- projectName string
-
- infoWriter io.Writer
-}
-
-var _ Syncer = &DockerCopy{}
-
-func NewDockerCopy(projectName string, client ComposeClient, infoWriter io.Writer) *DockerCopy {
- return &DockerCopy{
- projectName: projectName,
- client: client,
- infoWriter: infoWriter,
- }
-}
-
-func (d *DockerCopy) Sync(ctx context.Context, service types.ServiceConfig, paths []PathMapping) error {
- var errs []error
- for i := range paths {
- if err := d.sync(ctx, service, paths[i]); err != nil {
- errs = append(errs, err)
- }
- }
- return errors.Join(errs...)
-}
-
-func (d *DockerCopy) sync(ctx context.Context, service types.ServiceConfig, pathMapping PathMapping) error {
- scale := service.GetScale()
-
- if fi, statErr := os.Stat(pathMapping.HostPath); statErr == nil {
- if fi.IsDir() {
- for i := 1; i <= scale; i++ {
- _, err := d.client.Exec(ctx, d.projectName, api.RunOptions{
- Service: service.Name,
- Command: []string{"mkdir", "-p", pathMapping.ContainerPath},
- Index: i,
- })
- if err != nil {
- logrus.Warnf("failed to create %q from %s: %v", pathMapping.ContainerPath, service.Name, err)
- }
- }
- fmt.Fprintf(d.infoWriter, "%s created\n", pathMapping.ContainerPath)
- } else {
- err := d.client.Copy(ctx, d.projectName, api.CopyOptions{
- Source: pathMapping.HostPath,
- Destination: fmt.Sprintf("%s:%s", service.Name, pathMapping.ContainerPath),
- })
- if err != nil {
- return err
- }
- fmt.Fprintf(d.infoWriter, "%s updated\n", pathMapping.ContainerPath)
- }
- } else if errors.Is(statErr, fs.ErrNotExist) {
- for i := 1; i <= scale; i++ {
- _, err := d.client.Exec(ctx, d.projectName, api.RunOptions{
- Service: service.Name,
- Command: []string{"rm", "-rf", pathMapping.ContainerPath},
- Index: i,
- })
- if err != nil {
- logrus.Warnf("failed to delete %q from %s: %v", pathMapping.ContainerPath, service.Name, err)
- }
- }
- fmt.Fprintf(d.infoWriter, "%s deleted from service\n", pathMapping.ContainerPath)
- }
- return nil
-}
diff --git a/internal/sync/writer.go b/internal/sync/writer.go
deleted file mode 100644
index f5c182d1bf..0000000000
--- a/internal/sync/writer.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- Copyright 2023 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 sync
-
-import (
- "errors"
- "io"
-)
-
-// lossyMultiWriter attempts to tee all writes to the provided io.PipeWriter
-// instances.
-//
-// If a writer fails during a Write call, the write-side of the pipe is then
-// closed with the error and no subsequent attempts are made to write to the
-// pipe.
-//
-// If all writers fail during a write, an error is returned.
-//
-// On Close, any remaining writers are closed.
-type lossyMultiWriter struct {
- writers []*io.PipeWriter
-}
-
-// newLossyMultiWriter creates a new writer that *attempts* to tee all data written to it to the provided io.PipeWriter
-// instances. Rather than failing a write operation if any writer fails, writes only fail if there are no more valid
-// writers. Otherwise, errors for specific writers are propagated via CloseWithError.
-func newLossyMultiWriter(writers ...*io.PipeWriter) *lossyMultiWriter {
- // reverse the writers because during the write we iterate
- // backwards, so this way we'll end up writing in the same
- // order as the writers were passed to us
- writers = append([]*io.PipeWriter(nil), writers...)
- for i, j := 0, len(writers)-1; i < j; i, j = i+1, j-1 {
- writers[i], writers[j] = writers[j], writers[i]
- }
-
- return &lossyMultiWriter{
- writers: writers,
- }
-}
-
-// Write writes to each writer that is still active (i.e. has not failed/encountered an error on write).
-//
-// If a writer encounters an error during the write, the write side of the pipe is closed with the error
-// and no subsequent attempts will be made to write to that writer.
-//
-// An error is only returned from this function if ALL writers have failed.
-func (l *lossyMultiWriter) Write(p []byte) (int, error) {
- // NOTE: this function iterates backwards so that it can
- // safely remove elements during the loop
- for i := len(l.writers) - 1; i >= 0; i-- {
- written, err := l.writers[i].Write(p)
- if err == nil && written != len(p) {
- err = io.ErrShortWrite
- }
- if err != nil {
- // pipe writer close cannot fail
- _ = l.writers[i].CloseWithError(err)
- l.writers = append(l.writers[:i], l.writers[i+1:]...)
- }
- }
-
- if len(l.writers) == 0 {
- return 0, errors.New("no writers remaining")
- }
-
- return len(p), nil
-}
-
-// Close closes any still open (non-failed) writers.
-//
-// Failed writers have already been closed with an error.
-func (l *lossyMultiWriter) Close() {
- for i := range l.writers {
- // pipe writer close cannot fail
- _ = l.writers[i].Close()
- }
-}
diff --git a/internal/sync/writer_test.go b/internal/sync/writer_test.go
deleted file mode 100644
index b6de694c72..0000000000
--- a/internal/sync/writer_test.go
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- Copyright 2023 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 sync
-
-import (
- "context"
- "errors"
- "io"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestLossyMultiWriter(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- t.Cleanup(cancel)
-
- const count = 5
- readers := make([]*bufReader, count)
- writers := make([]*io.PipeWriter, count)
- for i := 0; i < count; i++ {
- r, w := io.Pipe()
- readers[i] = newBufReader(ctx, r)
- writers[i] = w
- }
-
- w := newLossyMultiWriter(writers...)
- t.Cleanup(w.Close)
- n, err := w.Write([]byte("hello world"))
- require.Equal(t, 11, n)
- require.NoError(t, err)
- for i := range readers {
- readers[i].waitForWrite(t)
- require.Equal(t, "hello world", string(readers[i].contents()))
- readers[i].reset()
- }
-
- // even if a writer fails (in this case simulated by closing the receiving end of the pipe),
- // write operations should continue to return nil error but the writer should be closed
- // with an error
- const failIndex = 3
- require.NoError(t, readers[failIndex].r.CloseWithError(errors.New("oh no")))
- n, err = w.Write([]byte("hello"))
- require.Equal(t, 5, n)
- require.NoError(t, err)
- for i := range readers {
- readers[i].waitForWrite(t)
- if i == failIndex {
- err := readers[i].error()
- require.EqualError(t, err, "io: read/write on closed pipe")
- require.Empty(t, readers[i].contents())
- } else {
- require.Equal(t, "hello", string(readers[i].contents()))
- }
- }
-
- // perform another write, verify there's still no errors
- n, err = w.Write([]byte(" world"))
- require.Equal(t, 6, n)
- require.NoError(t, err)
-}
-
-type bufReader struct {
- ctx context.Context
- r *io.PipeReader
- mu sync.Mutex
- err error
- data []byte
- writeSync chan struct{}
-}
-
-func newBufReader(ctx context.Context, r *io.PipeReader) *bufReader {
- b := &bufReader{
- ctx: ctx,
- r: r,
- writeSync: make(chan struct{}),
- }
- go b.consume()
- return b
-}
-
-func (b *bufReader) waitForWrite(t testing.TB) {
- t.Helper()
- select {
- case <-b.writeSync:
- return
- case <-time.After(50 * time.Millisecond):
- t.Fatal("timed out waiting for write")
- }
-}
-
-func (b *bufReader) consume() {
- defer close(b.writeSync)
- for {
- buf := make([]byte, 512)
- n, err := b.r.Read(buf)
- if n != 0 {
- b.mu.Lock()
- b.data = append(b.data, buf[:n]...)
- b.mu.Unlock()
- }
- if errors.Is(err, io.EOF) {
- return
- }
- if err != nil {
- b.mu.Lock()
- b.err = err
- b.mu.Unlock()
- return
- }
- // prevent goroutine leak, tie lifetime to the test
- select {
- case b.writeSync <- struct{}{}:
- case <-b.ctx.Done():
- return
- }
- }
-}
-
-func (b *bufReader) contents() []byte {
- b.mu.Lock()
- defer b.mu.Unlock()
- return b.data
-}
-
-func (b *bufReader) reset() {
- b.mu.Lock()
- defer b.mu.Unlock()
- b.data = nil
-}
-
-func (b *bufReader) error() error {
- b.mu.Lock()
- defer b.mu.Unlock()
- return b.err
-}
diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go
index 1fd2dd7f91..3d27c4796a 100644
--- a/internal/tracing/attributes.go
+++ b/internal/tracing/attributes.go
@@ -17,11 +17,13 @@
package tracing
import (
+ "context"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
"strings"
"time"
- "github.com/docker/compose/v2/pkg/utils"
-
"github.com/compose-spec/compose-go/v2/types"
moby "github.com/docker/docker/api/types"
"go.opentelemetry.io/otel/attribute"
@@ -32,6 +34,14 @@ import (
// downstream functions that accept slices of trace.SpanStartOption and trace.EventOption.
type SpanOptions []trace.SpanStartEventOption
+type MetricsKey struct{}
+
+type Metrics struct {
+ CountExtends int
+ CountIncludesLocal int
+ CountIncludesRemote int
+}
+
func (s SpanOptions) SpanStartOptions() []trace.SpanStartOption {
out := make([]trace.SpanStartOption, len(s))
for i := range s {
@@ -53,24 +63,37 @@ func (s SpanOptions) EventOptions() []trace.EventOption {
// For convenience, it's returned as a SpanOptions object to allow it to be
// passed directly to the wrapping helper methods in this package such as
// SpanWrapFunc.
-func ProjectOptions(proj *types.Project) SpanOptions {
+func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions {
if proj == nil {
return nil
}
-
+ capabilities, gpu, tpu := proj.ServicesWithCapabilities()
attrs := []attribute.KeyValue{
attribute.String("project.name", proj.Name),
attribute.String("project.dir", proj.WorkingDir),
attribute.StringSlice("project.compose_files", proj.ComposeFiles),
- attribute.StringSlice("project.services.active", proj.ServiceNames()),
- attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
attribute.StringSlice("project.profiles", proj.Profiles),
attribute.StringSlice("project.volumes", proj.VolumeNames()),
attribute.StringSlice("project.networks", proj.NetworkNames()),
attribute.StringSlice("project.secrets", proj.SecretNames()),
attribute.StringSlice("project.configs", proj.ConfigNames()),
attribute.StringSlice("project.extensions", keys(proj.Extensions)),
- attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)),
+ attribute.StringSlice("project.services.active", proj.ServiceNames()),
+ attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
+ attribute.StringSlice("project.services.build", proj.ServicesWithBuild()),
+ attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()),
+ attribute.StringSlice("project.services.capabilities", capabilities),
+ attribute.StringSlice("project.services.capabilities.gpu", gpu),
+ attribute.StringSlice("project.services.capabilities.tpu", tpu),
+ }
+ if metrics, ok := ctx.Value(MetricsKey{}).(Metrics); ok {
+ attrs = append(attrs, attribute.Int("project.services.extends", metrics.CountExtends))
+ attrs = append(attrs, attribute.Int("project.includes.local", metrics.CountIncludesLocal))
+ attrs = append(attrs, attribute.Int("project.includes.remote", metrics.CountIncludesRemote))
+ }
+
+ if projHash, ok := projectHash(proj); ok {
+ attrs = append(attrs, attribute.String("project.hash", projHash))
}
return []trace.SpanStartEventOption{
trace.WithAttributes(attrs...),
@@ -149,12 +172,22 @@ func unixTimeAttr(key string, value int64) attribute.KeyValue {
return timeAttr(key, time.Unix(value, 0).UTC())
}
-func flattenIncludeReferences(includeRefs map[string][]types.IncludeConfig) []string {
- ret := utils.NewSet[string]()
- for _, included := range includeRefs {
- for i := range included {
- ret.AddAll(included[i].Path...)
- }
+// projectHash returns a checksum from the JSON encoding of the project.
+func projectHash(p *types.Project) (string, bool) {
+ if p == nil {
+ return "", false
+ }
+ // disabled services aren't included in the output, so make a copy with
+ // all the services active for hashing
+ var err error
+ p, err = p.WithServicesEnabled(append(p.ServiceNames(), p.DisabledServiceNames()...)...)
+ if err != nil {
+ return "", false
+ }
+ projData, err := json.Marshal(p)
+ if err != nil {
+ return "", false
}
- return ret.Elements()
+ d := sha256.Sum256(projData)
+ return fmt.Sprintf("%x", d), true
}
diff --git a/internal/tracing/attributes_test.go b/internal/tracing/attributes_test.go
new file mode 100644
index 0000000000..d4277a940a
--- /dev/null
+++ b/internal/tracing/attributes_test.go
@@ -0,0 +1,67 @@
+/*
+ Copyright 2024 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 tracing
+
+import (
+ "testing"
+
+ "github.com/compose-spec/compose-go/v2/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestProjectHash(t *testing.T) {
+ projA := &types.Project{
+ Name: "fake-proj",
+ WorkingDir: "/tmp",
+ Services: map[string]types.ServiceConfig{
+ "foo": {Image: "fake-image"},
+ },
+ DisabledServices: map[string]types.ServiceConfig{
+ "bar": {Image: "diff-image"},
+ },
+ }
+ projB := &types.Project{
+ Name: "fake-proj",
+ WorkingDir: "/tmp",
+ Services: map[string]types.ServiceConfig{
+ "foo": {Image: "fake-image"},
+ "bar": {Image: "diff-image"},
+ },
+ }
+ projC := &types.Project{
+ Name: "fake-proj",
+ WorkingDir: "/tmp",
+ Services: map[string]types.ServiceConfig{
+ "foo": {Image: "fake-image"},
+ "bar": {Image: "diff-image"},
+ "baz": {Image: "yet-another-image"},
+ },
+ }
+
+ hashA, ok := projectHash(projA)
+ require.True(t, ok)
+ require.NotEmpty(t, hashA)
+ hashB, ok := projectHash(projB)
+ require.True(t, ok)
+ require.NotEmpty(t, hashB)
+ require.Equal(t, hashA, hashB)
+
+ hashC, ok := projectHash(projC)
+ require.True(t, ok)
+ require.NotEmpty(t, hashC)
+ require.NotEqual(t, hashC, hashA)
+}
diff --git a/internal/tracing/docker_context.go b/internal/tracing/docker_context.go
index f5f5ece3f7..229e77477d 100644
--- a/internal/tracing/docker_context.go
+++ b/internal/tracing/docker_context.go
@@ -24,6 +24,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/store"
+ "github.com/docker/compose/v2/internal/memnet"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"google.golang.org/grpc"
@@ -67,7 +68,9 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr
conn, err := grpc.DialContext(
dialCtx,
cfg.Endpoint,
- grpc.WithContextDialer(DialInMemory),
+ grpc.WithContextDialer(memnet.DialEndpoint),
+ // this dial is restricted to using a local Unix socket / named pipe,
+ // so there is no need for TLS
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 3b8f091611..641840a3e5 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -19,6 +19,7 @@ package api
import (
"context"
"fmt"
+ "io"
"strings"
"time"
@@ -52,8 +53,6 @@ type Service interface {
Ps(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error)
// List executes the equivalent to a `docker stack ls`
List(ctx context.Context, options ListOptions) ([]Stack, error)
- // Config executes the equivalent to a `compose config`
- Config(ctx context.Context, project *types.Project, options ConfigOptions) ([]byte, error)
// Kill executes the equivalent to a `compose kill`
Kill(ctx context.Context, projectName string, options KillOptions) error
// RunOneOffContainer creates a service oneoff container and starts its dependencies
@@ -116,9 +115,13 @@ type VizOptions struct {
Indentation string
}
+// WatchLogger is a reserved name to log watch events
+const WatchLogger = "#watch"
+
// WatchOptions group options of the Watch API
type WatchOptions struct {
- Build BuildOptions
+ Build *BuildOptions
+ LogTo LogConsumer
}
// BuildOptions group options of the Build API
@@ -145,6 +148,8 @@ type BuildOptions struct {
Memory int64
// Builder name passed in the command line
Builder string
+ // OutPrinter used for printing the progress output
+ OutPrinter io.Writer
}
// Apply mutates project according to build options
@@ -216,6 +221,7 @@ type StartOptions struct {
WaitTimeout time.Duration
// Services passed in the command line to be started
Services []string
+ Watch bool
}
// RestartOptions group options of the Restart API
diff --git a/pkg/compose/build.go b/pkg/compose/build.go
index b4ce4c80aa..a6ce19a12d 100644
--- a/pkg/compose/build.go
+++ b/pkg/compose/build.go
@@ -20,12 +20,14 @@ import (
"context"
"errors"
"fmt"
+ "io"
"os"
"path/filepath"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/compose-spec/compose-go/v2/types"
+ "github.com/containerd/console"
"github.com/containerd/containerd/platforms"
"github.com/docker/buildx/build"
"github.com/docker/buildx/builder"
@@ -127,7 +129,15 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
progressCtx, cancel := context.WithCancel(context.Background())
defer cancel()
- w, err = xprogress.NewPrinter(progressCtx, os.Stdout, progressui.DisplayMode(options.Progress),
+ var outPrinter io.Writer = os.Stdout
+ if options.OutPrinter != nil {
+ outPrinter = options.OutPrinter
+ }
+
+ if options.Quiet {
+ options.Progress = progress.ModeQuiet
+ }
+ w, err = xprogress.NewPrinter(progressCtx, progressPrinter(outPrinter), progressui.DisplayMode(options.Progress),
xprogress.WithDesc(
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
fmt.Sprintf("%s:%s", b.Driver, b.Name),
@@ -170,7 +180,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
}
if options.Memory != 0 {
- fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored.")
+ fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored")
}
buildOptions, err := s.toBuildOptions(project, service, options)
@@ -221,7 +231,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
return err
}
- err = tracing.SpanWrapFunc("project/pull", tracing.ProjectOptions(project),
+ err = tracing.SpanWrapFunc("project/pull", tracing.ProjectOptions(ctx, project),
func(ctx context.Context) error {
return s.pullRequiredImages(ctx, project, images, quietPull)
},
@@ -231,7 +241,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
}
if buildOpts != nil {
- err = tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(project),
+ err = tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(ctx, project),
func(ctx context.Context) error {
builtImages, err := s.build(ctx, project, *buildOpts, images)
if err != nil {
@@ -564,3 +574,29 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) {
return ret, nil
}
+
+type pPrinter struct {
+ io.Writer
+}
+
+func (p *pPrinter) Read(_ []byte) (n int, err error) {
+ return 0, errors.New("not implemented")
+}
+
+func (p *pPrinter) Close() error {
+ return nil
+}
+
+func (p *pPrinter) Fd() uintptr {
+ return 0
+}
+
+func (p *pPrinter) Name() string {
+ return "pPrinter"
+}
+
+func progressPrinter(w io.Writer) console.File {
+ return &pPrinter{
+ Writer: w,
+ }
+}
diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go
index 9230e8091c..fa631bfadb 100644
--- a/pkg/compose/compose.go
+++ b/pkg/compose/compose.go
@@ -18,6 +18,7 @@ package compose
import (
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -25,12 +26,11 @@ import (
"strings"
"sync"
- "github.com/jonboulle/clockwork"
-
+ "github.com/docker/compose/v2/internal/desktop"
"github.com/docker/docker/api/types/volume"
+ "github.com/jonboulle/clockwork"
"github.com/compose-spec/compose-go/v2/types"
- "github.com/distribution/reference"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/flags"
@@ -40,7 +40,6 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
- "github.com/opencontainers/go-digest"
)
var stdioToStdout bool
@@ -63,12 +62,29 @@ func NewComposeService(dockerCli command.Cli) api.Service {
}
type composeService struct {
- dockerCli command.Cli
+ dockerCli command.Cli
+ desktopCli *desktop.Client
+
clock clockwork.Clock
maxConcurrency int
dryRun bool
}
+// Close releases any connections/resources held by the underlying clients.
+//
+// In practice, this service has the same lifetime as the process, so everything
+// will get cleaned up at about the same time regardless even if not invoked.
+func (s *composeService) Close() error {
+ var errs []error
+ if s.dockerCli != nil {
+ errs = append(errs, s.dockerCli.Client().Close())
+ }
+ if s.desktopCli != nil {
+ errs = append(errs, s.desktopCli.Close())
+ }
+ return errors.Join(errs...)
+}
+
func (s *composeService) apiClient() client.APIClient {
return s.dockerCli.Client()
}
@@ -132,7 +148,8 @@ func getCanonicalContainerName(c moby.Container) string {
return name[1:]
}
}
- return c.Names[0][1:]
+
+ return strings.TrimPrefix(c.Names[0], "/")
}
func getContainerNameWithoutProject(c moby.Container) string {
@@ -146,35 +163,6 @@ func getContainerNameWithoutProject(c moby.Container) string {
return name[len(project)+1:]
}
-func (s *composeService) Config(ctx context.Context, project *types.Project, options api.ConfigOptions) ([]byte, error) {
- if options.ResolveImageDigests {
- var err error
- project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) {
- auth, err := encodedAuth(named, s.configFile())
- if err != nil {
- return "", err
- }
- inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
- if err != nil {
- return "", err
- }
- return inspect.Descriptor.Digest, nil
- })
- if err != nil {
- return nil, err
- }
- }
-
- switch options.Format {
- case "json":
- return project.MarshalJSON()
- case "yaml":
- return project.MarshalYAML()
- default:
- return nil, fmt.Errorf("unsupported format %q", options.Format)
- }
-}
-
// projectFromName builds a types.Project based on actual resources with compose labels set
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
project := &types.Project{
@@ -311,11 +299,13 @@ func (s *composeService) isSWarmEnabled(ctx context.Context) (bool, error) {
return swarmEnabled.val, swarmEnabled.err
}
-var runtimeVersion = struct {
+type runtimeVersionCache struct {
once sync.Once
val string
err error
-}{}
+}
+
+var runtimeVersion runtimeVersionCache
func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) {
runtimeVersion.once.Do(func() {
diff --git a/pkg/compose/containers.go b/pkg/compose/containers.go
index ce17228926..cd75eeff56 100644
--- a/pkg/compose/containers.go
+++ b/pkg/compose/containers.go
@@ -127,13 +127,7 @@ func isNotService(services ...string) containerPredicate {
// isOrphaned is a predicate to select containers without a matching service definition in compose project
func isOrphaned(project *types.Project) containerPredicate {
- var services []string
- for _, s := range project.Services {
- services = append(services, s.Name)
- }
- for _, s := range project.DisabledServices {
- services = append(services, s.Name)
- }
+ services := append(project.ServiceNames(), project.DisabledServiceNames()...)
return func(c moby.Container) bool {
service := c.Labels[api.ServiceLabel]
return !utils.StringContains(services, service)
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index f65247b086..6fb6f3e3c9 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -34,6 +34,7 @@ import (
"github.com/docker/compose/v2/internal/tracing"
moby "github.com/docker/docker/api/types"
containerType "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/api/types/versions"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
@@ -126,6 +127,24 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
}
sort.Slice(containers, func(i, j int) bool {
+ // select obsolete containers first, so they get removed as we scale down
+ if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete {
+ // i is obsolete, so must be first in the list
+ return true
+ }
+ if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete {
+ // j is obsolete, so must be first in the list
+ return false
+ }
+
+ // For up-to-date containers, sort by container number to preserve low-values in container numbers
+ ni, erri := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel])
+ nj, errj := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel])
+ if erri == nil && errj == nil {
+ return ni < nj
+ }
+
+ // If we don't get a container number (?) just sort by creation date
return containers[i].Created < containers[j].Created
})
for i, container := range containers {
@@ -600,19 +619,27 @@ func (s *composeService) createMobyContainer(ctx context.Context,
},
}
- // the highest-priority network is the primary and is included in the ContainerCreate API
- // call via container.NetworkMode & network.NetworkingConfig
- // any remaining networks are connected one-by-one here after creation (but before start)
- serviceNetworks := service.NetworksByPriority()
- for _, networkKey := range serviceNetworks {
- mobyNetworkName := project.Networks[networkKey].Name
- if string(cfgs.Host.NetworkMode) == mobyNetworkName {
- // primary network already configured as part of ContainerCreate
- continue
- }
- epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
- if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
- return created, err
+ apiVersion, err := s.RuntimeVersion(ctx)
+ if err != nil {
+ return created, err
+ }
+ // Starting API version 1.44, the ContainerCreate API call takes multiple networks
+ // so we include all the configurations there and can skip the one-by-one calls here
+ if versions.LessThan(apiVersion, "1.44") {
+ // the highest-priority network is the primary and is included in the ContainerCreate API
+ // call via container.NetworkMode & network.NetworkingConfig
+ // any remaining networks are connected one-by-one here after creation (but before start)
+ serviceNetworks := service.NetworksByPriority()
+ for _, networkKey := range serviceNetworks {
+ mobyNetworkName := project.Networks[networkKey].Name
+ if string(cfgs.Host.NetworkMode) == mobyNetworkName {
+ // primary network already configured as part of ContainerCreate
+ continue
+ }
+ epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
+ if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
+ return created, err
+ }
}
}
diff --git a/pkg/compose/convergence_test.go b/pkg/compose/convergence_test.go
index 32917cb661..e25ccd9f64 100644
--- a/pkg/compose/convergence_test.go
+++ b/pkg/compose/convergence_test.go
@@ -23,14 +23,18 @@ import (
"testing"
"github.com/compose-spec/compose-go/v2/types"
+ "github.com/docker/cli/cli/config/configfile"
moby "github.com/docker/docker/api/types"
containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/network"
+ "github.com/docker/go-connections/nat"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
+ "github.com/docker/compose/v2/pkg/progress"
)
func TestContainerName(t *testing.T) {
@@ -251,3 +255,182 @@ func TestWaitDependencies(t *testing.T) {
assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
})
}
+
+func TestCreateMobyContainer(t *testing.T) {
+ t.Run("connects container networks one by one if API <1.44", func(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+ apiClient := mocks.NewMockAPIClient(mockCtrl)
+ cli := mocks.NewMockCli(mockCtrl)
+ tested := composeService{
+ dockerCli: cli,
+ }
+ cli.EXPECT().Client().Return(apiClient).AnyTimes()
+ cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
+ apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
+ apiClient.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(moby.ImageInspect{}, nil, nil).AnyTimes()
+ // force `RuntimeVersion` to fetch again
+ runtimeVersion = runtimeVersionCache{}
+ apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{
+ APIVersion: "1.43",
+ }, nil).AnyTimes()
+
+ service := types.ServiceConfig{
+ Name: "test",
+ Networks: map[string]*types.ServiceNetworkConfig{
+ "a": {
+ Priority: 10,
+ },
+ "b": {
+ Priority: 100,
+ },
+ },
+ }
+ project := types.Project{
+ Name: "bork",
+ Services: types.Services{
+ "test": service,
+ },
+ Networks: types.Networks{
+ "a": types.NetworkConfig{
+ Name: "a-moby-name",
+ },
+ "b": types.NetworkConfig{
+ Name: "b-moby-name",
+ },
+ },
+ }
+
+ var falseBool bool
+ apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
+ &containerType.HostConfig{
+ PortBindings: nat.PortMap{},
+ ExtraHosts: []string{},
+ Tmpfs: map[string]string{},
+ Resources: containerType.Resources{
+ OomKillDisable: &falseBool,
+ },
+ NetworkMode: "b-moby-name",
+ }), gomock.Eq(
+ &network.NetworkingConfig{
+ EndpointsConfig: map[string]*network.EndpointSettings{
+ "b-moby-name": {
+ IPAMConfig: &network.EndpointIPAMConfig{},
+ Aliases: []string{"bork-test-0"},
+ },
+ },
+ }), gomock.Any(), gomock.Any()).Times(1).Return(
+ containerType.CreateResponse{
+ ID: "an-id",
+ }, nil)
+
+ apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return(
+ moby.ContainerJSON{
+ ContainerJSONBase: &moby.ContainerJSONBase{
+ ID: "an-id",
+ Name: "a-name",
+ },
+ Config: &containerType.Config{},
+ NetworkSettings: &moby.NetworkSettings{},
+ }, nil)
+
+ apiClient.EXPECT().NetworkConnect(gomock.Any(), "a-moby-name", "an-id", gomock.Eq(
+ &network.EndpointSettings{
+ IPAMConfig: &network.EndpointIPAMConfig{},
+ Aliases: []string{"bork-test-0"},
+ }))
+
+ _, err := tested.createMobyContainer(context.Background(), &project, service, "test", 0, nil, createOptions{
+ Labels: make(types.Labels),
+ }, progress.ContextWriter(context.TODO()))
+ assert.NilError(t, err)
+ })
+
+ t.Run("includes all container networks in ContainerCreate call if API >=1.44", func(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+ apiClient := mocks.NewMockAPIClient(mockCtrl)
+ cli := mocks.NewMockCli(mockCtrl)
+ tested := composeService{
+ dockerCli: cli,
+ }
+ cli.EXPECT().Client().Return(apiClient).AnyTimes()
+ cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
+ apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
+ apiClient.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(moby.ImageInspect{}, nil, nil).AnyTimes()
+ // force `RuntimeVersion` to fetch fresh version
+ runtimeVersion = runtimeVersionCache{}
+ apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{
+ APIVersion: "1.44",
+ }, nil).AnyTimes()
+
+ service := types.ServiceConfig{
+ Name: "test",
+ Networks: map[string]*types.ServiceNetworkConfig{
+ "a": {
+ Priority: 10,
+ },
+ "b": {
+ Priority: 100,
+ },
+ },
+ }
+ project := types.Project{
+ Name: "bork",
+ Services: types.Services{
+ "test": service,
+ },
+ Networks: types.Networks{
+ "a": types.NetworkConfig{
+ Name: "a-moby-name",
+ },
+ "b": types.NetworkConfig{
+ Name: "b-moby-name",
+ },
+ },
+ }
+
+ var falseBool bool
+ apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq(
+ &containerType.HostConfig{
+ PortBindings: nat.PortMap{},
+ ExtraHosts: []string{},
+ Tmpfs: map[string]string{},
+ Resources: containerType.Resources{
+ OomKillDisable: &falseBool,
+ },
+ NetworkMode: "b-moby-name",
+ }), gomock.Eq(
+ &network.NetworkingConfig{
+ EndpointsConfig: map[string]*network.EndpointSettings{
+ "a-moby-name": {
+ IPAMConfig: &network.EndpointIPAMConfig{},
+ Aliases: []string{"bork-test-0"},
+ },
+ "b-moby-name": {
+ IPAMConfig: &network.EndpointIPAMConfig{},
+ Aliases: []string{"bork-test-0"},
+ },
+ },
+ }), gomock.Any(), gomock.Any()).Times(1).Return(
+ containerType.CreateResponse{
+ ID: "an-id",
+ }, nil)
+
+ apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return(
+ moby.ContainerJSON{
+ ContainerJSONBase: &moby.ContainerJSONBase{
+ ID: "an-id",
+ Name: "a-name",
+ },
+ Config: &containerType.Config{},
+ NetworkSettings: &moby.NetworkSettings{},
+ }, nil)
+
+ _, err := tested.createMobyContainer(context.Background(), &project, service, "test", 0, nil, createOptions{
+ Labels: make(types.Labels),
+ }, progress.ContextWriter(context.TODO()))
+ assert.NilError(t, err)
+ })
+
+}
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index 069db7cfc7..c034b14662 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -235,7 +235,11 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
if err != nil {
return createConfigs{}, err
}
- networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases)
+ apiVersion, err := s.RuntimeVersion(ctx)
+ if err != nil {
+ return createConfigs{}, err
+ }
+ networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases, apiVersion)
portBindings := buildContainerPortBindingOptions(service)
// MISC
@@ -456,6 +460,7 @@ func defaultNetworkSettings(
serviceIndex int,
links []string,
useNetworkAliases bool,
+ version string,
) (container.NetworkMode, *network.NetworkingConfig) {
if service.NetworkMode != "" {
return container.NetworkMode(service.NetworkMode), nil
@@ -465,23 +470,38 @@ func defaultNetworkSettings(
return "none", nil
}
- var networkKey string
+ var primaryNetworkKey string
if len(service.Networks) > 0 {
- networkKey = service.NetworksByPriority()[0]
+ primaryNetworkKey = service.NetworksByPriority()[0]
} else {
- networkKey = "default"
+ primaryNetworkKey = "default"
+ }
+ primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name
+ endpointsConfig := map[string]*network.EndpointSettings{
+ primaryNetworkMobyNetworkName: createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases),
+ }
+
+ // Starting from API version 1.44, the Engine will take several EndpointsConfigs
+ // so we can pass all the extra networks we want the container to be connected to
+ // in the network configuration instead of connecting the container to each extra
+ // network individually after creation.
+ if versions.GreaterThanOrEqualTo(version, "1.44") && len(service.Networks) > 1 {
+ serviceNetworks := service.NetworksByPriority()
+ for _, networkKey := range serviceNetworks[1:] {
+ mobyNetworkName := project.Networks[networkKey].Name
+ epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
+ endpointsConfig[mobyNetworkName] = epSettings
+ }
}
- mobyNetworkName := project.Networks[networkKey].Name
- epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
+
networkConfig := &network.NetworkingConfig{
- EndpointsConfig: map[string]*network.EndpointSettings{
- mobyNetworkName: epSettings,
- },
+ EndpointsConfig: endpointsConfig,
}
+
// From the Engine API docs:
// > Supported standard values are: bridge, host, none, and container:.
// > Any other value is taken as a custom network's name to which this container should connect to.
- return container.NetworkMode(mobyNetworkName), networkConfig
+ return container.NetworkMode(primaryNetworkMobyNetworkName), networkConfig
}
func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go
index 1a47348960..7991d09e8a 100644
--- a/pkg/compose/create_test.go
+++ b/pkg/compose/create_test.go
@@ -193,7 +193,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
- networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+ networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
assert.Equal(t, string(networkMode), "myProject_myNetwork2")
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2"))
@@ -221,7 +221,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
- networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+ networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
assert.Equal(t, string(networkMode), "myProject_default")
assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_default"))
@@ -238,7 +238,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
},
}
- networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+ networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
assert.Equal(t, string(networkMode), "none")
assert.Check(t, cmp.Nil(networkConfig))
})
@@ -258,7 +258,7 @@ func TestDefaultNetworkSettings(t *testing.T) {
}),
}
- networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+ networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43")
assert.Equal(t, string(networkMode), "host")
assert.Check(t, cmp.Nil(networkConfig))
})
diff --git a/pkg/compose/desktop.go b/pkg/compose/desktop.go
new file mode 100644
index 0000000000..9af977fde2
--- /dev/null
+++ b/pkg/compose/desktop.go
@@ -0,0 +1,77 @@
+/*
+ Copyright 2024 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"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/docker/compose/v2/internal/desktop"
+ "github.com/sirupsen/logrus"
+)
+
+// engineLabelDesktopAddress is used to detect that Compose is running with a
+// Docker Desktop context. When this label is present, the value is an endpoint
+// address for an in-memory socket (AF_UNIX or named pipe).
+const engineLabelDesktopAddress = "com.docker.desktop.address"
+
+var _ desktop.IntegrationService = &composeService{}
+
+// MaybeEnableDesktopIntegration initializes the desktop.Client instance if
+// the server info from the Docker Engine is a Docker Desktop instance.
+//
+// EXPERIMENTAL: Requires `COMPOSE_EXPERIMENTAL_DESKTOP=1` env var set.
+func (s *composeService) MaybeEnableDesktopIntegration(ctx context.Context) error {
+ if desktopEnabled, _ := strconv.ParseBool(os.Getenv("COMPOSE_EXPERIMENTAL_DESKTOP")); !desktopEnabled {
+ return nil
+ }
+
+ if s.dryRun {
+ return nil
+ }
+
+ // safeguard to make sure this doesn't get stuck indefinitely
+ ctx, cancel := context.WithTimeout(ctx, time.Second)
+ defer cancel()
+
+ info, err := s.dockerCli.Client().Info(ctx)
+ if err != nil {
+ return fmt.Errorf("querying server info: %w", err)
+ }
+ for _, l := range info.Labels {
+ k, v, ok := strings.Cut(l, "=")
+ if !ok || k != engineLabelDesktopAddress {
+ continue
+ }
+
+ desktopCli := desktop.NewClient(v)
+ _, err := desktopCli.Ping(ctx)
+ if err != nil {
+ return fmt.Errorf("pinging Desktop API: %w", err)
+ }
+ logrus.Debugf("Enabling Docker Desktop integration (experimental): %s", v)
+ s.desktopCli = desktopCli
+ return nil
+ }
+
+ logrus.Trace("Docker Desktop not detected, no integration enabled")
+ return nil
+}
diff --git a/pkg/compose/printer.go b/pkg/compose/printer.go
index dea72e27a6..45b0f7914d 100644
--- a/pkg/compose/printer.go
+++ b/pkg/compose/printer.go
@@ -32,18 +32,19 @@ type logPrinter interface {
}
type printer struct {
- sync.Mutex
queue chan api.ContainerEvent
consumer api.LogConsumer
- stopped bool
+ stopCh chan struct{} // stopCh is a signal channel for producers to stop sending events to the queue
+ stop sync.Once
}
// newLogPrinter builds a LogPrinter passing containers logs to LogConsumer
func newLogPrinter(consumer api.LogConsumer) logPrinter {
- queue := make(chan api.ContainerEvent)
printer := printer{
consumer: consumer,
- queue: queue,
+ queue: make(chan api.ContainerEvent),
+ stopCh: make(chan struct{}),
+ stop: sync.Once{},
}
return &printer
}
@@ -54,24 +55,27 @@ func (p *printer) Cancel() {
}
func (p *printer) Stop() {
- p.Lock()
- defer p.Unlock()
- if !p.stopped {
- // only close if this is the first call to stop
- p.stopped = true
- close(p.queue)
- }
+ p.stop.Do(func() {
+ close(p.stopCh)
+ for {
+ select {
+ case <-p.queue:
+ // purge the queue to free producers goroutines
+ // p.queue will be garbage collected
+ default:
+ return
+ }
+ }
+ })
}
func (p *printer) HandleEvent(event api.ContainerEvent) {
- p.Lock()
- defer p.Unlock()
- if p.stopped {
- // prevent deadlocking, if the printer is done, there's no reader for
- // queue, so this write could block indefinitely
+ select {
+ case <-p.stopCh:
return
+ default:
+ p.queue <- event
}
- p.queue <- event
}
//nolint:gocyclo
@@ -80,58 +84,67 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
aborting bool
exitCode int
)
- containers := map[string]struct{}{}
- for event := range p.queue {
- container, id := event.Container, event.ID
- switch event.Type {
- case api.UserCancel:
- aborting = true
- case api.ContainerEventAttach:
- if _, ok := containers[id]; ok {
- continue
- }
- containers[id] = struct{}{}
- p.consumer.Register(container)
- case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
- if !event.Restarting {
- delete(containers, id)
- }
- if !aborting {
- p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
- if event.Type == api.ContainerEventRecreated {
- p.consumer.Status(container, "has been recreated")
+ defer p.Stop()
+
+ // containers we are tracking. Use true when container is running, false after we receive a stop|die signal
+ containers := map[string]bool{}
+ for {
+ select {
+ case <-p.stopCh:
+ return exitCode, nil
+ case event := <-p.queue:
+ container, id := event.Container, event.ID
+ switch event.Type {
+ case api.UserCancel:
+ aborting = true
+ case api.ContainerEventAttach:
+ if _, ok := containers[id]; ok {
+ continue
}
- }
- if cascadeStop {
- if !aborting {
- aborting = true
- err := stopFn()
- if err != nil {
- return 0, err
+ containers[id] = true
+ p.consumer.Register(container)
+ case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
+ if !aborting && containers[id] {
+ p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
+ if event.Type == api.ContainerEventRecreated {
+ p.consumer.Status(container, "has been recreated")
}
}
- if event.Type == api.ContainerEventExit {
- if exitCodeFrom == "" {
- exitCodeFrom = event.Service
+ containers[id] = false
+ if !event.Restarting {
+ delete(containers, id)
+ }
+
+ if cascadeStop {
+ if !aborting {
+ aborting = true
+ err := stopFn()
+ if err != nil {
+ return 0, err
+ }
}
- if exitCodeFrom == event.Service {
- exitCode = event.ExitCode
+ if event.Type == api.ContainerEventExit {
+ if exitCodeFrom == "" {
+ exitCodeFrom = event.Service
+ }
+ if exitCodeFrom == event.Service {
+ exitCode = event.ExitCode
+ }
}
}
- }
- if len(containers) == 0 {
- // Last container terminated, done
- return exitCode, nil
- }
- case api.ContainerEventLog:
- if !aborting {
- p.consumer.Log(container, event.Line)
- }
- case api.ContainerEventErr:
- if !aborting {
- p.consumer.Err(container, event.Line)
+ if len(containers) == 0 {
+ // Last container terminated, done
+ return exitCode, nil
+ }
+ case api.ContainerEventLog:
+ if !aborting {
+ p.consumer.Log(container, event.Line)
+ }
+ case api.ContainerEventErr:
+ if !aborting {
+ p.consumer.Err(container, event.Line)
+ }
}
}
}
- return exitCode, nil
}
diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go
index 406da5ec2b..a01d7be584 100644
--- a/pkg/compose/publish.go
+++ b/pkg/compose/publish.go
@@ -26,7 +26,6 @@ import (
"github.com/docker/compose/v2/internal/ocipush"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
- "github.com/opencontainers/go-digest"
)
func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error {
@@ -111,17 +110,7 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
if err != nil {
return nil, err
}
- project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) {
- auth, err := encodedAuth(named, s.configFile())
- if err != nil {
- return "", err
- }
- inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth)
- if err != nil {
- return "", err
- }
- return inspect.Descriptor.Digest, nil
- })
+ project, err = project.WithImagesResolved(ImageDigestResolver(ctx, s.configFile(), s.apiClient()))
if err != nil {
return nil, err
}
diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go
index 977397bd09..222876d657 100644
--- a/pkg/compose/pull.go
+++ b/pkg/compose/pull.go
@@ -28,10 +28,13 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/distribution/reference"
"github.com/docker/buildx/driver"
+ "github.com/docker/cli/cli/config/configfile"
moby "github.com/docker/docker/api/types"
+ "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/hashicorp/go-multierror"
+ "github.com/opencontainers/go-digest"
"golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/pkg/api"
@@ -242,6 +245,21 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
return inspected.ID, nil
}
+// ImageDigestResolver creates a func able to resolve image digest from a docker ref,
+func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiClient client.APIClient) func(named reference.Named) (digest.Digest, error) {
+ return func(named reference.Named) (digest.Digest, error) {
+ auth, err := encodedAuth(named, file)
+ if err != nil {
+ return "", err
+ }
+ inspect, err := apiClient.DistributionInspect(ctx, named.String(), auth)
+ if err != nil {
+ return "", err
+ }
+ return inspect.Descriptor.Digest, nil
+ }
+}
+
func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
diff --git a/pkg/compose/scale.go b/pkg/compose/scale.go
index 1e869ea726..985cc129d1 100644
--- a/pkg/compose/scale.go
+++ b/pkg/compose/scale.go
@@ -25,7 +25,7 @@ import (
)
func (s *composeService) Scale(ctx context.Context, project *types.Project, options api.ScaleOptions) error {
- return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(project), func(ctx context.Context) error {
+ return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
err := s.create(ctx, project, api.CreateOptions{Services: options.Services})
if err != nil {
return err
diff --git a/pkg/compose/up.go b/pkg/compose/up.go
index be6bbdb149..ab22de48b7 100644
--- a/pkg/compose/up.go
+++ b/pkg/compose/up.go
@@ -32,7 +32,7 @@ import (
)
func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo
- err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(project), func(ctx context.Context) error {
+ err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error {
w := progress.ContextWriter(ctx)
w.HasMore(options.Start.Attach == nil)
err := s.create(ctx, project, options.Create)
@@ -125,6 +125,17 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return err
})
+ if options.Start.Watch {
+ eg.Go(func() error {
+ buildOpts := *options.Create.Build
+ buildOpts.Quiet = true
+ return s.Watch(ctx, project, options.Start.Services, api.WatchOptions{
+ Build: &buildOpts,
+ LogTo: options.Start.Attach,
+ })
+ })
+ }
+
// We don't use parent (cancelable) context as we manage sigterm to stop the stack
err = s.start(context.Background(), project.Name, options.Start, printer.HandleEvent)
if err != nil && !isTerminated { // Ignore error if the process is terminated
diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go
index 3673c43557..2a651baac1 100644
--- a/pkg/compose/watch.go
+++ b/pkg/compose/watch.go
@@ -46,21 +46,23 @@ type fileEvent struct {
Action types.WatchAction
}
-// getSyncImplementation returns the the tar-based syncer unless it has been explicitly
-// disabled with `COMPOSE_EXPERIMENTAL_WATCH_TAR=0`. Note that the absence of the env
-// var means enabled.
-func (s *composeService) getSyncImplementation(project *types.Project) sync.Syncer {
+// getSyncImplementation returns an appropriate sync implementation for the
+// project.
+//
+// Currently, an implementation that batches files and transfers them using
+// the Moby `Untar` API.
+func (s *composeService) getSyncImplementation(project *types.Project) (sync.Syncer, error) {
var useTar bool
if useTarEnv, ok := os.LookupEnv("COMPOSE_EXPERIMENTAL_WATCH_TAR"); ok {
useTar, _ = strconv.ParseBool(useTarEnv)
} else {
useTar = true
}
- if useTar {
- return sync.NewTar(project.Name, tarDockerClient{s: s})
+ if !useTar {
+ return nil, errors.New("no available sync implementation")
}
- return sync.NewDockerCopy(project.Name, s, s.stdinfo())
+ return sync.NewTar(project.Name, tarDockerClient{s: s}), nil
}
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo
@@ -68,9 +70,13 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
if project, err = project.WithSelectedServices(services); err != nil {
return err
}
- syncer := s.getSyncImplementation(project)
+ syncer, err := s.getSyncImplementation(project)
+ if err != nil {
+ return err
+ }
eg, ctx := errgroup.WithContext(ctx)
watching := false
+ options.LogTo.Register(api.WatchLogger)
for i := range project.Services {
service := project.Services[i]
config, err := loadDevelopmentConfig(service, project)
@@ -86,9 +92,15 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
continue
}
- if len(config.Watch) > 0 && service.Build == nil {
- // service configured with watchers but no build section
- return fmt.Errorf("can't watch service %q without a build context", service.Name)
+ for _, trigger := range config.Watch {
+ if trigger.Action == types.WatchActionRebuild {
+ if service.Build == nil {
+ return fmt.Errorf("can't watch service %q with action %s without a build context", service.Name, types.WatchActionRebuild)
+ }
+ if options.Build == nil {
+ return fmt.Errorf("--no-build is incompatible with watch action %s in service %s", types.WatchActionRebuild, service.Name)
+ }
+ }
}
if len(services) > 0 && service.Build == nil {
@@ -137,9 +149,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
return err
}
- fmt.Fprintf(
- s.stdinfo(),
- "Watch configuration for service %q:%s\n",
+ logrus.Debugf("Watch configuration for service %q:%s\n",
service.Name,
strings.Join(append([]string{""}, pathLogs...), "\n - "),
)
@@ -158,6 +168,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
if !watching {
return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section")
}
+ options.LogTo.Log(api.WatchLogger, "watch enabled")
return eg.Wait()
}
@@ -185,7 +196,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, name
case batch := <-batchEvents:
start := time.Now()
logrus.Debugf("batch start: service[%s] count[%d]", name, len(batch))
- if err := s.handleWatchBatch(ctx, project, name, options.Build, batch, syncer); err != nil {
+ if err := s.handleWatchBatch(ctx, project, name, options, batch, syncer); err != nil {
logrus.Warnf("Error handling changed files for service %s: %v", name, err)
}
logrus.Debugf("batch complete: service[%s] duration[%s] count[%d]",
@@ -426,32 +437,37 @@ func (t tarDockerClient) Untar(ctx context.Context, id string, archive io.ReadCl
})
}
-func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Project, serviceName string, build api.BuildOptions, batch []fileEvent, syncer sync.Syncer) error {
+func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Project, serviceName string, options api.WatchOptions, batch []fileEvent, syncer sync.Syncer) error {
pathMappings := make([]sync.PathMapping, len(batch))
restartService := false
for i := range batch {
if batch[i].Action == types.WatchActionRebuild {
- fmt.Fprintf(
- s.stdinfo(),
- "Rebuilding service %q after changes were detected:%s\n",
- serviceName,
- strings.Join(append([]string{""}, batch[i].HostPath), "\n - "),
- )
+ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service %q after changes were detected...", serviceName))
// restrict the build to ONLY this service, not any of its dependencies
- build.Services = []string{serviceName}
- err := s.Up(ctx, project, api.UpOptions{
- Create: api.CreateOptions{
- Build: &build,
- Services: []string{serviceName},
- Inherit: true,
- },
- Start: api.StartOptions{
- Services: []string{serviceName},
- Project: project,
- },
+ options.Build.Services = []string{serviceName}
+ _, err := s.build(ctx, project, *options.Build, nil)
+ if err != nil {
+ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Build failed. Error: %v", err))
+ return err
+ }
+ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service %q successfully built", serviceName))
+
+ err = s.create(ctx, project, api.CreateOptions{
+ Services: []string{serviceName},
+ Inherit: true,
+ Recreate: api.RecreateForce,
})
if err != nil {
- fmt.Fprintf(s.stderr(), "Application failed to start after update. Error: %v\n", err)
+ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate service after update. Error: %v", err))
+ return err
+ }
+
+ err = s.start(ctx, project.Name, api.StartOptions{
+ Project: project,
+ Services: []string{serviceName},
+ }, nil)
+ if err != nil {
+ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Application failed to start after update. Error: %v", err))
}
return nil
}
@@ -461,7 +477,7 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr
pathMappings[i] = batch[i].PathMapping
}
- writeWatchSyncMessage(s.stdinfo(), serviceName, pathMappings)
+ writeWatchSyncMessage(options.LogTo, serviceName, pathMappings)
service, err := project.GetService(serviceName)
if err != nil {
@@ -481,29 +497,19 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr
}
// writeWatchSyncMessage prints out a message about the sync for the changed paths.
-func writeWatchSyncMessage(w io.Writer, serviceName string, pathMappings []sync.PathMapping) {
+func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings []sync.PathMapping) {
const maxPathsToShow = 10
if len(pathMappings) <= maxPathsToShow || logrus.IsLevelEnabled(logrus.DebugLevel) {
hostPathsToSync := make([]string, len(pathMappings))
for i := range pathMappings {
hostPathsToSync[i] = pathMappings[i].HostPath
}
- fmt.Fprintf(
- w,
- "Syncing %q after changes were detected:%s\n",
- serviceName,
- strings.Join(append([]string{""}, hostPathsToSync...), "\n - "),
- )
+ log.Log(api.WatchLogger, fmt.Sprintf("Syncing %q after changes were detected", serviceName))
} else {
hostPathsToSync := make([]string, len(pathMappings))
for i := range pathMappings {
hostPathsToSync[i] = pathMappings[i].HostPath
}
- fmt.Fprintf(
- w,
- "Syncing service %q after %d changes were detected\n",
- serviceName,
- len(pathMappings),
- )
+ log.Log(api.WatchLogger, fmt.Sprintf("Syncing service %q after %d changes were detected", serviceName, len(pathMappings)))
}
}
diff --git a/pkg/compose/watch_test.go b/pkg/compose/watch_test.go
index fc39ee7d34..39fbf2bcb2 100644
--- a/pkg/compose/watch_test.go
+++ b/pkg/compose/watch_test.go
@@ -16,6 +16,7 @@ package compose
import (
"context"
+ "fmt"
"os"
"testing"
"time"
@@ -91,10 +92,29 @@ func (t testWatcher) Errors() chan error {
return t.errors
}
+type stdLogger struct{}
+
+func (s stdLogger) Log(containerName, message string) {
+ fmt.Printf("%s: %s\n", containerName, message)
+}
+
+func (s stdLogger) Err(containerName, message string) {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", containerName, message)
+}
+
+func (s stdLogger) Status(container, msg string) {
+ fmt.Printf("%s: %s\n", container, msg)
+}
+
+func (s stdLogger) Register(container string) {
+
+}
+
func TestWatch_Sync(t *testing.T) {
mockCtrl := gomock.NewController(t)
cli := mocks.NewMockCli(mockCtrl)
cli.EXPECT().Err().Return(os.Stderr).AnyTimes()
+ cli.EXPECT().BuildKitEnabled().Return(true, nil)
apiClient := mocks.NewMockAPIClient(mockCtrl)
apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return([]moby.Container{
testContainer("test", "123", false),
@@ -124,7 +144,10 @@ func TestWatch_Sync(t *testing.T) {
dockerCli: cli,
clock: clock,
}
- err := service.watch(ctx, &proj, "test", api.WatchOptions{}, watcher, syncer, []types.Trigger{
+ err := service.watch(ctx, &proj, "test", api.WatchOptions{
+ Build: &api.BuildOptions{},
+ LogTo: stdLogger{},
+ }, watcher, syncer, []types.Trigger{
{
Path: "/sync",
Action: "sync",
diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go
index 349ab9f4cb..65044519b5 100644
--- a/pkg/e2e/build_test.go
+++ b/pkg/e2e/build_test.go
@@ -306,7 +306,7 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) {
"-f", "fixtures/build-test/platforms/compose-unsupported-platform.yml", "build")
res.Assert(t, icmd.Expected{
ExitCode: 17,
- Err: "failed to solve: alpine: no match for platform in",
+ Err: "no match for platform in",
})
})
diff --git a/pkg/e2e/cancel_test.go b/pkg/e2e/cancel_test.go
index 6e34bd8d2d..a3f4b77cda 100644
--- a/pkg/e2e/cancel_test.go
+++ b/pkg/e2e/cancel_test.go
@@ -37,18 +37,26 @@ func TestComposeCancel(t *testing.T) {
c := NewParallelCLI(t)
t.Run("metrics on cancel Compose build", func(t *testing.T) {
- c.RunDockerComposeCmd(t, "ls")
- buildProjectPath := "fixtures/build-infinite/compose.yaml"
+ const buildProjectPath = "fixtures/build-infinite/compose.yaml"
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
// require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal.
// sending kill signal
- stdout := &utils.SafeBuffer{}
- stderr := &utils.SafeBuffer{}
- cmd, err := StartWithNewGroupID(context.Background(),
+ var stdout, stderr utils.SafeBuffer
+ cmd, err := StartWithNewGroupID(
+ ctx,
c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build", "--progress", "plain"),
- stdout,
- stderr)
+ &stdout,
+ &stderr,
+ )
assert.NilError(t, err)
+ processDone := make(chan error, 1)
+ go func() {
+ defer close(processDone)
+ processDone <- cmd.Wait()
+ }()
c.WaitForCondition(t, func() (bool, string) {
out := stdout.String()
@@ -58,15 +66,21 @@ func TestComposeCancel(t *testing.T) {
errors)
}, 30*time.Second, 1*time.Second)
- err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
+ // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
+ err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
assert.NilError(t, err)
- c.WaitForCondition(t, func() (bool, string) {
- out := stdout.String()
- errors := stderr.String()
- return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out,
- errors)
- }, 10*time.Second, 1*time.Second)
+ select {
+ case <-ctx.Done():
+ t.Fatal("test context canceled")
+ case err := <-processDone:
+ // TODO(milas): Compose should really not return exit code 130 here,
+ // this is an old hack for the compose-cli wrapper
+ assert.Error(t, err, "exit status 130",
+ "STDOUT:\n%s\nSTDERR:\n%s\n", stdout.String(), stderr.String())
+ case <-time.After(10 * time.Second):
+ t.Fatal("timeout waiting for Compose exit")
+ }
})
}
diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go
index b074e6a80e..ceec152a86 100644
--- a/pkg/e2e/compose_run_test.go
+++ b/pkg/e2e/compose_run_test.go
@@ -160,4 +160,13 @@ func TestLocalComposeRun(t *testing.T) {
c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "down", "--remove-orphans")
})
+
+ t.Run("--quiet-pull", func(t *testing.T) {
+ res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "down", "--rmi", "all")
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "run", "--quiet-pull", "backend")
+ assert.Assert(t, !strings.Contains(res.Combined(), "Pull complete"), res.Combined())
+ assert.Assert(t, strings.Contains(res.Combined(), "Pulled"), res.Combined())
+ })
}
diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go
index 6384c29c86..75d546f730 100644
--- a/pkg/e2e/compose_test.go
+++ b/pkg/e2e/compose_test.go
@@ -235,7 +235,7 @@ func TestCompatibility(t *testing.T) {
})
}
-func TestConvert(t *testing.T) {
+func TestConfig(t *testing.T) {
const projectName = "compose-e2e-convert"
c := NewParallelCLI(t)
@@ -244,7 +244,8 @@ func TestConvert(t *testing.T) {
t.Run("up", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert")
- res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
+ res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
+services:
nginx:
build:
context: %s
@@ -253,11 +254,12 @@ func TestConvert(t *testing.T) {
default: null
networks:
default:
- name: compose-e2e-convert_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
+ name: compose-e2e-convert_default
+`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
})
}
-func TestConvertInterpolate(t *testing.T) {
+func TestConfigInterpolate(t *testing.T) {
const projectName = "compose-e2e-convert-interpolate"
c := NewParallelCLI(t)
@@ -266,16 +268,18 @@ func TestConvertInterpolate(t *testing.T) {
t.Run("convert", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose-interpolate.yaml", "-p", projectName, "convert", "--no-interpolate")
- res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services:
+ res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
+networks:
+ default:
+ name: compose-e2e-convert-interpolate_default
+services:
nginx:
build:
context: %s
dockerfile: ${MYVAR}
networks:
default: null
-networks:
- default:
- name: compose-e2e-convert-interpolate_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
+`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
})
}
@@ -313,3 +317,15 @@ func TestRemoveOrphaned(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "-p", projectName, "ps", "--format", "{{.Name}}")
res.Assert(t, icmd.Expected{Out: fmt.Sprintf("%s-words-1", projectName)})
}
+
+func TestResolveDotEnv(t *testing.T) {
+ c := NewCLI(t)
+
+ cmd := c.NewDockerComposeCmd(t, "config")
+ cmd.Dir = filepath.Join(".", "fixtures", "dotenv")
+ res := icmd.RunCmd(cmd)
+ res.Assert(t, icmd.Expected{
+ ExitCode: 0,
+ Out: "image: backend:latest",
+ })
+}
diff --git a/pkg/e2e/e2e_config_plugin.go b/pkg/e2e/e2e_config_plugin.go
index 5de99481f1..84ca6eabcd 100644
--- a/pkg/e2e/e2e_config_plugin.go
+++ b/pkg/e2e/e2e_config_plugin.go
@@ -18,4 +18,4 @@
package e2e
-const composeStandaloneMode = true
+const composeStandaloneMode = false
diff --git a/pkg/e2e/fixtures/dotenv/.env b/pkg/e2e/fixtures/dotenv/.env
new file mode 100644
index 0000000000..869938aa7c
--- /dev/null
+++ b/pkg/e2e/fixtures/dotenv/.env
@@ -0,0 +1 @@
+COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}"
\ No newline at end of file
diff --git a/pkg/e2e/fixtures/dotenv/development/.env b/pkg/e2e/fixtures/dotenv/development/.env
new file mode 100644
index 0000000000..9369028779
--- /dev/null
+++ b/pkg/e2e/fixtures/dotenv/development/.env
@@ -0,0 +1,2 @@
+IMAGE_NAME="${IMAGE_NAME:-backend}"
+IMAGE_TAG="${IMAGE_TAG:-latest}"
diff --git a/pkg/e2e/fixtures/dotenv/development/compose.yaml b/pkg/e2e/fixtures/dotenv/development/compose.yaml
new file mode 100644
index 0000000000..b44805e305
--- /dev/null
+++ b/pkg/e2e/fixtures/dotenv/development/compose.yaml
@@ -0,0 +1,3 @@
+services:
+ backend:
+ image: $IMAGE_NAME:$IMAGE_TAG
diff --git a/pkg/e2e/fixtures/logs-test/cat.yaml b/pkg/e2e/fixtures/logs-test/cat.yaml
new file mode 100644
index 0000000000..76bd5a9ab6
--- /dev/null
+++ b/pkg/e2e/fixtures/logs-test/cat.yaml
@@ -0,0 +1,6 @@
+services:
+ test:
+ image: alpine
+ command: cat /text_file.txt
+ volumes:
+ - ${FILE}:/text_file.txt
diff --git a/pkg/e2e/fixtures/run-test/quiet-pull.yaml b/pkg/e2e/fixtures/run-test/quiet-pull.yaml
new file mode 100644
index 0000000000..922676363f
--- /dev/null
+++ b/pkg/e2e/fixtures/run-test/quiet-pull.yaml
@@ -0,0 +1,3 @@
+services:
+ backend:
+ image: hello-world
\ No newline at end of file
diff --git a/pkg/e2e/fixtures/scale/compose.yaml b/pkg/e2e/fixtures/scale/compose.yaml
index 9ff67af699..619630876b 100644
--- a/pkg/e2e/fixtures/scale/compose.yaml
+++ b/pkg/e2e/fixtures/scale/compose.yaml
@@ -5,6 +5,8 @@ services:
- db
db:
image: nginx:alpine
+ environment:
+ - MAYBE
front:
image: nginx:alpine
deploy:
diff --git a/pkg/e2e/logs_test.go b/pkg/e2e/logs_test.go
index d22347b617..ff4cbab92d 100644
--- a/pkg/e2e/logs_test.go
+++ b/pkg/e2e/logs_test.go
@@ -17,6 +17,10 @@
package e2e
import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
"strings"
"testing"
"time"
@@ -96,6 +100,28 @@ func TestLocalComposeLogsFollow(t *testing.T) {
poll.WaitOn(t, expectOutput(res, "ping-2 "), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(20*time.Second))
}
+func TestLocalComposeLargeLogs(t *testing.T) {
+ const projectName = "compose-e2e-large_logs"
+ file := filepath.Join(t.TempDir(), "large.txt")
+ c := NewCLI(t, WithEnv("FILE="+file))
+ t.Cleanup(func() {
+ c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
+ })
+
+ f, err := os.Create(file)
+ assert.NilError(t, err)
+ for i := 0; i < 300_000; i++ {
+ _, err := io.WriteString(f, fmt.Sprintf("This is line %d in a laaaarge text file\n", i))
+ assert.NilError(t, err)
+ }
+ assert.NilError(t, f.Close())
+
+ cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/logs-test/cat.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
+ cmd.Stdout = io.Discard
+ res := icmd.RunCmd(cmd)
+ res.Assert(t, icmd.Expected{Out: "test-1 exited with code 0"})
+}
+
func expectOutput(res *icmd.Result, expected string) func(t poll.LogT) poll.Result {
return func(t poll.LogT) poll.Result {
if strings.Contains(res.Stdout(), expected) {
diff --git a/pkg/e2e/scale_test.go b/pkg/e2e/scale_test.go
index 1cd80ad1d3..21595dd477 100644
--- a/pkg/e2e/scale_test.go
+++ b/pkg/e2e/scale_test.go
@@ -95,6 +95,78 @@ func TestScaleWithDepsCases(t *testing.T) {
checkServiceContainer(t, res.Combined(), "scale-deps-tests-db", NO_STATE_TO_CHECK, 1)
}
+func TestScaleUpAndDownPreserveContainerNumber(t *testing.T) {
+ const projectName = "scale-up-down-test"
+
+ c := NewCLI(t, WithEnv(
+ "COMPOSE_PROJECT_NAME="+projectName))
+
+ reset := func() {
+ c.RunDockerComposeCmd(t, "down", "--rmi", "all")
+ }
+ t.Cleanup(reset)
+ res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
+
+ t.Log("scale down removes replica #2")
+ res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db")
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
+
+ t.Log("scale up restores replica #2")
+ res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
+}
+
+func TestScaleDownRemovesObsolete(t *testing.T) {
+ const projectName = "scale-down-obsolete-test"
+ c := NewCLI(t, WithEnv(
+ "COMPOSE_PROJECT_NAME="+projectName))
+
+ reset := func() {
+ c.RunDockerComposeCmd(t, "down", "--rmi", "all")
+ }
+ t.Cleanup(reset)
+ res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "db")
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
+
+ cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db")
+ res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+ cmd.Env = append(cmd.Env, "MAYBE=value")
+ })
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2")
+
+ t.Log("scale down removes obsolete replica #1")
+ cmd = c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db")
+ res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
+ cmd.Env = append(cmd.Env, "MAYBE=value")
+ })
+ res.Assert(t, icmd.Success)
+
+ res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db")
+ res.Assert(t, icmd.Success)
+ assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1")
+}
+
func checkServiceContainer(t *testing.T, stdout, containerName, containerState string, count int) {
found := 0
lines := strings.Split(stdout, "\n")
diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go
index 0740e98c22..677b080a0a 100644
--- a/pkg/e2e/watch_test.go
+++ b/pkg/e2e/watch_test.go
@@ -21,7 +21,6 @@ import (
"fmt"
"os"
"path/filepath"
- "strconv"
"strings"
"sync/atomic"
"testing"
@@ -38,23 +37,12 @@ func TestWatch(t *testing.T) {
t.Skip("Skipping watch tests until we can figure out why they are flaky/failing")
services := []string{"alpine", "busybox", "debian"}
- t.Run("docker cp", func(t *testing.T) {
- for _, svcName := range services {
- t.Run(svcName, func(t *testing.T) {
- t.Helper()
- doTest(t, svcName, false)
- })
- }
- })
-
- t.Run("tar", func(t *testing.T) {
- for _, svcName := range services {
- t.Run(svcName, func(t *testing.T) {
- t.Helper()
- doTest(t, svcName, true)
- })
- }
- })
+ for _, svcName := range services {
+ t.Run(svcName, func(t *testing.T) {
+ t.Helper()
+ doTest(t, svcName)
+ })
+ }
}
func TestRebuildOnDotEnvWithExternalNetwork(t *testing.T) {
@@ -150,8 +138,9 @@ func TestRebuildOnDotEnvWithExternalNetwork(t *testing.T) {
}
-// NOTE: these tests all share a single Compose file but are safe to run concurrently
-func doTest(t *testing.T, svcName string, tarSync bool) {
+// NOTE: these tests all share a single Compose file but are safe to run
+// concurrently (though that's not recommended).
+func doTest(t *testing.T, svcName string) {
tmpdir := t.TempDir()
dataDir := filepath.Join(tmpdir, "data")
configDir := filepath.Join(tmpdir, "config")
@@ -171,13 +160,9 @@ func doTest(t *testing.T, svcName string, tarSync bool) {
CopyFile(t, filepath.Join("fixtures", "watch", "compose.yaml"), composeFilePath)
projName := "e2e-watch-" + svcName
- if tarSync {
- projName += "-tar"
- }
env := []string{
"COMPOSE_FILE=" + composeFilePath,
"COMPOSE_PROJECT_NAME=" + projName,
- "COMPOSE_EXPERIMENTAL_WATCH_TAR=" + strconv.FormatBool(tarSync),
}
cli := NewCLI(t, WithEnv(env...))