Compare commits

..

37 Commits

Author SHA1 Message Date
Nicolas De Loof
fa11db3a71 only check attached networks on running containers
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-27 09:20:16 +01:00
MohammadHasan Akbari
1ff9b758d2 fix: commit tests
Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
2024-11-27 07:39:46 +01:00
MohammadHasan Akbari
9eaba55973 feat: add commit command
Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
2024-11-27 07:39:46 +01:00
Nicolas De Loof
a85f8a40a9 run build tests against bake
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-26 15:20:50 +01:00
Nicolas De Loof
095f65cb42 delegate build to buildx bake
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-26 08:51:34 +01:00
dependabot[bot]
208e57ded8 build(deps): bump github.com/stretchr/testify from 1.9.0 to 1.10.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 08:44:16 +01:00
Nicolas De Loof
2d148faedf use service.stop to stop dependent containers
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-25 11:56:22 +01:00
Trevor Foster
43ac1e31c6 Update wait-timeout flag usage to include the unit
Signed-off-by: Trevor Foster <trevorfoster19@gmail.com>
2024-11-25 11:22:18 +01:00
Sebastiaan van Stijn
5561a778c9 go.mod: github.com/docker/cli v27.4.0-rc.2
full diff: https://github.com/docker/cli/compare/8d1bacae3e49...v27.4.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-20 09:55:29 +01:00
Sebastiaan van Stijn
ae48f488d1 go.mod: github.com/docker/docker v27.4.0-rc.2
full diff: https://github.com/docker/docker/compare/v27.4.0-rc.1...v27.4.0-rc.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-20 09:55:29 +01:00
Sebastiaan van Stijn
5e3a095380 go.mod: github.com/docker/cli 8d1bacae3e49 (v27.4.0-rc.2-dev)
full diff: https://github.com/docker/cli/compare/v27.4.0-rc.1...8d1bacae3e49ed1d096eede8eef4ae851d7f2eae

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 16:30:26 +01:00
Sebastiaan van Stijn
a2a3eb72e2 go.mod: github.com/docker/cli v27.4.0-rc.1
- full diff: https://github.com/docker/cli/compare/cb3048fbebb1...v27.4.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 16:30:26 +01:00
Sebastiaan van Stijn
3513b42423 go.mod: github.com/docker/docker v27.4.0-rc.1
- full diff: https://github.com/docker/docker/compare/v27.3.1...v27.4.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 16:30:26 +01:00
Nicolas De loof
d4fa63fdcb Update pkg/compose/convergence.go
Co-authored-by: Guillaume Lours <705411+glours@users.noreply.github.com>
Signed-off-by: Nicolas De loof <nicolas.deloof@gmail.com>
2024-11-15 15:35:49 +01:00
Nicolas De Loof
c21d4cfb40 detect network config changes and recreate if needed
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-15 15:35:49 +01:00
Sebastiaan van Stijn
61f1d4f69b go.mod: github.com/docker/buildx v0.18.0
full diff: https://github.com/docker/buildx/compare/v0.17.1..v0.18.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:57:00 +01:00
Sebastiaan van Stijn
f7cce281de go.mod: github.com/moby/buildkit v0.17.1
full diff: https://github.com/moby/buildkit/compare/v0.16.0..v0.17.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:57:00 +01:00
Sebastiaan van Stijn
bcaacc7f23 gha: test against docker engine v27.4.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:48:29 +01:00
Guillaume Lours
3f5898f8d0 push empty descriptor layer when using OCI version 1.1 for Compose artifact
it fixes a repository creation issue when pushing the 1st time a Compose OCI artifact on the Hub

Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-11-13 13:36:31 +01:00
Guillaume Lours
2bb67f2700 remove ddev e2e tests
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-11-13 12:52:48 +01:00
Nicolas De Loof
bf521fe3ab implement remove-orphans on run
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-12 14:28:26 +01:00
Matthieu MOREL
11e9621da5 ci: enable testifylint linter
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2024-11-12 11:12:32 +01:00
Felix Fontein
a9de9abcfb Emit events for building images
Signed-off-by: Felix Fontein <felix@fontein.de>
2024-11-12 10:59:41 +01:00
koooge
799ab842a0 Fix compose images that reutn a different image with the same ID
Signed-off-by: koooge <koooooge@gmail.com>
2024-11-12 09:51:33 +01:00
Nicolas De Loof
2f65ace2aa remove obsolete containers first on scale down
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-12 09:47:36 +01:00
Guillaume Lours
aa0a4189ee pass stal bot inactivity limit from 6 to 3 months
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-11-12 09:37:21 +01:00
Suleiman Dibirov
eba3ff8f37 fix(config): Print service names with --no-interpolate
Signed-off-by: Suleiman Dibirov <idsulik@gmail.com>
2024-11-11 16:42:55 +01:00
dependabot[bot]
6313365ba4 build(deps): bump golang.org/x/sys from 0.26.0 to 0.27.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/sys/compare/v0.26.0...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-08 11:20:24 +01:00
dependabot[bot]
dbd51745c4 build(deps): bump golang.org/x/sync from 0.8.0 to 0.9.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/sync/compare/v0.8.0...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-08 10:49:59 +01:00
Guillaume Lours
a8bfbc147a bump compose-go v2.4.4
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-11-07 14:19:53 +01:00
Joana Hrotko
fbbd6f83d7 Avoid starting all services on rebuild
Signed-off-by: Joana Hrotko <joana.hrotko@docker.com>
2024-11-05 14:51:12 +00:00
Guillaume Lours
a000978980 remove ArtifactType from Config in OCI v1.1 definition of the artifact
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-11-05 15:10:56 +01:00
dependabot[bot]
361c0893a9 build(deps): bump github.com/compose-spec/compose-go/v2
Bumps [github.com/compose-spec/compose-go/v2](https://github.com/compose-spec/compose-go) from 2.4.2 to 2.4.3.
- [Release notes](https://github.com/compose-spec/compose-go/releases)
- [Commits](https://github.com/compose-spec/compose-go/compare/v2.4.2...v2.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-05 10:50:25 +01:00
Nicolas De Loof
513b6128c2 Service being declared in a profile must not trigger re-creation
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2024-11-05 10:49:08 +01:00
Joana Hrotko
eececb9add Add profile e2e test case to document in compose
Signed-off-by: Joana Hrotko <joana.hrotko@docker.com>
2024-10-30 16:00:41 +01:00
Laura Brehm
501b5acde6 Update MAINTAINERS file
Add `jhrotko` to Core Maintainers.

Signed-off-by: Laura Brehm <laurabrehm@hey.com>
2024-10-30 13:33:46 +01:00
Guillaume Lours
f51bc4cd00 bump compose-go to version v2.4.2
Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
2024-10-30 11:30:53 +01:00
54 changed files with 1326 additions and 364 deletions

2
.github/stale.yml vendored
View File

@@ -1,7 +1,7 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 180
daysUntilStale: 90
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.

View File

@@ -144,7 +144,7 @@ jobs:
- 24.0.9
- 25.0.5
- 26.1.4
- 27.3.0
- 27.4.0
steps:
- name: Prepare
run: |

View File

@@ -22,6 +22,7 @@ linters:
- nakedret
- nolintlint
- staticcheck
- testifylint
- typecheck
- unconvert
- unparam
@@ -71,3 +72,11 @@ issues:
# golangci hides some golint warnings (the warning about exported things
# without documentation for example), this will make it show them anyway.
exclude-use-default: false
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0

View File

@@ -23,6 +23,7 @@
people = [
"glours",
"jhrotko",
"milas",
"ndeloof",
"nicksieger",
@@ -72,6 +73,11 @@
Email = "guillaume.tardif@docker.com"
GitHub = "gtardif"
[people.jhrotko]
Name = "Joana Hrotko"
Email = "joana.hrotko@docker.com"
Github = "jhrotko"
[people.laurazard]
Name = "Laura Brehm"
Email = "laura.brehm@docker.com"

93
cmd/compose/commit.go Normal file
View File

@@ -0,0 +1,93 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
type commitOptions struct {
*ProjectOptions
service string
reference string
pause bool
comment string
author string
changes opts.ListOpts
index int
}
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
options := commitOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]",
Short: "Create a new image from a service container's changes",
Args: cobra.RangeArgs(1, 2),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
options.service = args[0]
if len(args) > 1 {
options.reference = args[1]
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runCommit(ctx, dockerCli, backend, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
flags.StringVarP(&options.author, "author", "a", "", `Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")`)
options.changes = opts.NewListOpts(nil)
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
return cmd
}
func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Service, options commitOptions) error {
projectName, err := options.toProjectName(ctx, dockerCli)
if err != nil {
return err
}
commitOptions := api.CommitOptions{
Service: options.service,
Reference: options.reference,
Pause: options.pause,
Comment: options.comment,
Author: options.author,
Changes: options.changes,
Index: options.index,
}
return backend.Commit(ctx, projectName, commitOptions)
}

View File

@@ -617,6 +617,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
execCommand(&opts, dockerCli, backend),
attachCommand(&opts, dockerCli, backend),
exportCommand(&opts, dockerCli, backend),
commitCommand(&opts, dockerCli, backend),
pauseCommand(&opts, dockerCli, backend),
unpauseCommand(&opts, dockerCli, backend),
topCommand(&opts, dockerCli, backend),

View File

@@ -279,6 +279,22 @@ func formatModel(model map[string]any, format string) (content []byte, err error
}
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
if opts.noInterpolate {
// we can't use ToProject, so the model we render here is only partially resolved
data, err := opts.ToModel(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
if _, ok := data["services"]; ok {
for serviceName := range data["services"].(map[string]any) {
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
}
}
return nil
}
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
@@ -287,6 +303,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions)
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
return nil
})
return err
}

View File

@@ -27,6 +27,7 @@ import (
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
@@ -74,13 +75,13 @@ func TestPsTable(t *testing.T) {
cli.EXPECT().Out().Return(stdout).AnyTimes()
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
err = runPs(ctx, cli, backend, nil, opts)
assert.NoError(t, err)
require.NoError(t, err)
_, err = f.Seek(0, 0)
assert.NoError(t, err)
require.NoError(t, err)
output, err := os.ReadFile(out)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
}

View File

@@ -63,6 +63,7 @@ type runOptions struct {
name string
noDeps bool
ignoreOrphans bool
removeOrphans bool
quietPull bool
}
@@ -189,7 +190,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
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")
flags.BoolVar(&options.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")
@@ -314,6 +315,7 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P
err := backend.Create(ctx, &project, api.CreateOptions{
Build: buildOpts,
IgnoreOrphans: options.ignoreOrphans,
RemoveOrphans: options.removeOrphans,
QuietPull: options.quietPull,
})
if err != nil {

View File

@@ -26,10 +26,10 @@ import (
"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/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
ui "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
@@ -167,7 +167,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
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 in seconds 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.")
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")

View File

@@ -17,10 +17,10 @@
package compose
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPreferredIndentationStr(t *testing.T) {
@@ -83,10 +83,12 @@ func TestPreferredIndentationStr(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := preferredIndentationStr(tt.args.size, tt.args.useSpace)
if tt.wantErr && assert.NotNilf(t, err, fmt.Sprintf("preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)) {
return
if tt.wantErr {
require.Errorf(t, err, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
} else {
require.NoError(t, err)
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
}
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
})
}
}

View File

@@ -13,6 +13,7 @@ Define and run multi-container applications with Docker
|:--------------------------------|:----------------------------------------------------------------------------------------|
| [`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 |
| [`commit`](compose_commit.md) | Create a new image from a service container's changes |
| [`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 |

View File

@@ -0,0 +1,19 @@
# docker compose commit
<!---MARKER_GEN_START-->
Create a new image from a service container's changes
### Options
| Name | Type | Default | Description |
|:------------------|:---------|:--------|:-----------------------------------------------------------|
| `-a`, `--author` | `string` | | Author (e.g., "John Hannibal Smith <hannibal@a-team.com>") |
| `-c`, `--change` | `list` | | Apply Dockerfile instruction to the created image |
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
| `-m`, `--message` | `string` | | Commit message |
| `-p`, `--pause` | `bool` | `true` | Pause container during commit |
<!---MARKER_GEN_END-->

View File

@@ -51,7 +51,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
| `--timestamps` | `bool` | | Show timestamps |
| `--wait` | `bool` | | 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 |
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |

View File

@@ -7,6 +7,7 @@ plink: docker.yaml
cname:
- docker compose attach
- docker compose build
- docker compose commit
- docker compose config
- docker compose cp
- docker compose create
@@ -39,6 +40,7 @@ cname:
clink:
- docker_compose_attach.yaml
- docker_compose_build.yaml
- docker_compose_commit.yaml
- docker_compose_config.yaml
- docker_compose_cp.yaml
- docker_compose_create.yaml

View File

@@ -0,0 +1,76 @@
command: docker compose commit
short: Create a new image from a service container's changes
long: Create a new image from a service container's changes
usage: docker compose commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]
pname: docker compose
plink: docker_compose.yaml
options:
- option: author
shorthand: a
value_type: string
description: Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: change
shorthand: c
value_type: list
description: Apply Dockerfile instruction to the created image
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: index
value_type: int
default_value: "0"
description: index of the container if service has multiple replicas.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: message
shorthand: m
value_type: string
description: Commit message
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: pause
shorthand: p
value_type: bool
default_value: "true"
description: Pause container during commit
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: dry-run
value_type: bool
default_value: "false"
description: Execute command in dry run mode
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false

View File

@@ -289,7 +289,8 @@ 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 in seconds to wait for the project to be running|healthy
deprecated: false
hidden: false
experimental: false

42
go.mod
View File

@@ -7,15 +7,15 @@ require (
github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.4.1
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465
github.com/containerd/containerd v1.7.23
github.com/containerd/platforms v0.2.1
github.com/davecgh/go-spew v1.1.1
github.com/distribution/reference v0.6.0
github.com/docker/buildx v0.17.1
github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible
github.com/docker/buildx v0.18.0
github.com/docker/cli v27.4.0-rc.2+incompatible
github.com/docker/cli-docs-tool v0.8.0
github.com/docker/docker v27.3.1+incompatible
github.com/docker/docker v27.4.0-rc.2+incompatible
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
@@ -27,7 +27,7 @@ require (
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.16.0
github.com/moby/buildkit v0.17.1
github.com/moby/patternmatcher v0.6.0
github.com/moby/term v0.5.0
github.com/morikuni/aec v1.0.0
@@ -39,7 +39,7 @@ require (
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
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.46.1
@@ -51,9 +51,9 @@ require (
go.opentelemetry.io/otel/trace v1.21.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.5.0
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
golang.org/x/sync v0.8.0
golang.org/x/sys v0.26.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
golang.org/x/sync v0.9.0
golang.org/x/sys v0.27.0
google.golang.org/grpc v1.67.1
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.1
@@ -83,12 +83,12 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/containerd/containerd/api v1.7.19 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/containerd/continuity v0.4.4 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/ttrpc v1.2.5 // indirect
github.com/containerd/typeurl/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
@@ -103,7 +103,6 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
@@ -121,7 +120,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
@@ -133,10 +132,11 @@ require (
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/symlink v0.3.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -145,6 +145,7 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
@@ -155,7 +156,8 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
@@ -169,16 +171,16 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

99
go.sum
View File

@@ -18,8 +18,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0=
github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
@@ -85,18 +85,19 @@ github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0Tx
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.4.1 h1:tEg6Qn/9LZnKg42fZlFmxN4lxSqnCvsiG5TXnxzvI4c=
github.com/compose-spec/compose-go/v2 v2.4.1/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465 h1:1PRX/3a/n4W2DrMJu4CV9OS8Z2eauOBLe0zOuSlrWDY=
github.com/compose-spec/compose-go/v2 v2.4.5-0.20241111154218-9d02caaf8465/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
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/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA=
github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig=
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
@@ -114,8 +115,9 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL
github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=
github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
@@ -126,17 +128,17 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/buildx v0.17.1 h1:9ob2jGp4+W9PxWw68GsoNFp+eYFc7eUoRL9VljLCSM4=
github.com/docker/buildx v0.17.1/go.mod h1:kJOhOhS47LRvrLFRulFiO5SE6VJf54yYMn7DzjgO5W0=
github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible h1:fJ3SzYiebfWoas3qOJpmRsNrDL2w1XIpPFywSLFhhfk=
github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/buildx v0.18.0 h1:rSauXHeJt90NvtXrLK5J992Eb0UPJZs2vV3u1zTf1nE=
github.com/docker/buildx v0.18.0/go.mod h1:JGNSshOhHs5FhG3u51jXUf4lLOeD2QBIlJ2vaRB67p4=
github.com/docker/cli v27.4.0-rc.2+incompatible h1:A0GZwegDlt2wdt3tpmrUzkVOZmbhvd7i05wPSf7Oo74=
github.com/docker/cli v27.4.0-rc.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.8.0 h1:YcDWl7rQJC3lJ7WVZRwSs3bc9nka97QLWfyJQli8yJU=
github.com/docker/cli-docs-tool v0.8.0/go.mod h1:8TQQ3E7mOXoYUs811LiPdUnAhXrcVsBIrW21a5pUbdk=
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 v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.4.0-rc.2+incompatible h1:9OJjVGtelk/zGC3TyKweJ29b9Axzh0s/0vtU4mneumE=
github.com/docker/docker v27.4.0-rc.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
@@ -192,8 +194,6 @@ github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAp
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -274,8 +274,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -319,8 +319,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z
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/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE=
github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ=
github.com/moby/buildkit v0.17.1 h1:VWj6eIdk7u6acHPn2CiA+tdq0/mQoBEk9ckweRzWmPw=
github.com/moby/buildkit v0.17.1/go.mod h1:ru8NFyDHD8HbuKaLXJIjK9nr3x6FZR+IWjtF07S+wdM=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
@@ -329,14 +329,16 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc=
github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
@@ -389,6 +391,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -462,14 +466,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
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-20240424095704-91a3fc46842c h1:+6wg/4ORAbnSoGDzg2Q1i3CeMcT/jjhye/ZfnBHy7/M=
github.com/tonistiigi/fsutil v0.0.0-20240424095704-91a3fc46842c/go.mod h1:vbbYqJlnswsbJqWUcJN8fKtBhnEgldDrcagTgnBVKKM=
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI=
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c h1:bQLsfX+uEPZUjyR2qmoJs5F8Z57bPVmF3IsUf22Xo9Y=
github.com/tonistiigi/fsutil v0.0.0-20241028165955-397af5306b5c/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
@@ -530,15 +536,15 @@ 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -549,8 +555,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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -560,8 +566,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -576,25 +582,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -602,8 +609,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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
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=
@@ -617,8 +624,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=

View File

@@ -88,6 +88,10 @@ func PushManifest(
layers []Pushable,
ociVersion api.OCIVersion,
) error {
// Check if we need an extra empty layer for the manifest config
if ociVersion == api.OCIVersion1_1 || ociVersion == "" {
layers = append(layers, Pushable{Descriptor: v1.DescriptorEmptyJSON, Data: []byte("{}")})
}
// prepare to push the manifest by pushing the layers
layerDescriptors := make([]v1.Descriptor, len(layers))
for i := range layers {
@@ -178,7 +182,6 @@ func generateManifest(layers []v1.Descriptor, ociCompat api.OCIVersion) ([]Pusha
case api.OCIVersion1_1:
config = v1.DescriptorEmptyJSON
artifactType = ComposeProjectArtifactType
config.ArtifactType = artifactType
// N.B. the descriptor has the data embedded in it
toPush = append(toPush, Pushable{Descriptor: config, Data: make([]byte, len(config.Data))})
default:

View File

@@ -23,6 +23,7 @@ import (
"time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/opts"
"github.com/docker/compose/v2/pkg/utils"
)
@@ -92,6 +93,8 @@ type Service interface {
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
// Export a service container's filesystem as a tar archive
Export(ctx context.Context, projectName string, options ExportOptions) error
// Create a new image from a service container's changes
Commit(ctx context.Context, projectName string, options CommitOptions) error
// Generate generates a Compose Project from existing containers
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
}
@@ -565,6 +568,19 @@ type ExportOptions struct {
Output string
}
// CommitOptions group options of the Commit API
type CommitOptions struct {
Service string
Reference string
Pause bool
Comment string
Author string
Changes opts.ListOpts
Index int
}
type GenerateOptions struct {
// ProjectName to set in the Compose file
ProjectName string

View File

@@ -21,7 +21,6 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms"
@@ -38,7 +37,6 @@ import (
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder/remotecontext/urlutil"
bclient "github.com/moby/buildkit/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
@@ -64,26 +62,16 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
}, s.stdinfo(), "Building")
}
type serviceToBuild struct {
name string
service types.ServiceConfig
}
//nolint:gocyclo
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]string) (map[string]string, error) {
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
if err != nil {
return nil, err
}
imageIDs := map[string]string{}
serviceToBeBuild := map[string]serviceToBuild{}
serviceToBuild := types.Services{}
var policy types.DependencyOption = types.IgnoreDependencies
if options.Deps {
policy = types.IncludeDependencies
}
err = project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
err := project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
if service.Build == nil {
return nil
}
@@ -92,14 +80,26 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
return nil
}
serviceToBeBuild[serviceName] = serviceToBuild{name: serviceName, service: *service}
serviceToBuild[serviceName] = *service
return nil
}, policy)
if err != nil || len(serviceToBeBuild) == 0 {
if err != nil || len(serviceToBuild) == 0 {
return imageIDs, err
}
bake, err := buildWithBake(s.dockerCli)
if err != nil {
return nil, err
}
if bake {
return s.doBuildBake(ctx, project, serviceToBuild, options)
}
// Initialize buildkit nodes
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
if err != nil {
return nil, err
}
var (
b *builder.Builder
nodes []builder.Node
@@ -152,17 +152,20 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
return -1
}
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
serviceToBuild, ok := serviceToBeBuild[name]
service, ok := serviceToBuild[name]
if !ok {
return nil
}
service := serviceToBuild.service
cw := progress.ContextWriter(ctx)
serviceName := fmt.Sprintf("Service %s", name)
if !buildkitEnabled {
cw.Event(progress.BuildingEvent(serviceName))
id, err := s.doBuildClassic(ctx, project, service, options)
if err != nil {
return err
}
cw.Event(progress.BuiltEvent(serviceName))
builtDigests[getServiceIndex(name)] = id
if options.Push {
@@ -180,10 +183,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
return err
}
cw.Event(progress.BuildingEvent(serviceName))
digest, err := s.doBuildBuildkit(ctx, name, buildOptions, w, nodes)
if err != nil {
return err
}
cw.Event(progress.BuiltEvent(serviceName))
builtDigests[getServiceIndex(name)] = digest
return nil
@@ -204,7 +209,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
for i, imageDigest := range builtDigests {
if imageDigest != "" {
imageRef := api.GetImageNameOrDefault(project.Services[names[i]], project.Name)
service := project.Services[names[i]]
imageRef := api.GetImageNameOrDefault(service, project.Name)
imageIDs[imageRef] = imageDigest
}
}
@@ -274,7 +280,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
imageNames = append(imageNames, imgName)
}
}
imgs, err := s.getImages(ctx, imageNames)
imgs, err := s.getImageSummaries(ctx, imageNames)
if err != nil {
return nil, err
}
@@ -327,12 +333,7 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
//
// Finally, standard proxy variables based on the Docker client configuration are added, but will not overwrite
// any values if already present.
func resolveAndMergeBuildArgs(
dockerCli command.Cli,
project *types.Project,
service types.ServiceConfig,
opts api.BuildOptions,
) types.MappingWithEquals {
func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, opts api.BuildOptions) types.MappingWithEquals {
result := make(types.MappingWithEquals).
OverrideBy(service.Build.Args).
OverrideBy(opts.Args).
@@ -472,16 +473,6 @@ func flatten(in types.MappingWithEquals) types.Mapping {
return out
}
func dockerFilePath(ctxName string, dockerfile string) string {
if dockerfile == "" {
return ""
}
if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
return dockerfile
}
return filepath.Join(ctxName, dockerfile)
}
func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
for _, sshKey := range sshKeys {

305
pkg/compose/build_bake.go Normal file
View File

@@ -0,0 +1,305 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/socket"
"github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"golang.org/x/sync/errgroup"
)
func buildWithBake(dockerCli command.Cli) (bool, error) {
b, ok := os.LookupEnv("COMPOSE_BAKE")
if !ok {
if dockerCli.ConfigFile().Plugins["compose"]["build"] == "bake" {
b, ok = "true", true
}
}
if !ok {
return false, nil
}
bake, err := strconv.ParseBool(b)
if err != nil {
return false, err
}
if !bake {
return false, nil
}
enabled, err := dockerCli.BuildKitEnabled()
if err != nil {
return false, err
}
if !enabled {
logrus.Warnf("Docker Compose is configured to build using Bake, but buildkit isn't enabled")
}
_, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{})
if err != nil {
if manager.IsNotFound(err) {
logrus.Warnf("Docker Compose is configured to build using Bake, but buildx isn't installed")
return false, nil
}
return false, err
}
return true, err
}
// We _could_ use bake.* types from github.com/docker/buildx but long term plan is to remove buildx as a dependency
type bakeConfig struct {
Groups map[string]bakeGroup `json:"group"`
Targets map[string]bakeTarget `json:"target"`
}
type bakeGroup struct {
Targets []string `json:"targets"`
}
type bakeTarget struct {
Context string `json:"context,omitempty"`
Dockerfile string `json:"dockerfile,omitempty"`
Args map[string]string `json:"args,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Tags []string `json:"tags,omitempty"`
CacheFrom []string `json:"cache-from,omitempty"`
CacheTo []string `json:"cache-to,omitempty"`
Secrets []string `json:"secret,omitempty"`
SSH []string `json:"ssh,omitempty"`
Platforms []string `json:"platforms,omitempty"`
Target string `json:"target,omitempty"`
Pull bool `json:"pull,omitempty"`
NoCache bool `json:"no-cache,omitempty"`
}
type bakeMetadata map[string]buildStatus
type buildStatus struct {
Digest string `json:"containerimage.digest"`
}
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
cw := progress.ContextWriter(ctx)
for name := range serviceToBeBuild {
cw.Event(progress.BuildingEvent(name))
}
eg := errgroup.Group{}
ch := make(chan *client.SolveStatus)
display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress))
if err != nil {
return nil, err
}
eg.Go(func() error {
_, err := display.UpdateFrom(ctx, ch)
return err
})
cfg := bakeConfig{
Groups: map[string]bakeGroup{},
Targets: map[string]bakeTarget{},
}
var group bakeGroup
for name, service := range serviceToBeBuild {
if service.Build == nil {
continue
}
build := *service.Build
args := types.Mapping{}
for k, v := range resolveAndMergeBuildArgs(s.dockerCli, project, service, options) {
if v == nil {
continue
}
args[k] = *v
}
cfg.Targets[name] = bakeTarget{
Context: build.Context,
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
Args: args,
Labels: build.Labels,
Tags: build.Tags,
CacheFrom: build.CacheFrom,
// CacheTo: TODO
Platforms: build.Platforms,
Target: build.Target,
Secrets: toBakeSecrets(project, build.Secrets),
SSH: toBakeSSH(build.SSH),
Pull: options.Pull,
NoCache: options.NoCache,
}
group.Targets = append(group.Targets, name)
}
cfg.Groups["default"] = group
b, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
metadata, err := os.CreateTemp(os.TempDir(), "compose")
if err != nil {
return nil, err
}
buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{})
if err != nil {
return nil, err
}
cmd := exec.CommandContext(ctx, buildx.Path, "bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadata.Name())
// Remove DOCKER_CLI_PLUGIN... variable so buildx can detect it run standalone
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
// Use docker/cli mechanism to propagate termination signal to child process
server, err := socket.NewPluginServer(nil)
if err != nil {
defer server.Close() //nolint:errcheck
cmd.Cancel = server.Close
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
}
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
carrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, &carrier)
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
cmd.Stdout = s.stdout()
cmd.Stdin = bytes.NewBuffer(b)
pipe, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
eg.Go(cmd.Wait)
for {
decoder := json.NewDecoder(pipe)
var s client.SolveStatus
err := decoder.Decode(&s)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
// bake displays build details at the end of a build, which isn't a json SolveStatus
continue
}
ch <- &s
}
close(ch) // stop build progress UI
err = eg.Wait()
if err != nil {
return nil, err
}
b, err = os.ReadFile(metadata.Name())
if err != nil {
return nil, err
}
var md bakeMetadata
err = json.Unmarshal(b, &md)
if err != nil {
return nil, err
}
results := map[string]string{}
for name, m := range md {
results[name] = m.Digest
cw.Event(progress.BuiltEvent(name))
}
return results, nil
}
func toBakeSSH(ssh types.SSHConfig) []string {
var s []string
for _, key := range ssh {
s = append(s, fmt.Sprintf("%s=%s", key.ID, key.Path))
}
return s
}
func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig) []string {
var s []string
for _, ref := range secrets {
def := project.Secrets[ref.Source]
switch {
case def.Environment != "":
s = append(s, fmt.Sprintf("id=%s,type=env,env=%s", ref.Source, def.Environment))
case def.File != "":
s = append(s, fmt.Sprintf("id=%s,type=file,src=%s", ref.Source, def.File))
}
}
return s
}
func filter(environ []string, variable string) []string {
prefix := variable + "="
filtered := make([]string, 0, len(environ))
for _, val := range environ {
if !strings.HasPrefix(val, prefix) {
filtered = append(filtered, val)
}
}
return filtered
}
func replace(environ []string, variable, value string) []string {
filtered := filter(environ, variable)
return append(filtered, fmt.Sprintf("%s=%s", variable, value))
}
func dockerFilePath(ctxName string, dockerfile string) string {
if dockerfile == "" {
return ""
}
if urlutil.IsGitURL(ctxName) || filepath.IsAbs(dockerfile) {
return dockerfile
}
return filepath.Join(ctxName, dockerfile)
}

View File

@@ -45,7 +45,7 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, service string, op
response, err = build.Build(ctx, nodes,
map[string]build.Options{service: opts},
dockerutil.NewClient(s.dockerCli),
confutil.ConfigDir(s.dockerCli),
confutil.NewConfig(s.dockerCli),
buildx.WithPrefix(p, service, true))
if err != nil {
return "", WrapCategorisedComposeError(err, BuildFailure)
@@ -70,11 +70,6 @@ func (s composeService) dryRunBuildResponse(ctx context.Context, name string, op
w := progress.ContextWriter(ctx)
buildResponse := map[string]*client.SolveResponse{}
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
w.Event(progress.Event{
ID: " ",
Status: progress.Done,
Text: fmt.Sprintf("build service %s", name),
})
w.Event(progress.Event{
ID: "==>",
Status: progress.Done,

87
pkg/compose/commit.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"fmt"
"strings"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
containerType "github.com/docker/docker/api/types/container"
)
func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.commit(ctx, projectName, options)
}, s.stdinfo(), "Committing")
}
func (s *composeService) commit(ctx context.Context, projectName string, options api.CommitOptions) error {
projectName = strings.ToLower(projectName)
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index)
if err != nil {
return err
}
clnt := s.dockerCli.Client()
w := progress.ContextWriter(ctx)
name := getCanonicalContainerName(container)
msg := fmt.Sprintf("Commit %s", name)
w.Event(progress.Event{
ID: name,
Text: msg,
Status: progress.Working,
StatusText: "Committing",
})
if s.dryRun {
w.Event(progress.Event{
ID: name,
Text: msg,
Status: progress.Done,
StatusText: "Committed",
})
return nil
}
response, err := clnt.ContainerCommit(ctx, container.ID, containerType.CommitOptions{
Reference: options.Reference,
Comment: options.Comment,
Author: options.Author,
Changes: options.Changes.GetAll(),
Pause: options.Pause,
})
if err != nil {
return err
}
w.Event(progress.Event{
ID: name,
Text: msg,
Status: progress.Done,
StatusText: fmt.Sprintf("Committed as %s", response.ID),
})
return nil
}

View File

@@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"
"slices"
"sort"
"strconv"
"strings"
@@ -56,24 +57,25 @@ const (
// Cross services dependencies are managed by creating services in expected order and updating `service:xx` reference
// when a service has converged, so dependent ones can be managed with resolved containers references.
type convergence struct {
service *composeService
observedState map[string]Containers
stateMutex sync.Mutex
service *composeService
services map[string]Containers
networks map[string]string
stateMutex sync.Mutex
}
func (c *convergence) getObservedState(serviceName string) Containers {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
return c.observedState[serviceName]
return c.services[serviceName]
}
func (c *convergence) setObservedState(serviceName string, containers Containers) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
c.observedState[serviceName] = containers
c.services[serviceName] = containers
}
func newConvergence(services []string, state Containers, s *composeService) *convergence {
func newConvergence(services []string, state Containers, networks map[string]string, s *composeService) *convergence {
observedState := map[string]Containers{}
for _, s := range services {
observedState[s] = Containers{}
@@ -83,8 +85,9 @@ func newConvergence(services []string, state Containers, s *composeService) *con
observedState[service] = append(observedState[service], c)
}
return &convergence{
service: s,
observedState: observedState,
service: s,
services: observedState,
networks: networks,
}
}
@@ -123,11 +126,11 @@ 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 {
if obsolete, _ := c.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 {
if obsolete, _ := c.mustRecreate(service, containers[j], recreate); obsolete {
// j is obsolete, so must be first in the list
return false
}
@@ -136,16 +139,18 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
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
return ni > nj
}
// If we don't get a container number (?) just sort by creation date
return containers[i].Created < containers[j].Created
})
slices.Reverse(containers)
for i, container := range containers {
if i >= expected {
// Scale Down
// As we sorted containers, obsolete ones and/or highest number will be removed
container := container
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
@@ -154,7 +159,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
continue
}
mustRecreate, err := mustRecreate(service, container, recreate)
mustRecreate, err := c.mustRecreate(service, container, recreate)
if err != nil {
return err
}
@@ -219,15 +224,18 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
}
func (c *convergence) stopDependentContainers(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
w := progress.ContextWriter(ctx)
// Stop dependent containers, so they will be restarted after service is re-created
dependents := project.GetDependentsForService(service)
err := c.service.stop(ctx, project.Name, api.StopOptions{
Services: dependents,
Project: project,
})
if err != nil {
return err
}
for _, name := range dependents {
dependents := c.getObservedState(name)
err := c.service.stopContainers(ctx, w, &service, dependents, nil)
if err != nil {
return err
}
for i, dependent := range dependents {
dependent.State = ContainerExited
dependents[i] = dependent
@@ -312,7 +320,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
return nil
}
func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
if policy == api.RecreateNever {
return false, nil
}
@@ -325,7 +333,33 @@ func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy st
}
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
return configChanged || imageUpdated, nil
if configChanged || imageUpdated {
return true, nil
}
if c.networks != nil && actual.State == "running" {
// check the networks container is connected to are the expected ones
for net := range expected.Networks {
id := c.networks[net]
if id == "swarm" {
// corner-case : swarm overlay network isn't visible until a container is attached
continue
}
found := false
for _, settings := range actual.NetworkSettings.Networks {
if settings.NetworkID == id {
found = true
break
}
}
if !found {
// config is up-to-date but container is not connected to network - maybe recreated ?
return true, nil
}
}
}
return false, nil
}
func getContainerName(projectName string, service types.ServiceConfig, number int) string {

View File

@@ -80,12 +80,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
return err
}
var observedState Containers
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
if err != nil {
return err
}
err = s.ensureImagesExists(ctx, project, options.Build, options.QuietPull)
if err != nil {
return err
@@ -93,7 +87,8 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
prepareNetworks(project)
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
networks, err := s.ensureNetworks(ctx, project)
if err != nil {
return err
}
@@ -101,6 +96,11 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
return err
}
var observedState Containers
observedState, err = s.getContainers(ctx, project.Name, oneOffInclude, true)
if err != nil {
return err
}
orphans := observedState.filter(isOrphaned(project))
if len(orphans) > 0 && !options.IgnoreOrphans {
if options.RemoveOrphans {
@@ -115,27 +115,30 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
"--remove-orphans flag to clean it up.", orphans.names())
}
}
return newConvergence(options.Services, observedState, s).apply(ctx, project, options)
return newConvergence(options.Services, observedState, networks, s).apply(ctx, project, options)
}
func prepareNetworks(project *types.Project) {
for k, nw := range project.Networks {
nw.Labels = nw.Labels.Add(api.NetworkLabel, k)
nw.Labels = nw.Labels.Add(api.ProjectLabel, project.Name)
nw.Labels = nw.Labels.Add(api.VersionLabel, api.ComposeVersion)
nw.CustomLabels = nw.CustomLabels.
Add(api.NetworkLabel, k).
Add(api.ProjectLabel, project.Name).
Add(api.VersionLabel, api.ComposeVersion)
project.Networks[k] = nw
}
}
func (s *composeService) ensureNetworks(ctx context.Context, networks types.Networks) error {
for i, nw := range networks {
err := s.ensureNetwork(ctx, &nw)
func (s *composeService) ensureNetworks(ctx context.Context, project *types.Project) (map[string]string, error) {
networks := map[string]string{}
for name, nw := range project.Networks {
id, err := s.ensureNetwork(ctx, project, name, &nw)
if err != nil {
return err
return nil, err
}
networks[i] = nw
networks[name] = id
project.Networks[name] = nw
}
return nil
return networks, nil
}
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
@@ -1200,24 +1203,21 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
}
}
func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
if n.External {
return s.resolveExternalNetwork(ctx, n)
}
err := s.resolveOrCreateNetwork(ctx, n)
id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
if errdefs.IsConflict(err) {
// Maybe another execution of `docker compose up|run` created same network
// let's retry once
return s.resolveOrCreateNetwork(ctx, n)
return s.resolveOrCreateNetwork(ctx, project, "", n)
}
return err
return id, err
}
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.NetworkConfig) error { //nolint:gocyclo
expectedNetworkLabel := n.Labels[api.NetworkLabel]
expectedProjectLabel := n.Labels[api.ProjectLabel]
func (s *composeService) resolveOrCreateNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) { //nolint:gocyclo
// First, try to find a unique network matching by name or ID
inspect, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
if err == nil {
@@ -1228,20 +1228,33 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
if !ok {
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
"Set `external: true` to use an existing network", n.Name)
} else if p != expectedProjectLabel {
} else if p != project.Name {
logrus.Warnf("a network with name %s exists but was not created for project %q.\n"+
"Set `external: true` to use an existing network", n.Name, expectedProjectLabel)
"Set `external: true` to use an existing network", n.Name, project.Name)
}
if inspect.Labels[api.NetworkLabel] != expectedNetworkLabel {
return fmt.Errorf(
if inspect.Labels[api.NetworkLabel] != name {
return "", fmt.Errorf(
"network %s was found but has incorrect label %s set to %q (expected: %q)",
n.Name,
api.NetworkLabel,
inspect.Labels[api.NetworkLabel],
expectedNetworkLabel,
name,
)
}
return nil
hash := inspect.Labels[api.ConfigHashLabel]
expected, err := NetworkHash(n)
if err != nil {
return "", err
}
if hash == "" || hash == expected {
return inspect.ID, nil
}
err = s.removeDivergedNetwork(ctx, project, name, n)
if err != nil {
return "", err
}
}
}
// ignore other errors. Typically, an ambiguous request by name results in some generic `invalidParameter` error
@@ -1251,7 +1264,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
Filters: filters.NewArgs(filters.Arg("name", n.Name)),
})
if err != nil {
return err
return "", err
}
// NetworkList Matches all or part of a network name, so we have to filter for a strict match
@@ -1260,9 +1273,9 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
})
for _, net := range networks {
if net.Labels[api.ProjectLabel] == expectedProjectLabel &&
net.Labels[api.NetworkLabel] == expectedNetworkLabel {
return nil
if net.Labels[api.ProjectLabel] == project.Name &&
net.Labels[api.NetworkLabel] == name {
return net.ID, nil
}
}
@@ -1272,7 +1285,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
if len(networks) > 0 {
logrus.Warnf("a network with name %s exists but was not created by compose.\n"+
"Set `external: true` to use an existing network", n.Name)
return nil
return networks[0].ID, nil
}
var ipam *network.IPAM
@@ -1291,8 +1304,13 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
Config: config,
}
}
hash, err := NetworkHash(n)
if err != nil {
return "", err
}
n.CustomLabels = n.CustomLabels.Add(api.ConfigHashLabel, hash)
createOpts := network.CreateOptions{
Labels: n.Labels,
Labels: mergeLabels(n.Labels, n.CustomLabels),
Driver: n.Driver,
Options: n.DriverOpts,
Internal: n.Internal,
@@ -1322,16 +1340,42 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(networkEventName))
_, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
resp, err := s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
if err != nil {
w.Event(progress.ErrorEvent(networkEventName))
return fmt.Errorf("failed to create network %s: %w", n.Name, err)
return "", fmt.Errorf("failed to create network %s: %w", n.Name, err)
}
w.Event(progress.CreatedEvent(networkEventName))
return nil
return resp.ID, nil
}
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) error {
func (s *composeService) removeDivergedNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) error {
// Remove services attached to this network to force recreation
var services []string
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
_, ok := config.Networks[name]
return ok
}) {
services = append(services, service.Name)
}
// Stop containers so we can remove network
// They will be restarted (actually: recreated) with the updated network
err := s.stop(ctx, project.Name, api.StopOptions{
Services: services,
Project: project,
})
if err != nil {
return err
}
err = s.apiClient().NetworkRemove(ctx, n.Name)
eventName := fmt.Sprintf("Network %s", n.Name)
progress.ContextWriter(ctx).Event(progress.RemovedEvent(eventName))
return err
}
func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.NetworkConfig) (string, error) {
// NetworkInspect will match on ID prefix, so NetworkList with a name
// filter is used to look for an exact match to prevent e.g. a network
// named `db` from getting erroneously matched to a network with an ID
@@ -1341,14 +1385,14 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
})
if err != nil {
return err
return "", err
}
if len(networks) == 0 {
// in this instance, n.Name is really an ID
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
if err != nil && !errdefs.IsNotFound(err) {
return err
return "", err
}
networks = append(networks, sn)
}
@@ -1363,22 +1407,22 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
switch len(networks) {
case 1:
return nil
return networks[0].ID, nil
case 0:
enabled, err := s.isSWarmEnabled(ctx)
if err != nil {
return err
return "", err
}
if enabled {
// Swarm nodes do not register overlay networks that were
// created on a different node unless they're in use.
// So we can't preemptively check network exists, but
// networkAttach will later fail anyway if network actually doesn't exist
return nil
return "swarm", nil
}
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
return "", fmt.Errorf("network %s declared as external, but could not be found", n.Name)
default:
return fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
return "", fmt.Errorf("multiple networks with name %q were found. Use network ID as `name` to avoid ambiguity", n.Name)
}
}

View File

@@ -92,7 +92,7 @@ func TestPrepareNetworkLabels(t *testing.T) {
Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{"skynet": {}}),
}
prepareNetworks(&project)
assert.DeepEqual(t, project.Networks["skynet"].Labels, composetypes.Labels(map[string]string{
assert.DeepEqual(t, project.Networks["skynet"].CustomLabels, composetypes.Labels(map[string]string{
"com.docker.compose.network": "skynet",
"com.docker.compose.project": "myProject",
"com.docker.compose.version": api.ComposeVersion,

View File

@@ -33,6 +33,7 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
o.Deploy.Replicas = nil
}
o.DependsOn = nil
o.Profiles = nil
bytes, err := json.Marshal(o)
if err != nil {
@@ -40,3 +41,11 @@ func ServiceHash(o types.ServiceConfig) (string, error) {
}
return digest.SHA256.FromBytes(bytes).Encoded(), nil
}
func NetworkHash(o *types.NetworkConfig) (string, error) {
bytes, err := json.Marshal(o)
if err != nil {
return "", err
}
return digest.SHA256.FromBytes(bytes).Encoded(), nil
}

View File

@@ -55,20 +55,19 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
containers = allContainers
}
imageIDs := []string{}
// aggregate image IDs
images := []string{}
for _, c := range containers {
if !utils.StringContains(imageIDs, c.ImageID) {
imageIDs = append(imageIDs, c.ImageID)
if !utils.StringContains(images, c.Image) {
images = append(images, c.Image)
}
}
images, err := s.getImages(ctx, imageIDs)
imageSummaries, err := s.getImageSummaries(ctx, images)
if err != nil {
return nil, err
}
summary := make([]api.ImageSummary, len(containers))
for i, container := range containers {
img, ok := images[container.ImageID]
img, ok := imageSummaries[container.Image]
if !ok {
return nil, fmt.Errorf("failed to retrieve image for container %s", getCanonicalContainerName(container))
}
@@ -79,34 +78,32 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
return summary, nil
}
func (s *composeService) getImages(ctx context.Context, images []string) (map[string]api.ImageSummary, error) {
func (s *composeService) getImageSummaries(ctx context.Context, repoTags []string) (map[string]api.ImageSummary, error) {
summary := map[string]api.ImageSummary{}
l := sync.Mutex{}
eg, ctx := errgroup.WithContext(ctx)
for _, img := range images {
img := img
for _, repoTag := range repoTags {
repoTag := repoTag
eg.Go(func() error {
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, img)
inspect, _, err := s.apiClient().ImageInspectWithRaw(ctx, repoTag)
if err != nil {
if errdefs.IsNotFound(err) {
return nil
}
return fmt.Errorf("unable to get image '%s': %w", img, err)
return fmt.Errorf("unable to get image '%s': %w", repoTag, err)
}
tag := ""
repository := ""
if len(inspect.RepoTags) > 0 {
ref, err := reference.ParseDockerRef(inspect.RepoTags[0])
if err != nil {
return err
}
repository = reference.FamiliarName(ref)
if tagged, ok := ref.(reference.Tagged); ok {
tag = tagged.Tag()
}
ref, err := reference.ParseDockerRef(repoTag)
if err != nil {
return err
}
repository = reference.FamiliarName(ref)
if tagged, ok := ref.(reference.Tagged); ok {
tag = tagged.Tag()
}
l.Lock()
summary[img] = api.ImageSummary{
summary[repoTag] = api.ImageSummary{
ID: inspect.ID,
Repository: repository,
Tag: tag,

103
pkg/compose/images_test.go Normal file
View File

@@ -0,0 +1,103 @@
/*
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"
"strings"
"testing"
containerType "github.com/docker/docker/api/types/container"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
compose "github.com/docker/compose/v2/pkg/api"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)
func TestImages(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
api, cli := prepareMocks(mockCtrl)
tested := composeService{
dockerCli: cli,
}
ctx := context.Background()
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
listOpts := containerType.ListOptions{All: true, Filters: args}
image1 := imageInspect("image1", "foo:1", 12345)
image2 := imageInspect("image2", "bar:2", 67890)
api.EXPECT().ImageInspectWithRaw(anyCancellableContext(), "foo:1").Return(image1, nil, nil)
api.EXPECT().ImageInspectWithRaw(anyCancellableContext(), "bar:2").Return(image2, nil, nil)
c1 := containerDetail("service1", "123", "running", "foo:1")
c2 := containerDetail("service1", "456", "running", "bar:2")
c2.Ports = []moby.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c3 := containerDetail("service2", "789", "exited", "foo:1")
api.EXPECT().ContainerList(ctx, listOpts).Return([]moby.Container{c1, c2, c3}, nil)
images, err := tested.Images(ctx, strings.ToLower(testProject), compose.ImagesOptions{})
expected := []compose.ImageSummary{
{
ID: "image1",
ContainerName: "123",
Repository: "foo",
Tag: "1",
Size: 12345,
},
{
ID: "image2",
ContainerName: "456",
Repository: "bar",
Tag: "2",
Size: 67890,
},
{
ID: "image1",
ContainerName: "789",
Repository: "foo",
Tag: "1",
Size: 12345,
},
}
assert.NilError(t, err)
assert.DeepEqual(t, images, expected)
}
func imageInspect(id string, image string, size int64) moby.ImageInspect {
return moby.ImageInspect{
ID: id,
RepoTags: []string{
"someRepo:someTag",
image,
},
Size: size,
}
}
func containerDetail(service string, id string, status string, image string) moby.Container {
return moby.Container{
ID: id,
Names: []string{"/" + id},
Image: image,
Labels: containerLabels(service, false),
State: status,
}
}

View File

@@ -81,6 +81,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options
_, _ = fmt.Fprintln(s.stdinfo(), "No stopped containers")
return nil
}
msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", "))
if options.Force {
_, _ = fmt.Fprintln(s.stdout(), msg)

View File

@@ -104,7 +104,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
Labels: mergeLabels(service.Labels, service.CustomLabels),
}
err = newConvergence(project.ServiceNames(), observedState, s).resolveServiceReferences(&service)
err = newConvergence(project.ServiceNames(), observedState, nil, s).resolveServiceReferences(&service)
if err != nil {
return "", err
}

View File

@@ -214,13 +214,13 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
}
var (
expected []string
expected = utils.NewSet[string]()
watched = map[string]int{}
replaced []string
)
for _, c := range containers {
if isRequired(c) {
expected = append(expected, c.ID)
expected.Add(c.ID)
}
watched[c.ID] = 0
}
@@ -242,7 +242,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
// be able to inspect in time before they're gone from the
// API, so just remove the watch without erroring
delete(watched, event.Container)
expected = utils.Remove(expected, event.Container)
expected.Remove(event.Container)
return nil
}
return err
@@ -253,7 +253,6 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
Labels: inspected.Config.Labels,
}
name := getContainerNameWithoutProject(container)
service := container.Labels[api.ServiceLabel]
switch event.Status {
case "stop":
@@ -278,7 +277,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
}
delete(watched, container.ID)
expected = utils.Remove(expected, container.ID)
expected.Remove(container.ID)
case "die":
restarted := watched[container.ID]
watched[container.ID] = restarted + 1
@@ -308,7 +307,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
if !willRestart {
// we're done with this one
delete(watched, container.ID)
expected = utils.Remove(expected, container.ID)
expected.Remove(container.ID)
}
case "start":
count, ok := watched[container.ID]
@@ -316,7 +315,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
if !ok {
// A new container has just been added to service by scale
watched[container.ID] = 0
expected = append(expected, container.ID)
expected.Add(container.ID)
mustAttach = true
}
if mustAttach {
@@ -333,17 +332,15 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
if err != nil {
return err
}
if utils.StringContains(expected, id) {
expected = append(expected, inspected.ID)
if expected.Has(id) {
expected.Add(inspected.ID)
expected.Add(container.ID)
}
watched[container.ID] = 1
if utils.Contains(expected, id) {
expected = append(expected, container.ID)
}
} else if ofInterest(container) {
watched[container.ID] = 1
if isRequired(container) {
expected = append(expected, container.ID)
expected.Add(container.ID)
}
}
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/compose-spec/compose-go/v2/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
compose "github.com/docker/compose/v2/pkg/api"
@@ -128,7 +129,7 @@ func TestViz(t *testing.T) {
IncludeImageName: false,
IncludeNetworks: false,
})
assert.NoError(t, err, "viz command failed")
require.NoError(t, err, "viz command failed")
// check indentation
assert.Contains(t, graphStr, "\n ", graphStr)
@@ -187,7 +188,7 @@ func TestViz(t *testing.T) {
IncludeImageName: true,
IncludeNetworks: true,
})
assert.NoError(t, err, "viz command failed")
require.NoError(t, err, "viz command failed")
// check indentation
assert.Contains(t, graphStr, "\n\t", graphStr)

View File

@@ -512,9 +512,15 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr
return err
}
services := []string{serviceName}
p, err := project.WithSelectedServices(services)
if err != nil {
return err
}
err = s.start(ctx, project.Name, api.StartOptions{
Project: project,
Services: []string{serviceName},
Project: p,
Services: services,
AttachTo: services,
}, nil)
if err != nil {
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Application failed to start after update. Error: %v", err))

View File

@@ -117,7 +117,6 @@ func TestWatch_Sync(t *testing.T) {
mockCtrl := gomock.NewController(t)
cli := mocks.NewMockCli(mockCtrl)
cli.EXPECT().Err().Return(streams.NewOut(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),

View File

@@ -33,8 +33,8 @@ import (
func TestLocalComposeBuild(t *testing.T) {
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1"} {
c := NewCLI(t, WithEnv(env))
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1", "DOCKER_BUILDKIT=1,COMPOSE-BAKE=1"} {
c := NewCLI(t, WithEnv(strings.Split(env, ",")...))
t.Run(env+" build named and unnamed images", func(t *testing.T) {
// ensure local test run does not reuse previously build image

93
pkg/e2e/commit_test.go Normal file
View File

@@ -0,0 +1,93 @@
/*
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 e2e
import (
"testing"
)
func TestCommit(t *testing.T) {
const projectName = "e2e-commit-service"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service")
c.RunDockerComposeCmd(
t,
"--project-name",
projectName,
"commit",
"-a",
"John Hannibal Smith <hannibal@a-team.com>",
"-c",
"ENV DEBUG=true",
"-m",
"sample commit",
"service",
"service:latest",
)
}
func TestCommitWithReplicas(t *testing.T) {
const projectName = "e2e-commit-service-with-replicas"
c := NewParallelCLI(t)
cleanup := func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
}
t.Cleanup(cleanup)
cleanup()
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service-with-replicas")
c.RunDockerComposeCmd(
t,
"--project-name",
projectName,
"commit",
"-a",
"John Hannibal Smith <hannibal@a-team.com>",
"-c",
"ENV DEBUG=true",
"-m",
"sample commit",
"--index=1",
"service-with-replicas",
"service-with-replicas:1",
)
c.RunDockerComposeCmd(
t,
"--project-name",
projectName,
"commit",
"-a",
"John Hannibal Smith <hannibal@a-team.com>",
"-c",
"ENV DEBUG=true",
"-m",
"sample commit",
"--index=2",
"service-with-replicas",
"service-with-replicas:2",
)
}

View File

@@ -1,106 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/require"
"gotest.tools/v3/assert"
)
const ddevVersion = "v1.21.1"
func TestComposeRunDdev(t *testing.T) {
if !composeStandaloneMode {
t.Skip("Not running in plugin mode - ddev only supports invoking standalone `docker-compose`")
}
if runtime.GOOS == "windows" {
t.Skip("Running on Windows. Skipping...")
}
// ddev shells out to `docker` and `docker-compose` (standalone), so a
// temporary directory is created with symlinks to system Docker and the
// locally-built standalone Compose binary to use as PATH
requiredTools := []string{
findToolInPath(t, DockerExecutableName),
ComposeStandalonePath(t),
findToolInPath(t, "tar"),
findToolInPath(t, "gzip"),
}
pathDir := t.TempDir()
for _, tool := range requiredTools {
require.NoError(t, os.Symlink(tool, filepath.Join(pathDir, filepath.Base(tool))),
"Could not create symlink for %q", tool)
}
c := NewCLI(t, WithEnv(
"DDEV_DEBUG=true",
fmt.Sprintf("PATH=%s", pathDir),
))
ddevDir := t.TempDir()
siteName := filepath.Base(ddevDir)
t.Cleanup(func() {
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "delete", "-Oy")
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
})
osName := "linux"
if runtime.GOOS == "darwin" {
osName = "macos"
}
compressedFilename := fmt.Sprintf("ddev_%s-%s.%s.tar.gz", osName, runtime.GOARCH, ddevVersion)
c.RunCmdInDir(t, ddevDir, "curl", "-LO", fmt.Sprintf("https://github.com/ddev/ddev/releases/download/%s/%s",
ddevVersion,
compressedFilename))
c.RunCmdInDir(t, ddevDir, "tar", "-xzf", compressedFilename)
// Create a simple index.php we can test against.
c.RunCmdInDir(t, ddevDir, "sh", "-c", "echo '<?php\nprint \"ddev is working\";' >index.php")
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "--auto")
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "global", "--use-docker-compose-from-path")
vRes := c.RunCmdInDir(t, ddevDir, "./ddev", "version")
out := vRes.Stdout()
fmt.Printf("ddev version: %s\n", out)
c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
c.RunCmdInDir(t, ddevDir, "./ddev", "start", "-y")
curlRes := c.RunCmdInDir(t, ddevDir, "curl", "-sSL", fmt.Sprintf("http://%s.ddev.site", siteName))
out = curlRes.Stdout()
fmt.Println(out)
assert.Assert(t, strings.Contains(out, "ddev is working"), "Could not start project")
}
func findToolInPath(t testing.TB, name string) string {
t.Helper()
binPath, err := exec.LookPath(name)
require.NoError(t, err, "Could not find %q in path", name)
return binPath
}

View File

@@ -0,0 +1,9 @@
services:
service:
image: alpine
command: sleep infinity
service-with-replicas:
image: alpine
command: sleep infinity
deploy:
replicas: 3

View File

@@ -0,0 +1,12 @@
services:
test:
image: nginx:alpine
networks:
- test
networks:
test:
ipam:
config:
- subnet: ${SUBNET-172.99.0.0/16}

View File

@@ -0,0 +1,15 @@
services:
foo:
container_name: foo_c
profiles: [ test ]
image: alpine
depends_on: [ db ]
bar:
container_name: bar_c
profiles: [ test ]
image: alpine
db:
container_name: db_c
image: alpine

View File

@@ -0,0 +1,17 @@
# 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.
FROM nginx:alpine
ARG FOO
LABEL FOO=$FOO

View File

@@ -0,0 +1,3 @@
services:
test:
build: .

View File

@@ -147,3 +147,28 @@ func TestNetworkModes(t *testing.T) {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
}
func TestNetworkConfigChanged(t *testing.T) {
// fixture is shared with TestNetworks and is not safe to run concurrently
c := NewCLI(t)
const projectName = "network_config_change"
c.RunDockerComposeCmd(t, "-f", "./fixtures/network-test/compose.subnet.yaml", "--project-name", projectName, "up", "-d")
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "hostname", "-i")
res.Assert(t, icmd.Expected{Out: "172.99.0."})
res.Combined()
cmd := c.NewCmdWithEnv([]string{"SUBNET=192.168.0.0/16"},
"docker", "compose", "-f", "./fixtures/network-test/compose.subnet.yaml", "--project-name", projectName, "up", "-d")
res = icmd.RunCmd(cmd)
res.Assert(t, icmd.Success)
out := res.Combined()
fmt.Println(out)
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "hostname", "-i")
res.Assert(t, icmd.Expected{Out: "192.168.0."})
}

View File

@@ -33,18 +33,17 @@ func TestPs(t *testing.T) {
const projectName = "e2e-ps"
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "up", "-d")
if assert.NoError(t, res.Error) {
t.Cleanup(func() {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
}
require.NoError(t, res.Error)
t.Cleanup(func() {
_ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
})
assert.Contains(t, res.Combined(), "Container e2e-ps-busybox-1 Started", res.Combined())
t.Run("table", func(t *testing.T) {
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps")
lines := strings.Split(res.Stdout(), "\n")
assert.Equal(t, 4, len(lines))
assert.Len(t, lines, 4)
count := 0
for _, line := range lines[1:3] {
if strings.Contains(line, "e2e-ps-busybox-1") {
@@ -77,12 +76,12 @@ func TestPs(t *testing.T) {
}
count := 0
assert.Equal(t, 2, len(output))
assert.Len(t, output, 2)
for _, service := range output {
assert.Equal(t, projectName, service.Project)
publishers := service.Publishers
if service.Name == "e2e-ps-busybox-1" {
assert.Equal(t, 1, len(publishers))
assert.Len(t, publishers, 1)
assert.Equal(t, api.PortPublishers{
{
URL: "127.0.0.1",
@@ -94,7 +93,7 @@ func TestPs(t *testing.T) {
count++
}
if service.Name == "e2e-ps-nginx-1" {
assert.Equal(t, 3, len(publishers))
assert.Len(t, publishers, 3)
assert.Equal(t, api.PortPublishers{
{TargetPort: 80, Protocol: "tcp"},
{TargetPort: 443, Protocol: "tcp"},
@@ -109,20 +108,20 @@ func TestPs(t *testing.T) {
t.Run("ps --all", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "stop")
assert.NoError(t, res.Error)
require.NoError(t, res.Error)
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps")
lines := strings.Split(res.Stdout(), "\n")
assert.Equal(t, 2, len(lines))
assert.Len(t, lines, 2)
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps", "--all")
lines = strings.Split(res.Stdout(), "\n")
assert.Equal(t, 4, len(lines))
assert.Len(t, lines, 4)
})
t.Run("ps unknown", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "stop")
assert.NoError(t, res.Error)
require.NoError(t, res.Error)
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "ps", "nginx")
res.Assert(t, icmd.Success)

View File

@@ -184,3 +184,34 @@ func checkServiceContainer(t *testing.T, stdout, containerName, containerState s
}
testify.Fail(t, errMessage, stdout)
}
func TestScaleDownNoRecreate(t *testing.T) {
const projectName = "scale-down-recreated-test"
c := NewCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME="+projectName))
reset := func() {
c.RunDockerComposeCmd(t, "down", "--rmi", "all")
}
t.Cleanup(reset)
c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "build", "--build-arg", "FOO=test")
c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "up", "-d", "--scale", "test=2")
c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "build", "--build-arg", "FOO=updated")
c.RunDockerComposeCmd(t, "-f", "fixtures/scale/build.yaml", "up", "-d", "--scale", "test=4", "--no-recreate")
res := c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "test")
res.Assert(t, icmd.Success)
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-1"))
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-2"))
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-3"))
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-4"))
t.Log("scale down removes obsolete replica #1 and #2")
c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "test=2")
res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "test")
res.Assert(t, icmd.Success)
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-3"))
assert.Check(t, strings.Contains(res.Stdout(), "scale-down-recreated-test-test-4"))
}

View File

@@ -98,7 +98,7 @@ func TestUpDependenciesNotStopped(t *testing.T) {
if exitErr.ExitCode() == -1 {
t.Fatalf("`compose up` was killed: %v", err)
}
require.EqualValues(t, exitErr.ExitCode(), 130)
require.EqualValues(t, 130, exitErr.ExitCode())
}
RequireServiceState(t, c, "app", "exited")
@@ -179,3 +179,16 @@ func TestUpWithAllResources(t *testing.T) {
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf(`Volume "%s_my_vol" Created`, projectName)), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf(`Network %s_my_net Created`, projectName)), res.Combined())
}
func TestUpProfile(t *testing.T) {
c := NewCLI(t)
const projectName = "compose-e2e-up-profile"
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "--project-name", projectName, "--profile", "test", "down", "-v")
})
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/profiles/docker-compose.yaml", "--project-name", projectName, "up", "foo")
assert.Assert(t, strings.Contains(res.Combined(), `Container db_c Created`), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), `Container foo_c Created`), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), `Container bar_c Created`), res.Combined())
}

View File

@@ -69,6 +69,20 @@ func (mr *MockServiceMockRecorder) Build(ctx, project, options any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockService)(nil).Build), ctx, project, options)
}
// Commit mocks base method.
func (m *MockService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Commit", ctx, projectName, options)
ret0, _ := ret[0].(error)
return ret0
}
// Commit indicates an expected call of Commit.
func (mr *MockServiceMockRecorder) Commit(ctx, projectName, options any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockService)(nil).Commit), ctx, projectName, options)
}
// Copy mocks base method.
func (m *MockService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
m.ctrl.T.Helper()

View File

@@ -153,6 +153,16 @@ func RemovedEvent(id string) Event {
return NewEvent(id, Done, "Removed")
}
// BuildingEvent creates a new Building in progress Event
func BuildingEvent(id string) Event {
return NewEvent(id, Working, "Building")
}
// BuiltEvent creates a new built (done) Event
func BuiltEvent(id string) Event {
return NewEvent(id, Done, "Built")
}
// SkippedEvent creates a new Skipped Event
func SkippedEvent(id string, reason string) Event {
return Event{

View File

@@ -19,6 +19,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/compose/v2/pkg/watch"
)
@@ -36,14 +37,12 @@ func TestEphemeralPathMatcher(t *testing.T) {
matcher := watch.EphemeralPathMatcher()
for _, p := range ignored {
ok, err := matcher.Matches(p)
if assert.NoErrorf(t, err, "Matching %s", p) {
assert.Truef(t, ok, "Path %s should have matched", p)
}
require.NoErrorf(t, err, "Matching %s", p)
assert.Truef(t, ok, "Path %s should have matched", p)
}
const includedPath = "normal.txt"
ok, err := matcher.Matches(includedPath)
if assert.NoErrorf(t, err, "Matching %s", includedPath) {
assert.Falsef(t, ok, "Path %s should NOT have matched", includedPath)
}
require.NoErrorf(t, err, "Matching %s", includedPath)
assert.Falsef(t, ok, "Path %s should NOT have matched", includedPath)
}

View File

@@ -39,15 +39,15 @@ func TestWindowsBufferSize(t *testing.T) {
defer os.Setenv(WindowsBufferSizeEnvVar, orig) //nolint:errcheck
err := os.Setenv(WindowsBufferSizeEnvVar, "")
assert.Nil(t, err)
require.NoError(t, err)
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
err = os.Setenv(WindowsBufferSizeEnvVar, "a")
assert.Nil(t, err)
require.NoError(t, err)
assert.Equal(t, defaultBufferSize, DesiredWindowsBufferSize())
err = os.Setenv(WindowsBufferSizeEnvVar, "10")
assert.Nil(t, err)
require.NoError(t, err)
assert.Equal(t, 10, DesiredWindowsBufferSize())
}

View File

@@ -21,17 +21,18 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGreatestExistingAncestor(t *testing.T) {
f := NewTempDirFixture(t)
p, err := greatestExistingAncestor(f.Path())
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, f.Path(), p)
p, err = greatestExistingAncestor(f.JoinPath("missing"))
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, f.Path(), p)
missingTopLevel := "/missingDir/a/b/c"