Compare commits

..

No commits in common. "main" and "v2.37.0" have entirely different histories.

143 changed files with 1328 additions and 3457 deletions

View File

@ -12,12 +12,6 @@ body:
Include both the current behavior (what you are seeing) as well as what you expected to happen. Include both the current behavior (what you are seeing) as well as what you expected to happen.
validations: validations:
required: true required: true
- type: markdown
attributes:
value: |
[Docker Swarm](https://www.mirantis.com/software/swarm/) uses a distinct compose file parser and
as such doesn't support some of the recent features of Docker Compose. Please contact Mirantis
if you need assistance with compose file support in Docker Swarm.
- type: textarea - type: textarea
attributes: attributes:
label: Steps To Reproduce label: Steps To Reproduce

View File

@ -183,11 +183,6 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set up Docker Model
run: |
sudo apt-get install docker-model-plugin
docker model version
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
@ -195,9 +190,6 @@ jobs:
check-latest: true check-latest: true
cache: true cache: true
- name: Build example provider
run: make example-provider
- name: Build - name: Build
uses: docker/bake-action@v6 uses: docker/bake-action@v6
with: with:

View File

@ -30,8 +30,6 @@ linters:
deny: deny:
- pkg: io/ioutil - pkg: io/ioutil
desc: io/ioutil package has been deprecated desc: io/ioutil package has been deprecated
- pkg: github.com/docker/docker/errdefs
desc: use github.com/containerd/errdefs instead.
- pkg: golang.org/x/exp/maps - pkg: golang.org/x/exp/maps
desc: use stdlib maps package desc: use stdlib maps package
- pkg: golang.org/x/exp/slices - pkg: golang.org/x/exp/slices

View File

@ -4,15 +4,12 @@
* Windows: * Windows:
* [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/) * [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/)
* make * make
* go (see [go.mod](go.mod) for minimum version)
* macOS: * macOS:
* [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/) * [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/)
* make * make
* go (see [go.mod](go.mod) for minimum version)
* Linux: * Linux:
* [Docker 20.10 or later](https://docs.docker.com/engine/install/) * [Docker 20.10 or later](https://docs.docker.com/engine/install/)
* make * make
* go (see [go.mod](go.mod) for minimum version)
### Building the CLI ### Building the CLI

View File

@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG GO_VERSION=1.24.7 ARG GO_VERSION=1.23.8
ARG XX_VERSION=1.6.1 ARG XX_VERSION=1.6.1
ARG GOLANGCI_LINT_VERSION=v2.0.2 ARG GOLANGCI_LINT_VERSION=v2.0.2
ARG ADDLICENSE_VERSION=v1.0.0 ARG ADDLICENSE_VERSION=v1.0.0

View File

@ -74,7 +74,7 @@ install: binary
install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose
.PHONY: e2e-compose .PHONY: e2e-compose
e2e-compose: example-provider ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
.PHONY: e2e-compose-standalone .PHONY: e2e-compose-standalone
@ -87,10 +87,6 @@ build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and r
.PHONY: build-and-e2e-compose-standalone .PHONY: build-and-e2e-compose-standalone
build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
.PHONY: example-provider
example-provider: ## build example provider for e2e tests
go build -o bin/build/example-provider docs/examples/provider.go
.PHONY: mocks .PHONY: mocks
mocks: mocks:
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0 mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0

View File

@ -23,12 +23,6 @@ your application are configured.
Once you have a Compose file, you can create and start your application with a Once you have a Compose file, you can create and start your application with a
single command: `docker compose up`. single command: `docker compose up`.
> **Note**: About Docker Swarm
> Docker Swarm used to rely on the legacy compose file format but did not adopted the compose specification
> so is missing some of the recent enhancements in the compose syntax. After
> [acquisition by Mirantis](https://www.mirantis.com/software/swarm/) swarm isn't maintained by Docker Inc, and
> as such some Docker Compose features aren't accessible to swarm users.
# Where to get Docker Compose # Where to get Docker Compose
### Windows and macOS ### Windows and macOS

View File

@ -55,10 +55,8 @@ func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
ctx, ctx,
"cli/"+strings.Join(commandName(cmd), "-"), "cli/"+strings.Join(commandName(cmd), "-"),
) )
cmdSpan.SetAttributes( cmdSpan.SetAttributes(attribute.StringSlice("cli.flags", getFlags(cmd.Flags())))
attribute.StringSlice("cli.flags", getFlags(cmd.Flags())), cmdSpan.SetAttributes(attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()))
attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()),
)
cmd.SetContext(ctx) cmd.SetContext(ctx)
wrapRunE(cmd, cmdSpan, tracingShutdown) wrapRunE(cmd, cmdSpan, tracingShutdown)

View File

@ -45,8 +45,7 @@ type buildOptions struct {
deps bool deps bool
print bool print bool
check bool check bool
sbom string provenance bool
provenance string
} }
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) { func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
@ -85,7 +84,6 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
Check: opts.check, Check: opts.check,
SSHs: SSHKeys, SSHs: SSHKeys,
Builder: builderName, Builder: builderName,
SBOM: opts.sbom,
Provenance: opts.provenance, Provenance: opts.provenance,
}, nil }, nil
} }
@ -121,14 +119,12 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.BoolVar(&opts.push, "push", false, "Push service images") flags.BoolVar(&opts.push, "push", false, "Push service images")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the build output") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image") flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services") flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services")
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)") flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
flags.StringVar(&opts.builder, "builder", "", "Set builder to use") flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)") flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
flags.StringVar(&opts.provenance, "provenance", "", `Add a provenance attestation`)
flags.StringVar(&opts.sbom, "sbom", "", `Add a SBOM attestation`)
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED") flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
flags.MarkHidden("parallel") //nolint:errcheck flags.MarkHidden("parallel") //nolint:errcheck
@ -149,7 +145,6 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
} }
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error { func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
opts.All = true // do not drop resources as build may involve some dependencies by additional_contexts
project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
if err != nil { if err != nil {
return err return err
@ -160,7 +155,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
} }
apiBuildOptions, err := opts.toAPIBuildOptions(services) apiBuildOptions, err := opts.toAPIBuildOptions(services)
apiBuildOptions.Attestations = true apiBuildOptions.Provenance = true
if err != nil { if err != nil {
return err return err
} }

View File

@ -36,7 +36,7 @@ import (
composegoutils "github.com/compose-spec/compose-go/v2/utils" composegoutils "github.com/compose-spec/compose-go/v2/utils"
"github.com/docker/buildx/util/logutil" "github.com/docker/buildx/util/logutil"
dockercli "github.com/docker/cli/cli" dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/pkg/kvfile" "github.com/docker/cli/pkg/kvfile"
"github.com/docker/compose/v2/cmd/formatter" "github.com/docker/compose/v2/cmd/formatter"
@ -245,7 +245,7 @@ func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cl
name := o.ProjectName name := o.ProjectName
var project *types.Project var project *types.Project
if len(o.ConfigPaths) > 0 || o.ProjectName == "" { if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution) p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile)
if err != nil { if err != nil {
envProjectName := os.Getenv(ComposeProjectName) envProjectName := os.Getenv(ComposeProjectName)
if envProjectName != "" { if envProjectName != "" {
@ -416,7 +416,7 @@ const PluginName = "compose"
// RunningAsStandalone detects when running as a standalone program // RunningAsStandalone detects when running as a standalone program
func RunningAsStandalone() bool { func RunningAsStandalone() bool {
return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
} }
// RootCommand returns the compose command with its child commands // RootCommand returns the compose command with its child commands
@ -636,7 +636,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
publishCommand(&opts, dockerCli, backend), publishCommand(&opts, dockerCli, backend),
alphaCommand(&opts, dockerCli, backend), alphaCommand(&opts, dockerCli, backend),
bridgeCommand(&opts, dockerCli), bridgeCommand(&opts, dockerCli),
volumesCommand(&opts, dockerCli, backend),
) )
c.Flags().SetInterspersed(false) c.Flags().SetInterspersed(false)
@ -661,10 +660,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
"profile", "profile",
completeProfileNames(dockerCli, &opts), completeProfileNames(dockerCli, &opts),
) )
c.RegisterFlagCompletionFunc( //nolint:errcheck
"progress",
cobra.FixedCompletions(printerModes, cobra.ShellCompDirectiveNoFileComp),
)
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`) c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
c.Flags().IntVar(&parallel, "parallel", -1, `Control max parallelism, -1 for unlimited`) c.Flags().IntVar(&parallel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
@ -693,7 +688,7 @@ func setEnvWithDotEnv(opts ProjectOptions) error {
return nil return nil
} }
for k, v := range envFromFile { for k, v := range envFromFile {
if _, ok := os.LookupEnv(k); !ok && strings.HasPrefix(k, "COMPOSE_") { if _, ok := os.LookupEnv(k); !ok {
if err = os.Setenv(k, v); err != nil { if err = os.Setenv(k, v); err != nil {
return nil return nil
} }

View File

@ -50,8 +50,6 @@ type configOptions struct {
noResolveEnv bool noResolveEnv bool
services bool services bool
volumes bool volumes bool
networks bool
models bool
profiles bool profiles bool
images bool images bool
hash string hash string
@ -113,12 +111,6 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
if opts.volumes { if opts.volumes {
return runVolumes(ctx, dockerCli, opts) return runVolumes(ctx, dockerCli, opts)
} }
if opts.networks {
return runNetworks(ctx, dockerCli, opts)
}
if opts.models {
return runModels(ctx, dockerCli, opts)
}
if opts.hash != "" { if opts.hash != "" {
return runHash(ctx, dockerCli, opts) return runHash(ctx, dockerCli, opts)
} }
@ -155,8 +147,6 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.") flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.") flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
flags.BoolVar(&opts.networks, "networks", false, "Print the network names, one per line.")
flags.BoolVar(&opts.models, "models", false, "Print the model names, one per line.")
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.") flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.") flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.") flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
@ -377,30 +367,6 @@ func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions)
return nil return nil
} }
func runNetworks(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
for n := range project.Networks {
_, _ = fmt.Fprintln(dockerCli.Out(), n)
}
return nil
}
func runModels(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
for _, model := range project.Models {
if model.Model != "" {
_, _ = fmt.Fprintln(dockerCli.Out(), model.Model)
}
}
return nil
}
func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error { func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
var services []string var services []string
if opts.hash != "*" { if opts.hash != "*" {

View File

@ -29,9 +29,7 @@ import (
type eventsOpts struct { type eventsOpts struct {
*composeOptions *composeOptions
json bool json bool
since string
until string
} }
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
@ -50,8 +48,6 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
} }
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects") cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
cmd.Flags().StringVar(&opts.since, "since", "", "Show all events created since timestamp")
cmd.Flags().StringVar(&opts.until, "until", "", "Stream events until this timestamp")
return cmd return cmd
} }
@ -63,8 +59,6 @@ func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service,
return backend.Events(ctx, name, api.EventsOptions{ return backend.Events(ctx, name, api.EventsOptions{
Services: services, Services: services,
Since: opts.since,
Until: opts.until,
Consumer: func(event api.Event) error { Consumer: func(event api.Event) error {
if opts.json { if opts.json {
marshal, err := json.Marshal(map[string]interface{}{ marshal, err := json.Marshal(map[string]interface{}{

View File

@ -18,18 +18,13 @@ package compose
import ( import (
"context" "context"
"errors"
"fmt"
"os"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose" "github.com/docker/compose/v2/pkg/compose"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
type execOpts struct { type execOpts struct {
@ -64,15 +59,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
return nil return nil
}), }),
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
err := runExec(ctx, dockerCli, backend, opts) return runExec(ctx, dockerCli, backend, opts)
if err != nil {
logrus.Debugf("%v", err)
var cliError cli.StatusError
if ok := errors.As(err, &cliError); ok {
os.Exit(err.(cli.StatusError).StatusCode) //nolint: errorlint
}
}
return err
}), }),
ValidArgsFunction: completeServiceNames(dockerCli, p), ValidArgsFunction: completeServiceNames(dockerCli, p),
} }
@ -82,7 +69,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas") runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process") runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process")
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user") runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user")
runCmd.Flags().BoolVarP(&opts.noTty, "no-tty", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.") runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command") runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command")
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached") runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
@ -91,12 +78,6 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
runCmd.Flags().MarkHidden("tty") //nolint:errcheck runCmd.Flags().MarkHidden("tty") //nolint:errcheck
runCmd.Flags().SetInterspersed(false) runCmd.Flags().SetInterspersed(false)
runCmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
if name == "no-TTY" { // legacy
name = "no-tty"
}
return pflag.NormalizedName(name)
})
return runCmd return runCmd
} }
@ -128,8 +109,8 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
exitCode, err := backend.Exec(ctx, projectName, execOpts) exitCode, err := backend.Exec(ctx, projectName, execOpts)
if exitCode != 0 { if exitCode != 0 {
errMsg := fmt.Sprintf("exit status %d", exitCode) errMsg := ""
if err != nil && err.Error() != "" { if err != nil {
errMsg = err.Error() errMsg = err.Error()
} }
return cli.StatusError{StatusCode: exitCode, Status: errMsg} return cli.StatusError{StatusCode: exitCode, Status: errMsg}

View File

@ -87,41 +87,6 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
} }
return nil return nil
} }
if opts.Format == "json" {
type img struct {
ID string `json:"ID"`
ContainerName string `json:"ContainerName"`
Repository string `json:"Repository"`
Tag string `json:"Tag"`
Platform string `json:"Platform"`
Size int64 `json:"Size"`
LastTagTime time.Time `json:"LastTagTime"`
}
// Convert map to slice
var imageList []img
for ctr, i := range images {
lastTagTime := i.LastTagTime
if lastTagTime.IsZero() {
lastTagTime = i.Created
}
imageList = append(imageList, img{
ContainerName: ctr,
ID: i.ID,
Repository: i.Repository,
Tag: i.Tag,
Platform: platforms.Format(i.Platform),
Size: i.Size,
LastTagTime: lastTagTime,
})
}
json, err := formatter.ToJSON(imageList, "", "")
if err != nil {
return err
}
_, err = fmt.Fprintln(dockerCli.Out(), json)
return err
}
return formatter.Print(images, opts.Format, dockerCli.Out(), return formatter.Print(images, opts.Format, dockerCli.Out(),
func(w io.Writer) { func(w io.Writer) {

View File

@ -120,8 +120,8 @@ func (options runOptions) apply(project *types.Project) (*types.Project, error)
return project, nil return project, nil
} }
func (options runOptions) getEnvironment(resolve func(string) (string, bool)) (types.Mapping, error) { func (options runOptions) getEnvironment() (types.Mapping, error) {
environment := types.NewMappingWithEquals(options.environment).Resolve(resolve).ToMapping() environment := types.NewMappingWithEquals(options.environment).Resolve(os.LookupEnv).ToMapping()
for _, file := range options.envFiles { for _, file := range options.envFiles {
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
@ -271,6 +271,22 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
return err return err
} }
err = progress.Run(ctx, func(ctx context.Context) error {
var buildForDeps *api.BuildOptions
if !createOpts.noBuild {
// allow dependencies needing build to be implicitly selected
bo, err := buildOpts.toAPIBuildOptions(nil)
if err != nil {
return err
}
buildForDeps = &bo
}
return startDependencies(ctx, backend, *project, buildForDeps, options)
}, dockerCli.Err())
if err != nil {
return err
}
labels := types.Labels{} labels := types.Labels{}
for _, s := range options.labels { for _, s := range options.labels {
parts := strings.SplitN(s, "=", 2) parts := strings.SplitN(s, "=", 2)
@ -282,26 +298,23 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
var buildForRun *api.BuildOptions var buildForRun *api.BuildOptions
if !createOpts.noBuild { if !createOpts.noBuild {
bo, err := buildOpts.toAPIBuildOptions(nil) // dependencies have already been started above, so only the service
// being run might need to be built at this point
bo, err := buildOpts.toAPIBuildOptions([]string{options.Service})
if err != nil { if err != nil {
return err return err
} }
buildForRun = &bo buildForRun = &bo
} }
environment, err := options.getEnvironment(project.Environment.Resolve) environment, err := options.getEnvironment()
if err != nil { if err != nil {
return err return err
} }
// start container and attach to container streams // start container and attach to container streams
runOpts := api.RunOptions{ runOpts := api.RunOptions{
CreateOptions: api.CreateOptions{ Build: buildForRun,
Build: buildForRun,
RemoveOrphans: options.removeOrphans,
IgnoreOrphans: options.ignoreOrphans,
QuietPull: options.quietPull,
},
Name: options.name, Name: options.name,
Service: options.Service, Service: options.Service,
Command: options.Command, Command: options.Command,
@ -319,6 +332,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
UseNetworkAliases: options.useAliases, UseNetworkAliases: options.useAliases,
NoDeps: options.noDeps, NoDeps: options.noDeps,
Index: 0, Index: 0,
QuietPull: options.quietPull,
} }
for name, service := range project.Services { for name, service := range project.Services {
@ -338,3 +352,34 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
} }
return err return err
} }
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error {
dependencies := types.Services{}
var requestedService types.ServiceConfig
for name, service := range project.Services {
if name != options.Service {
dependencies[name] = service
} else {
requestedService = service
}
}
project.Services = dependencies
project.DisabledServices[options.Service] = requestedService
err := backend.Create(ctx, &project, api.CreateOptions{
Build: buildOpts,
IgnoreOrphans: options.ignoreOrphans,
RemoveOrphans: options.removeOrphans,
QuietPull: options.quietPull,
})
if err != nil {
return err
}
if len(dependencies) > 0 {
return backend.Start(ctx, project.Name, api.StartOptions{
Project: &project,
})
}
return nil
}

View File

@ -165,7 +165,6 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.") flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers") flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers")
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information") flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information")
flags.BoolVar(&build.quiet, "quiet-build", false, "Suppress the build output")
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.") flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services") flags.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.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services")
@ -224,7 +223,6 @@ func validateFlags(up *upOptions, create *createOptions) error {
return nil return nil
} }
//nolint:gocyclo
func runUp( func runUp(
ctx context.Context, ctx context.Context,
dockerCli command.Cli, dockerCli command.Cli,
@ -332,7 +330,7 @@ func runUp(
WaitTimeout: timeout, WaitTimeout: timeout,
Watch: upOptions.watch, Watch: upOptions.watch,
Services: services, Services: services,
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(), NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
}, },
}) })
} }

View File

@ -1,92 +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 compose
import (
"context"
"fmt"
"slices"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/pkg/api"
"github.com/spf13/cobra"
)
type volumesOptions struct {
*ProjectOptions
Quiet bool
Format string
}
func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
options := volumesOptions{
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "volumes [OPTIONS] [SERVICE...]",
Short: "List volumes",
RunE: Adapt(func(ctx context.Context, args []string) error {
return runVol(ctx, dockerCli, backend, args, options)
}),
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display volume names")
cmd.Flags().StringVar(&options.Format, "format", "table", flags.FormatHelp)
return cmd
}
func runVol(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, options volumesOptions) error {
project, name, err := options.projectOrName(ctx, dockerCli, services...)
if err != nil {
return err
}
if project != nil {
names := project.ServiceNames()
for _, service := range services {
if !slices.Contains(names, service) {
return fmt.Errorf("no such service: %s", service)
}
}
}
volumes, err := backend.Volumes(ctx, name, api.VolumesOptions{
Services: services,
})
if err != nil {
return err
}
if options.Quiet {
for _, v := range volumes {
_, _ = fmt.Fprintln(dockerCli.Out(), v.Name)
}
return nil
}
volumeCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewVolumeFormat(options.Format, options.Quiet),
}
return formatter.VolumeWrite(volumeCtx, volumes)
}

View File

@ -28,42 +28,49 @@ func ansi(code string) string {
return fmt.Sprintf("\033%s", code) return fmt.Sprintf("\033%s", code)
} }
func saveCursor() { func SaveCursor() {
if disableAnsi { if disableAnsi {
return return
} }
fmt.Print(ansi("7")) fmt.Print(ansi("7"))
} }
func restoreCursor() { func RestoreCursor() {
if disableAnsi { if disableAnsi {
return return
} }
fmt.Print(ansi("8")) fmt.Print(ansi("8"))
} }
func showCursor() { func HideCursor() {
if disableAnsi {
return
}
fmt.Print(ansi("[?25l"))
}
func ShowCursor() {
if disableAnsi { if disableAnsi {
return return
} }
fmt.Print(ansi("[?25h")) fmt.Print(ansi("[?25h"))
} }
func moveCursor(y, x int) { func MoveCursor(y, x int) {
if disableAnsi { if disableAnsi {
return return
} }
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x))) fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
} }
func carriageReturn() { func MoveCursorX(pos int) {
if disableAnsi { if disableAnsi {
return return
} }
fmt.Print(ansi(fmt.Sprintf("[%dG", 0))) fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
} }
func clearLine() { func ClearLine() {
if disableAnsi { if disableAnsi {
return return
} }
@ -71,7 +78,7 @@ func clearLine() {
fmt.Print(ansi("[2K")) fmt.Print(ansi("[2K"))
} }
func moveCursorUp(lines int) { func MoveCursorUp(lines int) {
if disableAnsi { if disableAnsi {
return return
} }
@ -79,7 +86,7 @@ func moveCursorUp(lines int) {
fmt.Print(ansi(fmt.Sprintf("[%dA", lines))) fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
} }
func moveCursorDown(lines int) { func MoveCursorDown(lines int) {
if disableAnsi { if disableAnsi {
return return
} }
@ -87,7 +94,7 @@ func moveCursorDown(lines int) {
fmt.Print(ansi(fmt.Sprintf("[%dB", lines))) fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
} }
func newLine() { func NewLine() {
// Like \n // Like \n
fmt.Print("\012") fmt.Print("\012")
} }

View File

@ -19,7 +19,6 @@ package formatter
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"sync" "sync"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@ -59,9 +58,6 @@ const (
Auto = "auto" Auto = "auto"
) )
// ansiColorOffset is the offset for basic foreground colors in ANSI escape codes.
const ansiColorOffset = 30
// SetANSIMode configure formatter for colored output on ANSI-compliant console // SetANSIMode configure formatter for colored output on ANSI-compliant console
func SetANSIMode(streams command.Streams, ansi string) { func SetANSIMode(streams command.Streams, ansi string) {
if !useAnsi(streams, ansi) { if !useAnsi(streams, ansi) {
@ -95,15 +91,11 @@ func ansiColor(code, s string, formatOpts ...string) string {
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193 // Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
func ansiColorCode(code string, formatOpts ...string) string { func ansiColorCode(code string, formatOpts ...string) string {
var sb strings.Builder res := "\033["
sb.WriteString("\033[")
for _, c := range formatOpts { for _, c := range formatOpts {
sb.WriteString(c) res = fmt.Sprintf("%s%s;", res, c)
sb.WriteString(";")
} }
sb.WriteString(code) return fmt.Sprintf("%s%sm", res, code)
sb.WriteString("m")
return sb.String()
} }
func makeColorFunc(code string) colorFunc { func makeColorFunc(code string) colorFunc {
@ -130,8 +122,8 @@ func rainbowColor() colorFunc {
func init() { func init() {
colors := map[string]colorFunc{} colors := map[string]colorFunc{}
for i, name := range names { for i, name := range names {
colors[name] = makeColorFunc(strconv.Itoa(ansiColorOffset + i)) colors[name] = makeColorFunc(strconv.Itoa(30 + i))
colors["intense_"+name] = makeColorFunc(strconv.Itoa(ansiColorOffset+i) + ";1") colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
} }
rainbow = []colorFunc{ rainbow = []colorFunc{
colors["cyan"], colors["cyan"],

View File

@ -56,6 +56,10 @@ func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix
} }
} }
func (l *logConsumer) Register(name string) {
l.register(name)
}
func (l *logConsumer) register(name string) *presenter { func (l *logConsumer) register(name string) *presenter {
var p *presenter var p *presenter
root, _, found := strings.Cut(name, " ") root, _, found := strings.Cut(name, " ")
@ -69,12 +73,9 @@ func (l *logConsumer) register(name string) *presenter {
} else { } else {
cf := monochrome cf := monochrome
if l.color { if l.color {
switch name { if name == api.WatchLogger {
case "":
cf = monochrome
case api.WatchLogger:
cf = makeColorFunc("92") cf = makeColorFunc("92")
default: } else {
cf = nextColor() cf = nextColor()
} }
} }
@ -117,15 +118,23 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
if l.ctx.Err() != nil { if l.ctx.Err() != nil {
return return
} }
if KeyboardManager != nil {
KeyboardManager.ClearKeyboardInfo()
}
p := l.getPresenter(container) p := l.getPresenter(container)
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed) timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
for _, line := range strings.Split(message, "\n") { for _, line := range strings.Split(message, "\n") {
if l.timestamp { if l.timestamp {
_, _ = fmt.Fprintf(w, "%s%s %s\n", p.prefix, timestamp, line) _, _ = fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
} else { } else {
_, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line) _, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line)
} }
} }
if KeyboardManager != nil {
KeyboardManager.PrintKeyboardInfo()
}
} }
func (l *logConsumer) Status(container, msg string) { func (l *logConsumer) Status(container, msg string) {
@ -159,27 +168,3 @@ func (p *presenter) setPrefix(width int) {
} }
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name)) p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
} }
type logDecorator struct {
decorated api.LogConsumer
Before func()
After func()
}
func (l logDecorator) Log(containerName, message string) {
l.Before()
l.decorated.Log(containerName, message)
l.After()
}
func (l logDecorator) Err(containerName, message string) {
l.Before()
l.decorated.Err(containerName, message)
l.After()
}
func (l logDecorator) Status(container, msg string) {
l.Before()
l.decorated.Status(container, msg)
l.After()
}

View File

@ -14,16 +14,25 @@
limitations under the License. limitations under the License.
*/ */
package e2e package formatter
import ( import (
"testing" "strings"
"github.com/hashicorp/go-multierror"
) )
func TestComposeModel(t *testing.T) { // SetMultiErrorFormat set cli default format for multi-errors
t.Skip("waiting for docker-model release") func SetMultiErrorFormat(errs *multierror.Error) {
c := NewParallelCLI(t) if errs != nil {
defer c.cleanupWithDown(t, "model-test") errs.ErrorFormat = formatErrors
}
c.RunDockerComposeCmd(t, "-f", "./fixtures/model/compose.yaml", "run", "test", "sh", "-c", "curl ${FOO_URL}") }
func formatErrors(errs []error) string {
messages := make([]string, len(errs))
for i, err := range errs {
messages[i] = "Error: " + err.Error()
}
return strings.Join(messages, "\n")
} }

View File

@ -48,8 +48,8 @@ func (ke *KeyboardError) printError(height int, info string) {
if ke.shouldDisplay() { if ke.shouldDisplay() {
errMessage := ke.err.Error() errMessage := ke.err.Error()
moveCursor(height-1-extraLines(info)-extraLines(errMessage), 0) MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
clearLine() ClearLine()
fmt.Print(errMessage) fmt.Print(errMessage)
} }
@ -70,11 +70,10 @@ func (ke *KeyboardError) error() string {
type KeyboardWatch struct { type KeyboardWatch struct {
Watching bool Watching bool
Watcher Feature Watcher Toggle
} }
// Feature is an compose feature that can be started/stopped by a menu command type Toggle interface {
type Feature interface {
Start(context.Context) error Start(context.Context) error
Stop() error Stop() error
} }
@ -89,26 +88,32 @@ const (
type LogKeyboard struct { type LogKeyboard struct {
kError KeyboardError kError KeyboardError
Watch *KeyboardWatch Watch KeyboardWatch
IsDockerDesktopActive bool IsDockerDesktopActive bool
IsWatchConfigured bool
logLevel KEYBOARD_LOG_LEVEL logLevel KEYBOARD_LOG_LEVEL
signalChannel chan<- os.Signal signalChannel chan<- os.Signal
} }
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard { // FIXME(ndeloof) we should avoid use of such a global reference. see use in logConsumer
return &LogKeyboard{ var KeyboardManager *LogKeyboard
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal, w bool, watcher Toggle) *LogKeyboard {
KeyboardManager = &LogKeyboard{
Watch: KeyboardWatch{
Watching: w,
Watcher: watcher,
},
IsDockerDesktopActive: isDockerDesktopActive, IsDockerDesktopActive: isDockerDesktopActive,
IsWatchConfigured: true,
logLevel: INFO, logLevel: INFO,
signalChannel: sc, signalChannel: sc,
} }
return KeyboardManager
} }
func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer { func (lk *LogKeyboard) ClearKeyboardInfo() {
return logDecorator{ lk.clearNavigationMenu()
decorated: l,
Before: lk.clearNavigationMenu,
After: lk.PrintKeyboardInfo,
}
} }
func (lk *LogKeyboard) PrintKeyboardInfo() { func (lk *LogKeyboard) PrintKeyboardInfo() {
@ -133,7 +138,7 @@ func (lk *LogKeyboard) createBuffer(lines int) {
if lines > 0 { if lines > 0 {
allocateSpace(lines) allocateSpace(lines)
moveCursorUp(lines) MoveCursorUp(lines)
} }
} }
@ -146,17 +151,17 @@ func (lk *LogKeyboard) printNavigationMenu() {
height := goterm.Height() height := goterm.Height()
menu := lk.navigationMenu() menu := lk.navigationMenu()
carriageReturn() MoveCursorX(0)
saveCursor() SaveCursor()
lk.kError.printError(height, menu) lk.kError.printError(height, menu)
moveCursor(height-extraLines(menu), 0) MoveCursor(height-extraLines(menu), 0)
clearLine() ClearLine()
fmt.Print(menu) fmt.Print(menu)
carriageReturn() MoveCursorX(0)
restoreCursor() RestoreCursor()
} }
} }
@ -179,7 +184,7 @@ func (lk *LogKeyboard) navigationMenu() string {
watchInfo = navColor(" ") watchInfo = navColor(" ")
} }
isEnabled := " Enable" isEnabled := " Enable"
if lk.Watch != nil && lk.Watch.Watching { if lk.Watch.Watching {
isEnabled = " Disable" isEnabled = " Disable"
} }
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch") watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
@ -188,15 +193,15 @@ func (lk *LogKeyboard) navigationMenu() string {
func (lk *LogKeyboard) clearNavigationMenu() { func (lk *LogKeyboard) clearNavigationMenu() {
height := goterm.Height() height := goterm.Height()
carriageReturn() MoveCursorX(0)
saveCursor() SaveCursor()
// clearLine() // ClearLine()
for i := 0; i < height; i++ { for i := 0; i < height; i++ {
moveCursorDown(1) MoveCursorDown(1)
clearLine() ClearLine()
} }
restoreCursor() RestoreCursor()
} }
func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) { func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
@ -262,7 +267,7 @@ func (lk *LogKeyboard) keyboardError(prefix string, err error) {
} }
func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) { func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
if lk.Watch == nil { if !lk.IsWatchConfigured {
return return
} }
if lk.Watch.Watching { if lk.Watch.Watching {
@ -293,7 +298,7 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
case 'v': case 'v':
lk.openDockerDesktop(ctx, project) lk.openDockerDesktop(ctx, project)
case 'w': case 'w':
if lk.Watch == nil { if !lk.IsWatchConfigured {
// we try to open watch docs if DD is installed // we try to open watch docs if DD is installed
if lk.IsDockerDesktopActive { if lk.IsDockerDesktopActive {
lk.openDDWatchDocs(ctx, project) lk.openDDWatchDocs(ctx, project)
@ -316,29 +321,22 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
case keyboard.KeyCtrlC: case keyboard.KeyCtrlC:
_ = keyboard.Close() _ = keyboard.Close()
lk.clearNavigationMenu() lk.clearNavigationMenu()
showCursor() ShowCursor()
lk.logLevel = NONE lk.logLevel = NONE
// will notify main thread to kill and will handle gracefully // will notify main thread to kill and will handle gracefully
lk.signalChannel <- syscall.SIGINT lk.signalChannel <- syscall.SIGINT
case keyboard.KeyEnter: case keyboard.KeyEnter:
newLine() NewLine()
lk.printNavigationMenu() lk.printNavigationMenu()
} }
} }
func (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
lk.Watch = &KeyboardWatch{
Watching: enabled,
Watcher: watcher,
}
}
func allocateSpace(lines int) { func allocateSpace(lines int) {
for i := 0; i < lines; i++ { for i := 0; i < lines; i++ {
clearLine() ClearLine()
newLine() NewLine()
carriageReturn() MoveCursorX(0)
} }
} }

View File

@ -20,7 +20,7 @@ import (
"os" "os"
dockercli "github.com/docker/cli/cli" dockercli "github.com/docker/cli/cli"
"github.com/docker/cli/cli-plugins/metadata" "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli-plugins/plugin"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/cmd/cmdtrace" "github.com/docker/compose/v2/cmd/cmdtrace"
@ -68,7 +68,7 @@ func pluginMain() {
}) })
return cmd return cmd
}, },
metadata.Metadata{ manager.Metadata{
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
Vendor: "Docker Inc.", Vendor: "Docker Inc.",
Version: internal.Version, Version: internal.Version,

View File

@ -39,30 +39,20 @@ func main() {
} }
} }
type options struct {
db string
size int
}
func composeCommand() *cobra.Command { func composeCommand() *cobra.Command {
c := &cobra.Command{ c := &cobra.Command{
Use: "compose EVENT", Use: "compose EVENT",
TraverseChildren: true, TraverseChildren: true,
} }
c.PersistentFlags().String("project-name", "", "compose project name") // unused c.PersistentFlags().String("project-name", "", "compose project name") // unused
var options options
upCmd := &cobra.Command{ upCmd := &cobra.Command{
Use: "up", Use: "up",
Run: func(_ *cobra.Command, args []string) { Run: up,
up(options, args)
},
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
} }
upCmd.Flags().String("type", "", "Database type (mysql, postgres, etc.)")
upCmd.Flags().StringVar(&options.db, "type", "", "Database type (mysql, postgres, etc.)")
_ = upCmd.MarkFlagRequired("type") _ = upCmd.MarkFlagRequired("type")
upCmd.Flags().IntVar(&options.size, "size", 10, "Database size in GB") upCmd.Flags().Int("size", 10, "Database size in GB")
upCmd.Flags().String("name", "", "Name of the database to be created") upCmd.Flags().String("name", "", "Name of the database to be created")
_ = upCmd.MarkFlagRequired("name") _ = upCmd.MarkFlagRequired("name")
@ -81,13 +71,13 @@ func composeCommand() *cobra.Command {
const lineSeparator = "\n" const lineSeparator = "\n"
func up(options options, args []string) { func up(_ *cobra.Command, args []string) {
servicename := args[0] servicename := args[0]
fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator) fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator)
for i := 0; i < options.size; i += 10 { for i := 0; i < 100; i += 10 {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, lineSeparator) fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i, lineSeparator)
} }
fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator) fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator)
} }

View File

@ -43,7 +43,6 @@ Define and run multi-container applications with Docker
| [`unpause`](compose_unpause.md) | Unpause services | | [`unpause`](compose_unpause.md) | Unpause services |
| [`up`](compose_up.md) | Create and start containers | | [`up`](compose_up.md) | Create and start containers |
| [`version`](compose_version.md) | Show the Docker Compose version information | | [`version`](compose_version.md) | Show the Docker Compose version information |
| [`volumes`](compose_volumes.md) | List volumes |
| [`wait`](compose_wait.md) | Block until containers of all (or specified) services stop. | | [`wait`](compose_wait.md) | Block until containers of all (or specified) services stop. |
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated | | [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |

View File

@ -22,11 +22,9 @@ run `docker compose build` to rebuild it.
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. | | `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
| `--no-cache` | `bool` | | Do not use cache when building the image | | `--no-cache` | `bool` | | Do not use cache when building the image |
| `--print` | `bool` | | Print equivalent bake file | | `--print` | `bool` | | Print equivalent bake file |
| `--provenance` | `string` | | Add a provenance attestation |
| `--pull` | `bool` | | Always attempt to pull a newer version of the image | | `--pull` | `bool` | | Always attempt to pull a newer version of the image |
| `--push` | `bool` | | Push service images | | `--push` | `bool` | | Push service images |
| `-q`, `--quiet` | `bool` | | Suppress the build output | | `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
| `--sbom` | `string` | | Add a SBOM attestation |
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) | | `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
| `--with-dependencies` | `bool` | | Also build dependencies (transitively) | | `--with-dependencies` | `bool` | | Also build dependencies (transitively) |

View File

@ -15,8 +15,6 @@ the canonical format.
| `--hash` | `string` | | Print the service config hash, one per line. | | `--hash` | `string` | | Print the service config hash, one per line. |
| `--images` | `bool` | | Print the image names, one per line. | | `--images` | `bool` | | Print the image names, one per line. |
| `--lock-image-digests` | `bool` | | Produces an override file with image digests | | `--lock-image-digests` | `bool` | | Produces an override file with image digests |
| `--models` | `bool` | | Print the model names, one per line. |
| `--networks` | `bool` | | Print the network names, one per line. |
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output | | `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
| `--no-env-resolution` | `bool` | | Don't resolve service env files | | `--no-env-resolution` | `bool` | | Don't resolve service env files |
| `--no-interpolate` | `bool` | | Don't interpolate environment variables | | `--no-interpolate` | `bool` | | Don't interpolate environment variables |

View File

@ -23,12 +23,10 @@ The events that can be received using this can be seen [here](/reference/cli/doc
### Options ### Options
| Name | Type | Default | Description | | Name | Type | Default | Description |
|:------------|:---------|:--------|:------------------------------------------| |:------------|:-------|:--------|:------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode | | `--dry-run` | `bool` | | Execute command in dry run mode |
| `--json` | `bool` | | Output events as a stream of json objects | | `--json` | `bool` | | Output events as a stream of json objects |
| `--since` | `string` | | Show all events created since timestamp |
| `--until` | `string` | | Stream events until this timestamp |
<!---MARKER_GEN_END--> <!---MARKER_GEN_END-->

View File

@ -6,12 +6,6 @@ This is the equivalent of `docker exec` targeting a Compose service.
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
you can use a command such as `docker compose exec web sh` to get an interactive prompt. you can use a command such as `docker compose exec web sh` to get an interactive prompt.
By default, Compose will enter container in interactive mode and allocate a TTY, while the equivalent `docker exec`
command requires passing `--interactive --tty` flags to get the same behavior. Compose also support those two flags
to offer a smooth migration between commands, whenever they are no-op by default. Still, `interactive` can be used to
force disabling interactive mode (`--interactive=false`), typically when `docker compose exec` command is used inside
a script.
### Options ### Options
| Name | Type | Default | Description | | Name | Type | Default | Description |
@ -20,7 +14,7 @@ a script.
| `--dry-run` | `bool` | | Execute command in dry run mode | | `--dry-run` | `bool` | | Execute command in dry run mode |
| `-e`, `--env` | `stringArray` | | Set environment variables | | `-e`, `--env` | `stringArray` | | Set environment variables |
| `--index` | `int` | `0` | Index of the container if service has multiple replicas | | `--index` | `int` | `0` | Index of the container if service has multiple replicas |
| `-T`, `--no-tty` | `bool` | `true` | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. | | `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
| `--privileged` | `bool` | | Give extended privileges to the process | | `--privileged` | `bool` | | Give extended privileges to the process |
| `-u`, `--user` | `string` | | Run the command as this user | | `-u`, `--user` | `string` | | Run the command as this user |
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command | | `-w`, `--workdir` | `string` | | Path to workdir directory for this command |
@ -34,9 +28,3 @@ This is the equivalent of `docker exec` targeting a Compose service.
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
you can use a command such as `docker compose exec web sh` to get an interactive prompt. you can use a command such as `docker compose exec web sh` to get an interactive prompt.
By default, Compose will enter container in interactive mode and allocate a TTY, while the equivalent `docker exec`
command requires passing `--interactive --tty` flags to get the same behavior. Compose also support those two flags
to offer a smooth migration between commands, whenever they are no-op by default. Still, `interactive` can be used to
force disabling interactive mode (`--interactive=false`), typically when `docker compose exec` command is used inside
a script.

View File

@ -44,7 +44,6 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
| `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | | `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
| `--no-start` | `bool` | | Don't start the services after creating them | | `--no-start` | `bool` | | Don't start the services after creating them |
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") | | `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
| `--quiet-build` | `bool` | | Suppress the build output |
| `--quiet-pull` | `bool` | | Pull without printing progress information | | `--quiet-pull` | `bool` | | Pull without printing progress information |
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file | | `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
| `-V`, `--renew-anon-volumes` | `bool` | | Recreate anonymous volumes instead of retrieving data from the previous containers | | `-V`, `--renew-anon-volumes` | `bool` | | Recreate anonymous volumes instead of retrieving data from the previous containers |

View File

@ -1,16 +0,0 @@
# docker compose volumes
<!---MARKER_GEN_START-->
List volumes
### Options
| Name | Type | Default | Description |
|:----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--dry-run` | `bool` | | Execute command in dry run mode |
| `--format` | `string` | `table` | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
| `-q`, `--quiet` | `bool` | | Only display volume names |
<!---MARKER_GEN_END-->

View File

@ -37,7 +37,6 @@ cname:
- docker compose unpause - docker compose unpause
- docker compose up - docker compose up
- docker compose version - docker compose version
- docker compose volumes
- docker compose wait - docker compose wait
- docker compose watch - docker compose watch
clink: clink:
@ -73,7 +72,6 @@ clink:
- docker_compose_unpause.yaml - docker_compose_unpause.yaml
- docker_compose_up.yaml - docker_compose_up.yaml
- docker_compose_version.yaml - docker_compose_version.yaml
- docker_compose_volumes.yaml
- docker_compose_wait.yaml - docker_compose_wait.yaml
- docker_compose_watch.yaml - docker_compose_watch.yaml
options: options:

View File

@ -125,15 +125,6 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: provenance
value_type: string
description: Add a provenance attestation
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: pull - option: pull
value_type: bool value_type: bool
default_value: "false" default_value: "false"
@ -158,16 +149,7 @@ options:
shorthand: q shorthand: q
value_type: bool value_type: bool
default_value: "false" default_value: "false"
description: Suppress the build output description: Don't print anything to STDOUT
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: sbom
value_type: string
description: Add a SBOM attestation
deprecated: false deprecated: false
hidden: false hidden: false
experimental: false experimental: false

View File

@ -56,26 +56,6 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: models
value_type: bool
default_value: "false"
description: Print the model names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: networks
value_type: bool
default_value: "false"
description: Print the network names, one per line.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: no-consistency - option: no-consistency
value_type: bool value_type: bool
default_value: "false" default_value: "false"

View File

@ -34,24 +34,6 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: since
value_type: string
description: Show all events created since timestamp
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: until
value_type: string
description: Stream events until this timestamp
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options: inherited_options:
- option: dry-run - option: dry-run
value_type: bool value_type: bool

View File

@ -5,12 +5,6 @@ long: |-
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
you can use a command such as `docker compose exec web sh` to get an interactive prompt. you can use a command such as `docker compose exec web sh` to get an interactive prompt.
By default, Compose will enter container in interactive mode and allocate a TTY, while the equivalent `docker exec`
command requires passing `--interactive --tty` flags to get the same behavior. Compose also support those two flags
to offer a smooth migration between commands, whenever they are no-op by default. Still, `interactive` can be used to
force disabling interactive mode (`--interactive=false`), typically when `docker compose exec` command is used inside
a script.
usage: docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...] usage: docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]
pname: docker compose pname: docker compose
plink: docker_compose.yaml plink: docker_compose.yaml
@ -58,7 +52,7 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: no-tty - option: no-TTY
shorthand: T shorthand: T
value_type: bool value_type: bool
default_value: "true" default_value: "true"

View File

@ -211,16 +211,6 @@ options:
experimentalcli: false experimentalcli: false
kubernetes: false kubernetes: false
swarm: false swarm: false
- option: quiet-build
value_type: bool
default_value: "false"
description: Suppress the build output
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet-pull - option: quiet-pull
value_type: bool value_type: bool
default_value: "false" default_value: "false"

View File

@ -1,52 +0,0 @@
command: docker compose volumes
short: List volumes
long: List volumes
usage: docker compose volumes [OPTIONS] [SERVICE...]
pname: docker compose
plink: docker_compose.yaml
options:
- option: format
value_type: string
default_value: table
description: |-
Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: quiet
shorthand: q
value_type: bool
default_value: "false"
description: Only display volume names
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

88
go.mod
View File

@ -1,6 +1,6 @@
module github.com/docker/compose/v2 module github.com/docker/compose/v2
go 1.24.7 go 1.23.8
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
@ -8,27 +8,28 @@ require (
github.com/Microsoft/go-winio v0.6.2 github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4 github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.8.2 github.com/compose-spec/compose-go/v2 v2.6.4
github.com/containerd/containerd/v2 v2.1.4 github.com/containerd/containerd/v2 v2.1.1
github.com/containerd/errdefs v1.0.0 github.com/containerd/errdefs v1.0.0
github.com/containerd/platforms v1.0.0-rc.1 github.com/containerd/platforms v1.0.0-rc.1
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/buildx v0.28.0 github.com/docker/buildx v0.24.0
github.com/docker/cli v28.4.0+incompatible github.com/docker/cli v28.2.2+incompatible
github.com/docker/cli-docs-tool v0.10.0 github.com/docker/cli-docs-tool v0.10.0
github.com/docker/docker v28.4.0+incompatible github.com/docker/docker v28.2.2+incompatible
github.com/docker/go-connections v0.6.0 github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
github.com/fsnotify/fsevents v0.2.0 github.com/fsnotify/fsevents v0.2.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/go-version v1.7.0
github.com/jonboulle/clockwork v0.5.0 github.com/jonboulle/clockwork v0.5.0
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/go-ps v1.0.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/moby/buildkit v0.24.0 github.com/moby/buildkit v0.22.0
github.com/moby/go-archive v0.1.0 github.com/moby/go-archive v0.1.0
github.com/moby/patternmatcher v0.6.0 github.com/moby/patternmatcher v0.6.0
github.com/moby/sys/atomicwriter v0.1.0 github.com/moby/sys/atomicwriter v0.1.0
@ -39,30 +40,31 @@ require (
github.com/otiai10/copy v1.14.1 github.com/otiai10/copy v1.14.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.10.0
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0
go.opentelemetry.io/otel/metric v1.36.0 go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/sdk v1.36.0 go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/trace v1.36.0 go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/goleak v1.3.0 go.uber.org/goleak v1.3.0
go.uber.org/mock v0.6.0 go.uber.org/mock v0.5.2
golang.org/x/sync v0.17.0 golang.org/x/sync v0.14.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.33.0
google.golang.org/grpc v1.74.2 google.golang.org/grpc v1.72.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2 gotest.tools/v3 v3.5.2
tags.cncf.io/container-device-interface v1.0.1 tags.cncf.io/container-device-interface v1.0.1
) )
require ( require (
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
@ -80,14 +82,14 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/console v1.0.5 // indirect github.com/containerd/console v1.0.4 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
@ -96,12 +98,12 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
@ -115,8 +117,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/in-toto/in-toto-golang v0.5.0 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
@ -156,36 +157,37 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // 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/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect github.com/zclconf/go-cty v1.16.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/term v0.31.0 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
@ -202,7 +204,7 @@ require (
) )
exclude ( exclude (
// FIXME(thaJeztah): remove this once kubernetes updated their dependencies to no longer need this. // FIXME(thaJeztah): remoove this once kubernetes updated their dependencies to no longer need this.
// //
// For additional details, see this PR and links mentioned in that PR: // For additional details, see this PR and links mentioned in that PR:
// https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859 // https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859

187
go.sum
View File

@ -1,3 +1,5 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
@ -8,8 +10,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0= github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0=
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
@ -78,16 +80,16 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.8.2 h1:A1iVoZJUex7buGv1CpnC5uwNuyTMBYpDAmBnAQmia9Q= github.com/compose-spec/compose-go/v2 v2.6.4 h1:Gjv6x8eAhqwwWvoXIo0oZ4bDQBh0OMwdU7LUL9PDLiM=
github.com/compose-spec/compose-go/v2 v2.8.2/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE= github.com/compose-spec/compose-go/v2 v2.6.4/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ= github.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM=
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM= github.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@ -98,8 +100,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg= github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8=
github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM= github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
@ -111,9 +113,8 @@ github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRq
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@ -124,26 +125,24 @@ 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/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 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/docker/buildx v0.24.0 h1:qiD+xktY+Fs3R79oz8M+7pbhip78qGLx6LBuVmyb+64=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/buildx v0.24.0/go.mod h1:vYkdBUBjFo/i5vUE0mkajGlk03gE0T/HaGXXhgIxo8E=
github.com/docker/buildx v0.28.0 h1:ZnrVsZ/qQwSOQ4Fx3IgXjiurAwvocaF1YUaPbIXD89E= github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/buildx v0.28.0/go.mod h1:nLwx58w7xrQbLVSXiWiHpkVhY4ou4ci/hYomc139Vjk= github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU= github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U= github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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 h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
@ -170,8 +169,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
@ -187,8 +186,8 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -217,8 +216,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -244,8 +243,8 @@ github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY=
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@ -316,8 +315,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 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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.24.0 h1:qYfTl7W1SIJzWDIDCcPT8FboHIZCYfi++wvySi3eyFE= github.com/moby/buildkit v0.22.0 h1:aWN06w1YGSVN1XfeZbj2ZbgY+zi5xDAjEFI8Cy9fTjA=
github.com/moby/buildkit v0.24.0/go.mod h1:4qovICAdR2H4C7+EGMRva5zgHW1gyhT4/flHI7F5F9k= github.com/moby/buildkit v0.22.0/go.mod h1:j4pP5hxiTWcz7xuTK2cyxQislHl/N2WWHzOy43DlLJw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 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/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@ -424,10 +423,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/secure-systems-lab/go-securesystemslib v0.4.0 h1:b23VGrQhTA8cN2CbBw7/FulN9fTtqYUdS5+Oxzt+DUE=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/secure-systems-lab/go-securesystemslib v0.4.0/go.mod h1:FGBZgq2tXWICsxWQW1msNf49F0Pf2Op5Htayx335Qbs=
github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA=
github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
@ -439,19 +436,18 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 h1:XTHrT015sxHyJ5FnQ0AeemSspZWaDq7DoTRW0EVsDCE=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c h1:2EejZtjFjKJGk71ANb+wtFK5EjUzUkEM3R0xnp559xg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -468,18 +464,18 @@ 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.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.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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= 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 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 h1:k9tdF32oJYwtjzMx+D26M6eYiCaAPdJ7tyN7tF1oU5Q=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= 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= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
@ -488,6 +484,13 @@ github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnn
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -501,38 +504,36 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -541,13 +542,13 @@ 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-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-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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -557,10 +558,10 @@ 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-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-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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -568,8 +569,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-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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -591,19 +592,19 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -611,19 +612,19 @@ 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-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.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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=

View File

@ -1,4 +1,4 @@
//go:build linux || openbsd || freebsd //go:build linux || openbsd
/* /*
Copyright 2020 Docker Compose CLI authors Copyright 2020 Docker Compose CLI authors

View File

@ -105,9 +105,7 @@ func PushManifest(
) error { ) error {
// Check if we need an extra empty layer for the manifest config // Check if we need an extra empty layer for the manifest config
if ociVersion == api.OCIVersion1_1 || ociVersion == "" { if ociVersion == api.OCIVersion1_1 || ociVersion == "" {
if err := resolver.Push(ctx, named, v1.DescriptorEmptyJSON, v1.DescriptorEmptyJSON.Data); err != nil { layers = append(layers, Pushable{Descriptor: v1.DescriptorEmptyJSON, Data: []byte("{}")})
return err
}
} }
// prepare to push the manifest by pushing the layers // prepare to push the manifest by pushing the layers
layerDescriptors := make([]v1.Descriptor, len(layers)) layerDescriptors := make([]v1.Descriptor, len(layers))

View File

@ -1,38 +0,0 @@
/*
Copyright 2023 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import "github.com/distribution/reference"
const (
// IndexHostname is the index hostname, used for authentication and image search.
IndexHostname = "index.docker.io"
// IndexServer is used for user auth and image search
IndexServer = "https://index.docker.io/v1/"
// IndexName is the name of the index
IndexName = "docker.io"
)
// GetAuthConfigKey special-cases using the full index address of the official
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
func GetAuthConfigKey(reposName reference.Named) string {
indexName := reference.Domain(reposName)
if indexName == IndexName || indexName == IndexHostname {
return IndexServer
}
return indexName
}

View File

@ -29,11 +29,11 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/hashicorp/go-multierror"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/moby/go-archive" "github.com/moby/go-archive"
"golang.org/x/sync/errgroup"
) )
type archiveEntry struct { type archiveEntry struct {
@ -84,14 +84,7 @@ func (t *Tar) Sync(ctx context.Context, service string, paths []*PathMapping) er
if len(pathsToDelete) != 0 { if len(pathsToDelete) != 0 {
deleteCmd = append([]string{"rm", "-rf"}, pathsToDelete...) deleteCmd = append([]string{"rm", "-rf"}, pathsToDelete...)
} }
var eg multierror.Group
var (
eg errgroup.Group
errMu sync.Mutex
errs = make([]error, 0, len(containers)*2) // max 2 errs per container
)
eg.SetLimit(16) // arbitrary limit, adjust to taste :D
for i := range containers { for i := range containers {
containerID := containers[i].ID containerID := containers[i].ID
tarReader := tarArchive(pathsToCopy) tarReader := tarArchive(pathsToCopy)
@ -99,23 +92,17 @@ func (t *Tar) Sync(ctx context.Context, service string, paths []*PathMapping) er
eg.Go(func() error { eg.Go(func() error {
if len(deleteCmd) != 0 { if len(deleteCmd) != 0 {
if err := t.client.Exec(ctx, containerID, deleteCmd, nil); err != nil { if err := t.client.Exec(ctx, containerID, deleteCmd, nil); err != nil {
errMu.Lock() return fmt.Errorf("deleting paths in %s: %w", containerID, err)
errs = append(errs, fmt.Errorf("deleting paths in %s: %w", containerID, err))
errMu.Unlock()
} }
} }
if err := t.client.Untar(ctx, containerID, tarReader); err != nil { if err := t.client.Untar(ctx, containerID, tarReader); err != nil {
errMu.Lock() return fmt.Errorf("copying files to %s: %w", containerID, err)
errs = append(errs, fmt.Errorf("copying files to %s: %w", containerID, err))
errMu.Unlock()
} }
return nil // don't fail-fast; collect all errors return nil
}) })
} }
return eg.Wait().ErrorOrNil()
_ = eg.Wait()
return errors.Join(errs...)
} }
type ArchiveBuilder struct { type ArchiveBuilder struct {
@ -242,7 +229,7 @@ func (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {
return nil return nil
} }
// entriesForPath writes the given source path into tarWriter at the given dest (recursively for directories). // tarPath writes the given source path into tarWriter at the given dest (recursively for directories).
// e.g. tarring my_dir --> dest d: d/file_a, d/file_b // e.g. tarring my_dir --> dest d: d/file_a, d/file_b
// If source path does not exist, quietly skips it and returns no err // If source path does not exist, quietly skips it and returns no err
func (a *ArchiveBuilder) entriesForPath(localPath, containerPath string) ([]archiveEntry, error) { func (a *ArchiveBuilder) entriesForPath(localPath, containerPath string) ([]archiveEntry, error) {

View File

@ -77,13 +77,11 @@ func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions {
attribute.StringSlice("project.networks", proj.NetworkNames()), attribute.StringSlice("project.networks", proj.NetworkNames()),
attribute.StringSlice("project.secrets", proj.SecretNames()), attribute.StringSlice("project.secrets", proj.SecretNames()),
attribute.StringSlice("project.configs", proj.ConfigNames()), attribute.StringSlice("project.configs", proj.ConfigNames()),
attribute.StringSlice("project.models", proj.ModelNames()),
attribute.StringSlice("project.extensions", keys(proj.Extensions)), attribute.StringSlice("project.extensions", keys(proj.Extensions)),
attribute.StringSlice("project.services.active", proj.ServiceNames()), attribute.StringSlice("project.services.active", proj.ServiceNames()),
attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()), attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()),
attribute.StringSlice("project.services.build", proj.ServicesWithBuild()), attribute.StringSlice("project.services.build", proj.ServicesWithBuild()),
attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()), attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()),
attribute.StringSlice("project.services.models", proj.ServicesWithModels()),
attribute.StringSlice("project.services.capabilities", capabilities), attribute.StringSlice("project.services.capabilities", capabilities),
attribute.StringSlice("project.services.capabilities.gpu", gpu), attribute.StringSlice("project.services.capabilities.gpu", gpu),
attribute.StringSlice("project.services.capabilities.tpu", tpu), attribute.StringSlice("project.services.capabilities.tpu", tpu),
@ -112,7 +110,6 @@ func ServiceOptions(service types.ServiceConfig) SpanOptions {
attribute.String("service.name", service.Name), attribute.String("service.name", service.Name),
attribute.String("service.image", service.Image), attribute.String("service.image", service.Image),
attribute.StringSlice("service.networks", keys(service.Networks)), attribute.StringSlice("service.networks", keys(service.Networks)),
attribute.StringSlice("service.models", keys(service.Models)),
} }
configNames := make([]string, len(service.Configs)) configNames := make([]string, len(service.Configs))

View File

@ -22,12 +22,15 @@ import (
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
) )
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive bool) { func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
commandAvailable := []string{} commandAvailable := []string{}
if isDockerDesktopActive { if isDockerDesktopActive {
commandAvailable = append(commandAvailable, "gui") commandAvailable = append(commandAvailable, "gui")
commandAvailable = append(commandAvailable, "gui/composeview") commandAvailable = append(commandAvailable, "gui/composeview")
} }
if isWatchConfigured {
commandAvailable = append(commandAvailable, "watch")
}
AddAttributeToSpan(ctx, AddAttributeToSpan(ctx,
attribute.Bool("navmenu.enabled", enabled), attribute.Bool("navmenu.enabled", enabled),

View File

@ -18,9 +18,8 @@ package tracing
import ( import (
"context" "context"
"errors"
"sync"
"github.com/hashicorp/go-multierror"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
@ -29,45 +28,23 @@ type MuxExporter struct {
} }
func (m MuxExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { func (m MuxExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error {
var ( var eg multierror.Group
wg sync.WaitGroup for i := range m.exporters {
errMu sync.Mutex exporter := m.exporters[i]
errs = make([]error, 0, len(m.exporters)) eg.Go(func() error {
) return exporter.ExportSpans(ctx, spans)
})
for _, exporter := range m.exporters {
wg.Add(1)
go func() {
defer wg.Done()
if err := exporter.ExportSpans(ctx, spans); err != nil {
errMu.Lock()
errs = append(errs, err)
errMu.Unlock()
}
}()
} }
wg.Wait() return eg.Wait()
return errors.Join(errs...)
} }
func (m MuxExporter) Shutdown(ctx context.Context) error { func (m MuxExporter) Shutdown(ctx context.Context) error {
var ( var eg multierror.Group
wg sync.WaitGroup for i := range m.exporters {
errMu sync.Mutex exporter := m.exporters[i]
errs = make([]error, 0, len(m.exporters)) eg.Go(func() error {
) return exporter.Shutdown(ctx)
})
for _, exporter := range m.exporters {
wg.Add(1)
go func() {
defer wg.Done()
if err := exporter.Shutdown(ctx); err != nil {
errMu.Lock()
errs = append(errs, err)
errMu.Unlock()
}
}()
} }
wg.Wait() return eg.Wait()
return errors.Join(errs...)
} }

View File

@ -19,7 +19,6 @@ package api
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"slices" "slices"
"strings" "strings"
"time" "time"
@ -27,7 +26,6 @@ import (
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms" "github.com/containerd/platforms"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"github.com/docker/docker/api/types/volume"
) )
// Service manages a compose project // Service manages a compose project
@ -100,16 +98,8 @@ type Service interface {
Commit(ctx context.Context, projectName string, options CommitOptions) error Commit(ctx context.Context, projectName string, options CommitOptions) error
// Generate generates a Compose Project from existing containers // Generate generates a Compose Project from existing containers
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error) Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
// Volumes executes the equivalent to a `docker volume ls`
Volumes(ctx context.Context, project string, options VolumesOptions) ([]VolumesSummary, error)
} }
type VolumesOptions struct {
Services []string
}
type VolumesSummary = *volume.Volume
type ScaleOptions struct { type ScaleOptions struct {
Services []string Services []string
} }
@ -171,14 +161,8 @@ type BuildOptions struct {
Print bool Print bool
// Check let builder validate build configuration // Check let builder validate build configuration
Check bool Check bool
// Attestations allows to enable attestations generation // Provenance
Attestations bool Provenance bool
// Provenance generate a provenance attestation
Provenance string
// SBOM generate a SBOM attestation
SBOM string
// Out is the stream to write build progress
Out io.Writer
} }
// Apply mutates project according to build options // Apply mutates project according to build options
@ -367,7 +351,7 @@ type RemoveOptions struct {
// RunOptions group options of the Run API // RunOptions group options of the Run API
type RunOptions struct { type RunOptions struct {
CreateOptions Build *BuildOptions
// Project is the compose project used to define this app. Might be nil if user ran command just with project name // Project is the compose project used to define this app. Might be nil if user ran command just with project name
Project *types.Project Project *types.Project
Name string Name string
@ -387,6 +371,8 @@ type RunOptions struct {
Privileged bool Privileged bool
UseNetworkAliases bool UseNetworkAliases bool
NoDeps bool NoDeps bool
// QuietPull makes the pulling process quiet
QuietPull bool
// used by exec // used by exec
Index int Index int
} }
@ -405,8 +391,6 @@ type AttachOptions struct {
type EventsOptions struct { type EventsOptions struct {
Services []string Services []string
Consumer func(event Event) error Consumer func(event Event) error
Since string
Until string
} }
// Event is a container runtime event served by Events API // Event is a container runtime event served by Events API
@ -558,7 +542,6 @@ type ImageSummary struct {
Tag string Tag string
Platform platforms.Platform Platform platforms.Platform
Size int64 Size int64
Created time.Time
LastTagTime time.Time LastTagTime time.Time
} }
@ -657,6 +640,7 @@ type LogConsumer interface {
Log(containerName, message string) Log(containerName, message string)
Err(containerName, message string) Err(containerName, message string)
Status(container, msg string) Status(container, msg string)
Register(container string)
} }
// ContainerEventListener is a callback to process ContainerEvent from services // ContainerEventListener is a callback to process ContainerEvent from services
@ -664,18 +648,16 @@ type ContainerEventListener func(event ContainerEvent)
// ContainerEvent notify an event has been collected on source container implementing Service // ContainerEvent notify an event has been collected on source container implementing Service
type ContainerEvent struct { type ContainerEvent struct {
Type int Type int
Time int64 // Container is the name of the container _without the project prefix_.
Container *ContainerSummary
// Source is the name of the container _without the project prefix_.
// //
// This is only suitable for display purposes within Compose, as it's // This is only suitable for display purposes within Compose, as it's
// not guaranteed to be unique across services. // not guaranteed to be unique across services.
Source string Container string
ID string ID string
Service string Service string
Line string Line string
// ExitCode is only set on ContainerEventExited events // ContainerEventExit only
ExitCode int ExitCode int
Restarting bool Restarting bool
} }
@ -685,19 +667,17 @@ const (
ContainerEventLog = iota ContainerEventLog = iota
// ContainerEventErr is a ContainerEvent of type log on stderr. Line is set // ContainerEventErr is a ContainerEvent of type log on stderr. Line is set
ContainerEventErr ContainerEventErr
// ContainerEventStarted let consumer know a container has been started // ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
ContainerEventStarted ContainerEventAttach
// ContainerEventRestarted let consumer know a container has been restarted
ContainerEventRestarted
// ContainerEventStopped is a ContainerEvent of type stopped. // ContainerEventStopped is a ContainerEvent of type stopped.
ContainerEventStopped ContainerEventStopped
// ContainerEventCreated let consumer know a new container has been created
ContainerEventCreated
// ContainerEventRecreated let consumer know container stopped but his being replaced // ContainerEventRecreated let consumer know container stopped but his being replaced
ContainerEventRecreated ContainerEventRecreated
// ContainerEventExited is a ContainerEvent of type exit. ExitCode is set // ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
ContainerEventExited ContainerEventExit
// UserCancel user cancelled compose up, we are stopping containers // UserCancel user cancelled compose up, we are stopping containers
UserCancel
// HookEventLog is a ContainerEvent of type log on stdout by service hook
HookEventLog HookEventLog
) )

View File

@ -130,7 +130,7 @@ func (d *DryRunClient) ContainerInspect(ctx context.Context, container string) (
ID: id, ID: id,
Name: container, Name: container,
State: &containerType.State{ State: &containerType.State{
Status: containerType.StateRunning, // needed for --wait option Status: "running", // needed for --wait option
Health: &containerType.Health{ Health: &containerType.Health{
Status: containerType.Healthy, // needed for healthcheck control Status: containerType.Healthy, // needed for healthcheck control
}, },

View File

@ -20,7 +20,6 @@ import (
"github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/streams"
) )
// Streams defines the standard streams (stdin, stdout, stderr) used by the CLI.
type Streams interface { type Streams interface {
Out() *streams.Out Out() *streams.Out
Err() *streams.Out Err() *streams.Out

View File

@ -17,6 +17,8 @@
package api package api
import ( import (
"fmt"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"github.com/docker/compose/v2/internal" "github.com/docker/compose/v2/internal"
@ -63,6 +65,9 @@ var ComposeVersion string
func init() { func init() {
v, err := version.NewVersion(internal.Version) v, err := version.NewVersion(internal.Version)
if err == nil { if err == nil {
ComposeVersion = v.Core().String() segments := v.Segments()
if len(segments) > 2 {
ComposeVersion = fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2])
}
} }
} }

View File

@ -1,35 +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 api
import (
"testing"
"github.com/docker/compose/v2/internal"
"github.com/hashicorp/go-version"
"gotest.tools/v3/assert"
)
func TestComposeVersionInitialization(t *testing.T) {
v, err := version.NewVersion(internal.Version)
if err != nil {
assert.Equal(t, "", ComposeVersion, "ComposeVersion should be empty for a non-semver internal version (e.g. 'devel')")
} else {
expected := v.Core().String()
assert.Equal(t, expected, ComposeVersion, "ComposeVersion should be the core of internal.Version")
}
}

View File

@ -23,11 +23,10 @@ import (
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
cli "github.com/docker/cli/cli/command/container" cli "github.com/docker/cli/cli/command/container"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
@ -113,20 +112,15 @@ func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, o
return err return err
} }
containerConfig := &container.Config{ usr, err := user.Current()
if err != nil {
return err
}
created, err := dockerCli.Client().ContainerCreate(ctx, &container.Config{
Image: transformation, Image: transformation,
Env: []string{"LICENSE_AGREEMENT=true"}, Env: []string{"LICENSE_AGREEMENT=true"},
} User: usr.Uid,
// On POSIX systems, this is a decimal number representing the uid. }, &container.HostConfig{
// On Windows, this is a security identifier (SID) in a string format and the engine isn't able to manage it
if runtime.GOOS != "windows" {
usr, err := user.Current()
if err != nil {
return err
}
containerConfig.User = usr.Uid
}
created, err := dockerCli.Client().ContainerCreate(ctx, containerConfig, &container.HostConfig{
AutoRemove: true, AutoRemove: true,
Binds: binds, Binds: binds,
}, &network.NetworkingConfig{}, nil, "") }, &network.NetworkingConfig{}, nil, "")
@ -204,7 +198,7 @@ func loadFileObject(conf types.FileObjectConfig) (types.FileObjectConfig, error)
func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName string) (image.InspectResponse, error) { func inspectWithPull(ctx context.Context, dockerCli command.Cli, imageName string) (image.InspectResponse, error) {
inspect, err := dockerCli.Client().ImageInspect(ctx, imageName) inspect, err := dockerCli.Client().ImageInspect(ctx, imageName)
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
var stream io.ReadCloser var stream io.ReadCloser
stream, err = dockerCli.Client().ImagePull(ctx, imageName, image.PullOptions{}) stream, err = dockerCli.Client().ImagePull(ctx, imageName, image.PullOptions{})
if err != nil { if err != nil {

View File

@ -33,8 +33,6 @@ import (
const ( const (
TransformerLabel = "com.docker.compose.bridge" TransformerLabel = "com.docker.compose.bridge"
DefaultTransformerImage = "docker/compose-bridge-kubernetes" DefaultTransformerImage = "docker/compose-bridge-kubernetes"
templatesPath = "/templates"
) )
type CreateTransformerOptions struct { type CreateTransformerOptions struct {
@ -75,7 +73,7 @@ func CreateTransformer(ctx context.Context, dockerCli command.Cli, options Creat
if err != nil { if err != nil {
return err return err
} }
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, templatesPath) content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, "/templates")
if err != nil { if err != nil {
return err return err
} }
@ -84,7 +82,7 @@ func CreateTransformer(ctx context.Context, dockerCli command.Cli, options Creat
}() }()
srcInfo := archive.CopyInfo{ srcInfo := archive.CopyInfo{
Path: templatesPath, Path: "/templates",
Exists: true, Exists: true,
IsDir: stat.Mode.IsDir(), IsDir: stat.Mode.IsDir(),
} }

View File

@ -1,91 +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 compose
import (
"bytes"
"errors"
"fmt"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli/config/configfile"
)
// --use-api-socket is not actually supported by the Docker Engine
// but is a client-side hack (see https://github.com/docker/cli/blob/master/cli/command/container/create.go#L246)
// we replicate here by transforming the project model
func (s *composeService) useAPISocket(project *types.Project) (*types.Project, error) {
useAPISocket := false
for _, service := range project.Services {
if service.UseAPISocket {
useAPISocket = true
break
}
}
if !useAPISocket {
return project, nil
}
if s.dockerCli.ServerInfo().OSType == "windows" {
return nil, errors.New("use_api_socket can't be used with a Windows Docker Engine")
}
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
if err != nil {
return nil, fmt.Errorf("resolving credentials failed: %w", err)
}
newConfig := &configfile.ConfigFile{
AuthConfigs: creds,
}
var configBuf bytes.Buffer
if err := newConfig.SaveToWriter(&configBuf); err != nil {
return nil, fmt.Errorf("saving creds for API socket: %w", err)
}
project.Configs["#apisocket"] = types.ConfigObjConfig{
Content: configBuf.String(),
}
for name, service := range project.Services {
if !service.UseAPISocket {
continue
}
service.Volumes = append(service.Volumes, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
})
_, envvarPresent := service.Environment["DOCKER_CONFIG"]
// If the DOCKER_CONFIG env var is already present, we assume the client knows
// what they're doing and don't inject the creds.
if !envvarPresent {
// Set our special little location for the config file.
path := "/run/secrets/docker"
service.Environment["DOCKER_CONFIG"] = &path
}
service.Configs = append(service.Configs, types.ServiceConfigObjConfig{
Source: "#apisocket",
Target: "/run/secrets/docker/config.json",
})
project.Services[name] = service
}
return project, nil
}

View File

@ -61,37 +61,41 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, lis
} }
func (s *composeService) attachContainer(ctx context.Context, container containerType.Summary, listener api.ContainerEventListener) error { func (s *composeService) attachContainer(ctx context.Context, container containerType.Summary, listener api.ContainerEventListener) error {
service := container.Labels[api.ServiceLabel] serviceName := container.Labels[api.ServiceLabel]
name := getContainerNameWithoutProject(container) containerName := getContainerNameWithoutProject(container)
return s.doAttachContainer(ctx, service, container.ID, name, listener)
}
func (s *composeService) doAttachContainer(ctx context.Context, service, id, name string, listener api.ContainerEventListener) error { listener(api.ContainerEvent{
inspect, err := s.apiClient().ContainerInspect(ctx, id) Type: api.ContainerEventAttach,
if err != nil { Container: containerName,
return err ID: container.ID,
} Service: serviceName,
})
wOut := utils.GetWriter(func(line string) { wOut := utils.GetWriter(func(line string) {
listener(api.ContainerEvent{ listener(api.ContainerEvent{
Type: api.ContainerEventLog, Type: api.ContainerEventLog,
Source: name, Container: containerName,
ID: id, ID: container.ID,
Service: service, Service: serviceName,
Line: line, Line: line,
}) })
}) })
wErr := utils.GetWriter(func(line string) { wErr := utils.GetWriter(func(line string) {
listener(api.ContainerEvent{ listener(api.ContainerEvent{
Type: api.ContainerEventErr, Type: api.ContainerEventErr,
Source: name, Container: containerName,
ID: id, ID: container.ID,
Service: service, Service: serviceName,
Line: line, Line: line,
}) })
}) })
_, _, err = s.attachContainerStreams(ctx, id, inspect.Config.Tty, nil, wOut, wErr) inspect, err := s.apiClient().ContainerInspect(ctx, container.ID)
if err != nil {
return err
}
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, wOut, wErr)
return err return err
} }

View File

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -29,6 +28,7 @@ import (
"github.com/containerd/platforms" "github.com/containerd/platforms"
"github.com/docker/buildx/build" "github.com/docker/buildx/build"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
"github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/buildflags"
xprogress "github.com/docker/buildx/util/progress" xprogress "github.com/docker/buildx/util/progress"
@ -79,19 +79,16 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
policy = types.IncludeDependencies policy = types.IncludeDependencies
} }
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
}
// also include services used as additional_contexts with service: prefix
options.Services = addBuildDependencies(options.Services, project)
// Some build dependencies we just introduced may not be enabled
var err error var err error
project, err = project.WithServicesEnabled(options.Services...) if len(options.Services) > 0 {
if err != nil { // As user requested some services to be built, also include those used as additional_contexts
return nil, err options.Services = addBuildDependencies(options.Services, project)
// Some build dependencies we just introduced may not be enabled
project, err = project.WithServicesEnabled(options.Services...)
if err != nil {
return nil, err
}
} }
project, err = project.WithSelectedServices(options.Services) project, err = project.WithSelectedServices(options.Services)
if err != nil { if err != nil {
return nil, err return nil, err
@ -176,7 +173,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
if options.Quiet { if options.Quiet {
options.Progress = progress.ModeQuiet options.Progress = progress.ModeQuiet
} }
if options.Progress == progress.ModeAuto { if options.Progress == "" {
options.Progress = os.Getenv("BUILDKIT_PROGRESS") options.Progress = os.Getenv("BUILDKIT_PROGRESS")
} }
w, err = xprogress.NewPrinter(progressCtx, os.Stdout, progressui.DisplayMode(options.Progress), w, err = xprogress.NewPrinter(progressCtx, os.Stdout, progressui.DisplayMode(options.Progress),
@ -401,7 +398,6 @@ func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, ser
return result return result
} }
//nolint:gocyclo
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) { func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
plats, err := parsePlatforms(service) plats, err := parsePlatforms(service)
if err != nil { if err != nil {
@ -476,19 +472,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
} }
attests := map[string]*string{} attests := map[string]*string{}
if options.Attestations { if !options.Provenance {
if service.Build.Provenance != "" { attests["provenance"] = nil
attests["provenance"] = attestation(service.Build.Provenance, "provenance")
}
if service.Build.SBOM != "" {
attests["sbom"] = attestation(service.Build.SBOM, "sbom")
}
}
if options.Provenance != "" {
attests["provenance"] = attestation(options.Provenance, "provenance")
}
if options.SBOM != "" {
attests["sbom"] = attestation(options.SBOM, "sbom")
} }
return build.Options{ return build.Options{
@ -498,8 +483,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile), DockerfilePath: dockerFilePath(service.Build.Context, service.Build.Dockerfile),
NamedContexts: toBuildContexts(service, project), NamedContexts: toBuildContexts(service, project),
}, },
CacheFrom: build.CreateCaches(cacheFrom), CacheFrom: pb.CreateCaches(cacheFrom.ToPB()),
CacheTo: build.CreateCaches(cacheTo), CacheTo: pb.CreateCaches(cacheTo.ToPB()),
NoCache: service.Build.NoCache, NoCache: service.Build.NoCache,
Pull: service.Build.Pull, Pull: service.Build.Pull,
BuildArgs: flatten(resolveAndMergeBuildArgs(s.dockerCli, project, service, options)), BuildArgs: flatten(resolveAndMergeBuildArgs(s.dockerCli, project, service, options)),
@ -518,16 +503,6 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
}, nil }, nil
} }
func attestation(attest string, val string) *string {
if b, err := strconv.ParseBool(val); err == nil {
s := fmt.Sprintf("type=%s,disabled=%t", attest, b)
return &s
} else {
s := fmt.Sprintf("type=%s,%s", attest, val)
return &s
}
}
func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt { func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt {
ref := map[string]*container.Ulimit{} ref := map[string]*container.Ulimit{}
for _, limit := range toUlimits(ulimits) { for _, limit := range toUlimits(ulimits) {
@ -653,11 +628,7 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) {
func addBuildDependencies(services []string, project *types.Project) []string { func addBuildDependencies(services []string, project *types.Project) []string {
servicesWithDependencies := utils.NewSet(services...) servicesWithDependencies := utils.NewSet(services...)
for _, service := range services { for _, service := range services {
s, ok := project.Services[service] b := project.Services[service].Build
if !ok {
s = project.DisabledServices[service]
}
b := s.Build
if b != nil { if b != nil {
for _, target := range b.AdditionalContexts { for _, target := range b.AdditionalContexts {
if s, found := strings.CutPrefix(target, types.ServicePrefix); found { if s, found := strings.CutPrefix(target, types.ServicePrefix); found {

View File

@ -20,12 +20,9 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"crypto/sha1"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"math/rand"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -34,18 +31,20 @@ import (
"strings" "strings"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/socket"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/builder/remotecontext/urlutil" "github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
gitutil "github.com/moby/buildkit/frontend/dockerfile/dfgitutil" "github.com/moby/buildkit/util/gitutil"
"github.com/moby/buildkit/util/progress/progressui" "github.com/moby/buildkit/util/progress/progressui"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -59,9 +58,6 @@ func buildWithBake(dockerCli command.Cli) (bool, error) {
return false, err return false, err
} }
if !bake { if !bake {
if ok {
logrus.Warnf("COMPOSE_BAKE=false is deprecated, support for internal compose builder will be removed in next release")
}
return false, nil return false, nil
} }
@ -76,7 +72,7 @@ func buildWithBake(dockerCli command.Cli) (bool, error) {
_, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{}) _, err = manager.GetPlugin("buildx", dockerCli, &cobra.Command{})
if err != nil { if err != nil {
if errdefs.IsNotFound(err) { if manager.IsNotFound(err) {
logrus.Warnf("Docker Compose is configured to build using Bake, but buildx isn't installed") logrus.Warnf("Docker Compose is configured to build using Bake, but buildx isn't installed")
return false, nil return false, nil
} }
@ -117,9 +113,7 @@ type bakeTarget struct {
Ulimits []string `json:"ulimits,omitempty"` Ulimits []string `json:"ulimits,omitempty"`
Call string `json:"call,omitempty"` Call string `json:"call,omitempty"`
Entitlements []string `json:"entitlements,omitempty"` Entitlements []string `json:"entitlements,omitempty"`
ExtraHosts map[string]string `json:"extra-hosts,omitempty"`
Outputs []string `json:"output,omitempty"` Outputs []string `json:"output,omitempty"`
Attest []string `json:"attest,omitempty"`
} }
type bakeMetadata map[string]buildStatus type bakeMetadata map[string]buildStatus
@ -132,18 +126,7 @@ type buildStatus struct {
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
eg := errgroup.Group{} eg := errgroup.Group{}
ch := make(chan *client.SolveStatus) ch := make(chan *client.SolveStatus)
if options.Progress == progress.ModeAuto { display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress))
options.Progress = os.Getenv("BUILDKIT_PROGRESS")
}
displayMode := progressui.DisplayMode(options.Progress)
out := options.Out
if out == nil {
if displayMode == progress.ModeAuto && !s.dockerCli.Out().IsTerminal() {
displayMode = progressui.PlainMode
}
out = os.Stdout // should be s.dockerCli.Out(), but NewDisplay require access to the underlying *File
}
display, err := progressui.NewDisplay(out, displayMode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -161,27 +144,13 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
privileged bool privileged bool
read []string read []string
expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
) )
// produce a unique ID for service used as bake target for serviceName, service := range serviceToBeBuild {
for serviceName := range project.Services {
t := strings.ReplaceAll(serviceName, ".", "_")
for {
if _, ok := targets[serviceName]; !ok {
targets[serviceName] = t
break
}
t += "_"
}
}
for serviceName, service := range project.Services {
if service.Build == nil { if service.Build == nil {
continue continue
} }
build := *service.Build build := *service.Build
labels := getImageBuildLabels(project, service)
args := types.Mapping{} args := types.Mapping{}
for k, v := range resolveAndMergeBuildArgs(s.dockerCli, project, service, options) { for k, v := range resolveAndMergeBuildArgs(s.dockerCli, project, service, options) {
@ -191,6 +160,9 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
args[k] = *v args[k] = *v
} }
image := api.GetImageNameOrDefault(service, project.Name)
expectedImages[serviceName] = image
entitlements := build.Entitlements entitlements := build.Entitlements
if slices.Contains(build.Entitlements, "security.insecure") { if slices.Contains(build.Entitlements, "security.insecure") {
privileged = true privileged = true
@ -209,63 +181,42 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
case len(service.Build.Platforms) > 1: case len(service.Build.Platforms) > 1:
outputs = []string{fmt.Sprintf("type=image,push=%t", push)} outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
default: default:
if push { outputs = []string{fmt.Sprintf("type=docker,load=true,push=%t", push)}
outputs = []string{"type=registry"}
} else {
outputs = []string{"type=docker"}
}
} }
read = append(read, build.Context) read = append(read, build.Context)
for _, path := range build.AdditionalContexts { for _, path := range build.AdditionalContexts {
_, _, err := gitutil.ParseGitRef(path) _, err := gitutil.ParseGitRef(path)
if !strings.Contains(path, "://") && err != nil { if !strings.Contains(path, "://") && err != nil {
read = append(read, path) read = append(read, path)
} }
} }
image := api.GetImageNameOrDefault(service, project.Name) cfg.Targets[serviceName] = bakeTarget{
expectedImages[serviceName] = image
pull := service.Build.Pull || options.Pull
noCache := service.Build.NoCache || options.NoCache
target := targets[serviceName]
cfg.Targets[target] = bakeTarget{
Context: build.Context, Context: build.Context,
Contexts: additionalContexts(build.AdditionalContexts, targets), Contexts: additionalContexts(build.AdditionalContexts),
Dockerfile: dockerFilePath(build.Context, build.Dockerfile), Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"), DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"),
Args: args, Args: args,
Labels: labels, Labels: build.Labels,
Tags: append(build.Tags, image), Tags: append(build.Tags, image),
CacheFrom: build.CacheFrom, CacheFrom: build.CacheFrom,
CacheTo: build.CacheTo, // CacheTo: TODO
NetworkMode: build.Network,
Platforms: build.Platforms, Platforms: build.Platforms,
Target: build.Target, Target: build.Target,
Secrets: toBakeSecrets(project, build.Secrets), Secrets: toBakeSecrets(project, build.Secrets),
SSH: toBakeSSH(append(build.SSH, options.SSHs...)), SSH: toBakeSSH(append(build.SSH, options.SSHs...)),
Pull: pull, Pull: options.Pull,
NoCache: noCache, NoCache: options.NoCache,
ShmSize: build.ShmSize, ShmSize: build.ShmSize,
Ulimits: toBakeUlimits(build.Ulimits), Ulimits: toBakeUlimits(build.Ulimits),
Entitlements: entitlements, Entitlements: entitlements,
ExtraHosts: toBakeExtraHosts(build.ExtraHosts),
Outputs: outputs, Outputs: outputs,
Call: call, Call: call,
Attest: toBakeAttest(build),
} }
} group.Targets = append(group.Targets, serviceName)
// create a bake group with targets for services to build
for serviceName, service := range serviceToBeBuild {
if service.Build == nil {
continue
}
group.Targets = append(group.Targets, targets[serviceName])
} }
cfg.Groups["default"] = group cfg.Groups["default"] = group
@ -281,25 +232,17 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
} }
logrus.Debugf("bake build config:\n%s", string(b)) logrus.Debugf("bake build config:\n%s", string(b))
var metadataFile string metadata, err := os.CreateTemp(os.TempDir(), "compose")
for { if err != nil {
// we don't use os.CreateTemp here as we need a temporary file name, but don't want it actually created return nil, err
// as bake relies on atomicwriter and this creates conflict during rename
metadataFile = filepath.Join(os.TempDir(), fmt.Sprintf("compose-build-metadataFile-%d.json", rand.Int31()))
if _, err = os.Stat(metadataFile); os.IsNotExist(err) {
break
}
} }
defer func() {
_ = os.Remove(metadataFile)
}()
buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{}) buildx, err := manager.GetPlugin("buildx", s.dockerCli, &cobra.Command{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadataFile} args := []string{"bake", "--file", "-", "--progress", "rawjson", "--metadata-file", metadata.Name()}
mustAllow := buildx.Version != "" && versions.GreaterThanOrEqualTo(buildx.Version[1:], "0.17.0") mustAllow := buildx.Version != "" && versions.GreaterThanOrEqualTo(buildx.Version[1:], "0.17.0")
if mustAllow { if mustAllow {
// FIXME we should prompt user about this, but this is a breaking change in UX // FIXME we should prompt user about this, but this is a breaking change in UX
@ -310,12 +253,6 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
args = append(args, "--allow", "security.insecure") args = append(args, "--allow", "security.insecure")
} }
} }
if options.SBOM != "" {
args = append(args, "--sbom="+options.SBOM)
}
if options.Provenance != "" {
args = append(args, "--provenance="+options.Provenance)
}
if options.Builder != "" { if options.Builder != "" {
args = append(args, "--builder", options.Builder) args = append(args, "--builder", options.Builder)
@ -326,21 +263,24 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
logrus.Debugf("Executing bake with args: %v", args) logrus.Debugf("Executing bake with args: %v", args)
if s.dryRun {
return dryRunBake(ctx, cfg), nil
}
cmd := exec.CommandContext(ctx, buildx.Path, args...) cmd := exec.CommandContext(ctx, buildx.Path, args...)
// Remove DOCKER_CLI_PLUGIN... variable so buildx can detect it run standalone
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
err = s.prepareShellOut(ctx, types.NewMapping(os.Environ()), cmd) // Use docker/cli mechanism to propagate termination signal to child process
server, err := socket.NewPluginServer(nil)
if err != nil { if err != nil {
return nil, err defer server.Close() //nolint:errcheck
cmd.Cancel = server.Close
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
} }
endpoint, cleanup, err := s.propagateDockerEndpoint()
if err != nil { cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
return nil, err
} // propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
cmd.Env = append(cmd.Env, endpoint...) carrier := propagation.MapCarrier{}
defer cleanup() otel.GetTextMapPropagator().Inject(ctx, &carrier)
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
cmd.Stdout = s.stdout() cmd.Stdout = s.stdout()
cmd.Stdin = bytes.NewBuffer(b) cmd.Stdin = bytes.NewBuffer(b)
@ -350,22 +290,16 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
} }
var errMessage []string var errMessage []string
reader := bufio.NewReader(pipe) scanner := bufio.NewScanner(pipe)
scanner.Split(bufio.ScanLines)
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
eg.Go(cmd.Wait) eg.Go(cmd.Wait)
for { for scanner.Scan() {
line, readErr := reader.ReadString('\n') line := scanner.Text()
if readErr != nil {
if readErr == io.EOF {
break
} else {
return nil, fmt.Errorf("failed to execute bake: %w", readErr)
}
}
decoder := json.NewDecoder(strings.NewReader(line)) decoder := json.NewDecoder(strings.NewReader(line))
var status client.SolveStatus var status client.SolveStatus
err := decoder.Decode(&status) err := decoder.Decode(&status)
@ -389,7 +323,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
return nil, fmt.Errorf("failed to execute bake: %w", err) return nil, fmt.Errorf("failed to execute bake: %w", err)
} }
b, err = os.ReadFile(metadataFile) b, err = os.ReadFile(metadata.Name())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -402,32 +336,22 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
cw := progress.ContextWriter(ctx) cw := progress.ContextWriter(ctx)
results := map[string]string{} results := map[string]string{}
for name := range serviceToBeBuild { for service, name := range expectedImages {
image := expectedImages[name] built, ok := md[service] // bake target == service name
target := targets[name]
built, ok := md[target]
if !ok { if !ok {
return nil, fmt.Errorf("build result not found in Bake metadata for service %s", name) return nil, fmt.Errorf("build result not found in Bake metadata for service %s", service)
} }
results[image] = built.Digest results[name] = built.Digest
cw.Event(progress.BuiltEvent(image)) cw.Event(progress.BuiltEvent(name))
} }
return results, nil return results, nil
} }
func toBakeExtraHosts(hosts types.HostsList) map[string]string { func additionalContexts(contexts types.Mapping) map[string]string {
m := make(map[string]string)
for k, v := range hosts {
m[k] = strings.Join(v, ",")
}
return m
}
func additionalContexts(contexts types.Mapping, targets map[string]string) map[string]string {
ac := map[string]string{} ac := map[string]string{}
for k, v := range contexts { for k, v := range contexts {
if target, found := strings.CutPrefix(v, types.ServicePrefix); found { if target, found := strings.CutPrefix(v, types.ServicePrefix); found {
v = "target:" + targets[target] v = "target:" + target
} }
ac[k] = v ac[k] = v
} }
@ -472,28 +396,20 @@ func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig)
return s return s
} }
func toBakeAttest(build types.BuildConfig) []string { func filter(environ []string, variable string) []string {
var attests []string prefix := variable + "="
filtered := make([]string, 0, len(environ))
// Handle per-service provenance configuration (only from build config, not global options) for _, val := range environ {
if build.Provenance != "" { if !strings.HasPrefix(val, prefix) {
if build.Provenance == "true" { filtered = append(filtered, val)
attests = append(attests, "type=provenance")
} else if build.Provenance != "false" {
attests = append(attests, fmt.Sprintf("type=provenance,%s", build.Provenance))
} }
} }
return filtered
}
// Handle per-service SBOM configuration (only from build config, not global options) func replace(environ []string, variable, value string) []string {
if build.SBOM != "" { filtered := filter(environ, variable)
if build.SBOM == "true" { return append(filtered, fmt.Sprintf("%s=%s", variable, value))
attests = append(attests, "type=sbom")
} else if build.SBOM != "false" {
attests = append(attests, fmt.Sprintf("type=sbom,%s", build.SBOM))
}
}
return attests
} }
func dockerFilePath(ctxName string, dockerfile string) string { func dockerFilePath(ctxName string, dockerfile string) string {
@ -506,37 +422,9 @@ func dockerFilePath(ctxName string, dockerfile string) string {
if !filepath.IsAbs(dockerfile) { if !filepath.IsAbs(dockerfile) {
dockerfile = filepath.Join(ctxName, dockerfile) dockerfile = filepath.Join(ctxName, dockerfile)
} }
dir := filepath.Dir(dockerfile) symlinks, err := filepath.EvalSymlinks(dockerfile)
symlinks, err := filepath.EvalSymlinks(dir)
if err == nil { if err == nil {
return filepath.Join(symlinks, filepath.Base(dockerfile)) return symlinks
} }
return dockerfile return dockerfile
} }
func dryRunBake(ctx context.Context, cfg bakeConfig) map[string]string {
w := progress.ContextWriter(ctx)
bakeResponse := map[string]string{}
for name, target := range cfg.Targets {
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
displayDryRunBuildEvent(w, name, dryRunUUID, target.Tags[0])
bakeResponse[name] = dryRunUUID
}
for name := range bakeResponse {
w.Event(progress.BuiltEvent(name))
}
return bakeResponse
}
func displayDryRunBuildEvent(w progress.Writer, name string, dryRunUUID, tag string) {
w.Event(progress.Event{
ID: name + " ==>",
Status: progress.Done,
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
})
w.Event(progress.Event{
ID: name + " ==> ==>",
Status: progress.Done,
Text: fmt.Sprintf(`naming to %s`, tag),
})
}

View File

@ -70,7 +70,16 @@ func (s composeService) dryRunBuildResponse(ctx context.Context, name string, op
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
buildResponse := map[string]*client.SolveResponse{} buildResponse := map[string]*client.SolveResponse{}
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name))) dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
displayDryRunBuildEvent(w, name, dryRunUUID, options.Tags[0]) w.Event(progress.Event{
ID: "==>",
Status: progress.Done,
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
})
w.Event(progress.Event{
ID: "==> ==>",
Status: progress.Done,
Text: fmt.Sprintf(`naming to %s`, options.Tags[0]),
})
buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{ buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
"containerimage.digest": dryRunUUID, "containerimage.digest": dryRunUUID,
}} }}

View File

@ -22,6 +22,23 @@ import (
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
) )
const (
// ContainerCreated created status
ContainerCreated = "created"
// ContainerRestarting restarting status
ContainerRestarting = "restarting"
// ContainerRunning running status
ContainerRunning = "running"
// ContainerRemoving removing status
ContainerRemoving = "removing"
// ContainerPaused paused status
ContainerPaused = "paused"
// ContainerExited exited status
ContainerExited = "exited"
// ContainerDead dead status
ContainerDead = "dead"
)
var _ io.ReadCloser = ContainerStdout{} var _ io.ReadCloser = ContainerStdout{}
// ContainerStdout implement ReadCloser for moby.HijackedResponse // ContainerStdout implement ReadCloser for moby.HijackedResponse

View File

@ -100,7 +100,7 @@ func (s *composeService) getSpecifiedContainer(ctx context.Context, projectName
IsOneOffLabelTrueX := containers[i].Labels[api.OneoffLabel] == "True" IsOneOffLabelTrueX := containers[i].Labels[api.OneoffLabel] == "True"
IsOneOffLabelTrueY := containers[j].Labels[api.OneoffLabel] == "True" IsOneOffLabelTrueY := containers[j].Labels[api.OneoffLabel] == "True"
if IsOneOffLabelTrueX || IsOneOffLabelTrueY { if numberLabelX == numberLabelY {
return !IsOneOffLabelTrueX && IsOneOffLabelTrueY return !IsOneOffLabelTrueX && IsOneOffLabelTrueY
} }
@ -128,6 +128,12 @@ func isService(services ...string) containerPredicate {
} }
} }
func isRunning() containerPredicate {
return func(c container.Summary) bool {
return c.State == "running"
}
}
// isOrphaned is a predicate to select containers without a matching service definition in compose project // isOrphaned is a predicate to select containers without a matching service definition in compose project
func isOrphaned(project *types.Project) containerPredicate { func isOrphaned(project *types.Project) containerPredicate {
services := append(project.ServiceNames(), project.DisabledServiceNames()...) services := append(project.ServiceNames(), project.DisabledServiceNames()...)
@ -135,7 +141,7 @@ func isOrphaned(project *types.Project) containerPredicate {
// One-off container // One-off container
v, ok := c.Labels[api.OneoffLabel] v, ok := c.Labels[api.OneoffLabel]
if ok && v == "True" { if ok && v == "True" {
return c.State == container.StateExited || c.State == container.StateDead return c.State == ContainerExited || c.State == ContainerDead
} }
// Service that is not defined in the compose model // Service that is not defined in the compose model
service := c.Labels[api.ServiceLabel] service := c.Labels[api.ServiceLabel]

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"maps"
"slices" "slices"
"sort" "sort"
"strconv" "strconv"
@ -30,7 +29,7 @@ import (
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/platforms" "github.com/containerd/platforms"
"github.com/docker/docker/api/types/container" containerType "github.com/docker/docker/api/types/container"
mmount "github.com/docker/docker/api/types/mount" mmount "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
@ -152,19 +151,19 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
}) })
slices.Reverse(containers) slices.Reverse(containers)
for i, ctr := range containers { for i, container := range containers {
if i >= expected { if i >= expected {
// Scale Down // Scale Down
// As we sorted containers, obsolete ones and/or highest number will be removed // As we sorted containers, obsolete ones and/or highest number will be removed
ctr := ctr container := container
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(ctr)...) traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error { eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "service/scale/down", traceOpts, func(ctx context.Context) error {
return c.service.stopAndRemoveContainer(ctx, ctr, &service, timeout, false) return c.service.stopAndRemoveContainer(ctx, container, &service, timeout, false)
})) }))
continue continue
} }
mustRecreate, err := c.mustRecreate(service, ctr, recreate) mustRecreate, err := c.mustRecreate(service, container, recreate)
if err != nil { if err != nil {
return err return err
} }
@ -174,9 +173,9 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
return err return err
} }
i, ctr := i, ctr i, container := i, container
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "container/recreate", tracing.ContainerOptions(ctr), func(ctx context.Context) error { eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "container/recreate", tracing.ContainerOptions(container), func(ctx context.Context) error {
recreated, err := c.service.recreateContainer(ctx, project, service, ctr, inherit, timeout) recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
updated[i] = recreated updated[i] = recreated
return err return err
})) }))
@ -185,20 +184,20 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
// Enforce non-diverged containers are running // Enforce non-diverged containers are running
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
name := getContainerProgressName(ctr) name := getContainerProgressName(container)
switch ctr.State { switch container.State {
case container.StateRunning: case ContainerRunning:
w.Event(progress.RunningEvent(name)) w.Event(progress.RunningEvent(name))
case container.StateCreated: case ContainerCreated:
case container.StateRestarting: case ContainerRestarting:
case container.StateExited: case ContainerExited:
default: default:
ctr := ctr container := container
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(ctr), func(ctx context.Context) error { eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
return c.service.startContainer(ctx, ctr) return c.service.startContainer(ctx, container)
})) }))
} }
updated[i] = ctr updated[i] = container
} }
next := nextContainerNumber(containers) next := nextContainerNumber(containers)
@ -214,8 +213,8 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
UseNetworkAliases: true, UseNetworkAliases: true,
Labels: mergeLabels(service.Labels, service.CustomLabels), Labels: mergeLabels(service.Labels, service.CustomLabels),
} }
ctr, err := c.service.createContainer(ctx, project, service, name, number, opts) container, err := c.service.createContainer(ctx, project, service, name, number, opts)
updated[actual+i] = ctr updated[actual+i] = container
return err return err
})) }))
continue continue
@ -237,7 +236,7 @@ func (c *convergence) stopDependentContainers(ctx context.Context, project *type
err := c.service.stop(ctx, project.Name, api.StopOptions{ err := c.service.stop(ctx, project.Name, api.StopOptions{
Services: dependents, Services: dependents,
Project: project, Project: project,
}, nil) })
if err != nil { if err != nil {
return err return err
} }
@ -245,7 +244,7 @@ func (c *convergence) stopDependentContainers(ctx context.Context, project *type
for _, name := range dependents { for _, name := range dependents {
dependentStates := c.getObservedState(name) dependentStates := c.getObservedState(name)
for i, dependent := range dependentStates { for i, dependent := range dependentStates {
dependent.State = container.StateExited dependent.State = ContainerExited
dependentStates[i] = dependent dependentStates[i] = dependent
} }
c.setObservedState(name, dependentStates) c.setObservedState(name, dependentStates)
@ -328,7 +327,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
return nil return nil
} }
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual container.Summary, policy string) (bool, error) { func (c *convergence) mustRecreate(expected types.ServiceConfig, actual containerType.Summary, policy string) (bool, error) {
if policy == api.RecreateNever { if policy == api.RecreateNever {
return false, nil return false, nil
} }
@ -360,7 +359,7 @@ func (c *convergence) mustRecreate(expected types.ServiceConfig, actual containe
return false, nil return false, nil
} }
func checkExpectedNetworks(expected types.ServiceConfig, actual container.Summary, networks map[string]string) bool { func checkExpectedNetworks(expected types.ServiceConfig, actual containerType.Summary, networks map[string]string) bool {
// check the networks container is connected to are the expected ones // check the networks container is connected to are the expected ones
for net := range expected.Networks { for net := range expected.Networks {
id := networks[net] id := networks[net]
@ -383,7 +382,7 @@ func checkExpectedNetworks(expected types.ServiceConfig, actual container.Summar
return false return false
} }
func checkExpectedVolumes(expected types.ServiceConfig, actual container.Summary, volumes map[string]string) bool { func checkExpectedVolumes(expected types.ServiceConfig, actual containerType.Summary, volumes map[string]string) bool {
// check container's volume mounts and search for the expected ones // check container's volume mounts and search for the expected ones
for _, vol := range expected.Volumes { for _, vol := range expected.Volumes {
if vol.Type != string(mmount.TypeVolume) { if vol.Type != string(mmount.TypeVolume) {
@ -423,7 +422,7 @@ func getDefaultContainerName(projectName, serviceName, index string) string {
return strings.Join([]string{projectName, serviceName, index}, api.Separator) return strings.Join([]string{projectName, serviceName, index}, api.Separator)
} }
func getContainerProgressName(ctr container.Summary) string { func getContainerProgressName(ctr containerType.Summary) string {
return "Container " + getCanonicalContainerName(ctr) return "Container " + getCanonicalContainerName(ctr)
} }
@ -564,14 +563,11 @@ func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceD
} else if service.GetScale() == 0 { } else if service.GetScale() == 0 {
// don't wait for the dependency which configured to have 0 containers running // don't wait for the dependency which configured to have 0 containers running
return false, nil return false, nil
} else if service.Provider != nil {
// don't wait for provider services
return false, nil
} }
return true, nil return true, nil
} }
func nextContainerNumber(containers []container.Summary) int { func nextContainerNumber(containers []containerType.Summary) int {
maxNumber := 0 maxNumber := 0
for _, c := range containers { for _, c := range containers {
s, ok := c.Labels[api.ContainerNumberLabel] s, ok := c.Labels[api.ContainerNumberLabel]
@ -592,7 +588,7 @@ func nextContainerNumber(containers []container.Summary) int {
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
name string, number int, opts createOptions, name string, number int, opts createOptions,
) (ctr container.Summary, err error) { ) (ctr containerType.Summary, err error) {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eventName := "Container " + name eventName := "Container " + name
w.Event(progress.CreatingEvent(eventName)) w.Event(progress.CreatingEvent(eventName))
@ -612,8 +608,8 @@ func (s *composeService) createContainer(ctx context.Context, project *types.Pro
} }
func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
replaced container.Summary, inherit bool, timeout *time.Duration, replaced containerType.Summary, inherit bool, timeout *time.Duration,
) (created container.Summary, err error) { ) (created containerType.Summary, err error) {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eventName := getContainerProgressName(replaced) eventName := getContainerProgressName(replaced)
w.Event(progress.NewEvent(eventName, progress.Working, "Recreate")) w.Event(progress.NewEvent(eventName, progress.Working, "Recreate"))
@ -632,22 +628,17 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
return created, err return created, err
} }
var inherited *container.Summary var inherited *containerType.Summary
if inherit { if inherit {
inherited = &replaced inherited = &replaced
} }
replacedContainerName := service.ContainerName
if replacedContainerName == "" {
replacedContainerName = service.Name + api.Separator + strconv.Itoa(number)
}
name := getContainerName(project.Name, service, number) name := getContainerName(project.Name, service, number)
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name) tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
opts := createOptions{ opts := createOptions{
AutoRemove: false, AutoRemove: false,
AttachStdin: false, AttachStdin: false,
UseNetworkAliases: true, UseNetworkAliases: true,
Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replacedContainerName), Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID),
} }
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, opts, w) created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, opts, w)
if err != nil { if err != nil {
@ -655,17 +646,17 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
} }
timeoutInSecond := utils.DurationSecondToInt(timeout) timeoutInSecond := utils.DurationSecondToInt(timeout)
err = s.apiClient().ContainerStop(ctx, replaced.ID, container.StopOptions{Timeout: timeoutInSecond}) err = s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
if err != nil { if err != nil {
return created, err return created, err
} }
err = s.apiClient().ContainerRemove(ctx, replaced.ID, container.RemoveOptions{}) err = s.apiClient().ContainerRemove(ctx, replaced.ID, containerType.RemoveOptions{})
if err != nil { if err != nil {
return created, err return created, err
} }
err = s.apiClient().ContainerRename(ctx, tmpName, name) err = s.apiClient().ContainerRename(ctx, created.ID, name)
if err != nil { if err != nil {
return created, err return created, err
} }
@ -677,12 +668,12 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
// force sequential calls to ContainerStart to prevent race condition in engine assigning ports from ranges // force sequential calls to ContainerStart to prevent race condition in engine assigning ports from ranges
var startMx sync.Mutex var startMx sync.Mutex
func (s *composeService) startContainer(ctx context.Context, ctr container.Summary) error { func (s *composeService) startContainer(ctx context.Context, ctr containerType.Summary) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
w.Event(progress.NewEvent(getContainerProgressName(ctr), progress.Working, "Restart")) w.Event(progress.NewEvent(getContainerProgressName(ctr), progress.Working, "Restart"))
startMx.Lock() startMx.Lock()
defer startMx.Unlock() defer startMx.Unlock()
err := s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{}) err := s.apiClient().ContainerStart(ctx, ctr.ID, containerType.StartOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -695,11 +686,11 @@ func (s *composeService) createMobyContainer(ctx context.Context,
service types.ServiceConfig, service types.ServiceConfig,
name string, name string,
number int, number int,
inherit *container.Summary, inherit *containerType.Summary,
opts createOptions, opts createOptions,
w progress.Writer, w progress.Writer,
) (container.Summary, error) { ) (containerType.Summary, error) {
var created container.Summary var created containerType.Summary
cfgs, err := s.getCreateConfigs(ctx, project, service, number, inherit, opts) cfgs, err := s.getCreateConfigs(ctx, project, service, number, inherit, opts)
if err != nil { if err != nil {
return created, err return created, err
@ -733,11 +724,11 @@ func (s *composeService) createMobyContainer(ctx context.Context,
if err != nil { if err != nil {
return created, err return created, err
} }
created = container.Summary{ created = containerType.Summary{
ID: inspectedContainer.ID, ID: inspectedContainer.ID,
Labels: inspectedContainer.Config.Labels, Labels: inspectedContainer.Config.Labels,
Names: []string{inspectedContainer.Name}, Names: []string{inspectedContainer.Name},
NetworkSettings: &container.NetworkSettingsSummary{ NetworkSettings: &containerType.NetworkSettingsSummary{
Networks: inspectedContainer.NetworkSettings.Networks, Networks: inspectedContainer.NetworkSettings.Networks,
}, },
} }
@ -765,7 +756,14 @@ func (s *composeService) createMobyContainer(ctx context.Context,
} }
} }
} }
return created, nil
err = s.injectSecrets(ctx, project, service, created.ID)
if err != nil {
return created, err
}
err = s.injectConfigs(ctx, project, service, created.ID)
return created, err
} }
// getLinks mimics V1 compose/service.py::Service::_get_links() // getLinks mimics V1 compose/service.py::Service::_get_links()
@ -828,33 +826,33 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) { func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
for _, c := range containers { for _, c := range containers {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID) container, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil { if err != nil {
return false, err return false, err
} }
name := ctr.Name[1:] name := container.Name[1:]
if ctr.State.Status == container.StateExited { if container.State.Status == "exited" {
return false, fmt.Errorf("container %s exited (%d)", name, ctr.State.ExitCode) return false, fmt.Errorf("container %s exited (%d)", name, container.State.ExitCode)
} }
if ctr.Config.Healthcheck == nil && fallbackRunning { if container.Config.Healthcheck == nil && fallbackRunning {
// Container does not define a health check, but we can fall back to "running" state // Container does not define a health check, but we can fall back to "running" state
return ctr.State != nil && ctr.State.Status == container.StateRunning, nil return container.State != nil && container.State.Status == "running", nil
} }
if ctr.State == nil || ctr.State.Health == nil { if container.State == nil || container.State.Health == nil {
return false, fmt.Errorf("container %s has no healthcheck configured", name) return false, fmt.Errorf("container %s has no healthcheck configured", name)
} }
switch ctr.State.Health.Status { switch container.State.Health.Status {
case container.Healthy: case containerType.Healthy:
// Continue by checking the next container. // Continue by checking the next container.
case container.Unhealthy: case containerType.Unhealthy:
return false, fmt.Errorf("container %s is unhealthy", name) return false, fmt.Errorf("container %s is unhealthy", name)
case container.Starting: case containerType.Starting:
return false, nil return false, nil
default: default:
return false, fmt.Errorf("container %s had unexpected health status %q", name, ctr.State.Health.Status) return false, fmt.Errorf("container %s had unexpected health status %q", name, container.State.Health.Status)
} }
} }
return true, nil return true, nil
@ -862,12 +860,12 @@ func (s *composeService) isServiceHealthy(ctx context.Context, containers Contai
func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) { func (s *composeService) isServiceCompleted(ctx context.Context, containers Containers) (bool, int, error) {
for _, c := range containers { for _, c := range containers {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID) container, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
if ctr.State != nil && ctr.State.Status == container.StateExited { if container.State != nil && container.State.Status == "exited" {
return true, ctr.State.ExitCode, nil return true, container.State.ExitCode, nil
} }
} }
return false, 0, nil return false, 0, nil
@ -896,23 +894,12 @@ func (s *composeService) startService(ctx context.Context,
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
for _, ctr := range containers.filter(isService(service.Name)) { for _, ctr := range containers.filter(isService(service.Name)) {
if ctr.State == container.StateRunning { if ctr.State == ContainerRunning {
continue continue
} }
err = s.injectSecrets(ctx, project, service, ctr.ID)
if err != nil {
return err
}
err = s.injectConfigs(ctx, project, service, ctr.ID)
if err != nil {
return err
}
eventName := getContainerProgressName(ctr) eventName := getContainerProgressName(ctr)
w.Event(progress.StartingEvent(eventName)) w.Event(progress.StartingEvent(eventName))
err = s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{}) err = s.apiClient().ContainerStart(ctx, ctr.ID, containerType.StartOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -932,7 +919,9 @@ func (s *composeService) startService(ctx context.Context,
func mergeLabels(ls ...types.Labels) types.Labels { func mergeLabels(ls ...types.Labels) types.Labels {
merged := types.Labels{} merged := types.Labels{}
for _, l := range ls { for _, l := range ls {
maps.Copy(merged, l) for k, v := range l {
merged[k] = v
}
} }
return merged return merged
} }

View File

@ -31,6 +31,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/system"
"github.com/moby/go-archive" "github.com/moby/go-archive"
) )
@ -160,7 +161,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
// If the destination is a symbolic link, we should evaluate it. // If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 { if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget linkTarget := dstStat.LinkTarget
if !isAbs(linkTarget) { if !system.IsAbs(linkTarget) {
// Join with the parent directory. // Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath) dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget) linkTarget = filepath.Join(dstParent, linkTarget)
@ -263,7 +264,7 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
// If the destination is a symbolic link, we should follow it. // If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 { if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget linkTarget := srcStat.LinkTarget
if !isAbs(linkTarget) { if !system.IsAbs(linkTarget) {
// Join with the parent directory. // Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath) srcParent, _ := archive.SplitPathDirEntry(srcPath)
linkTarget = filepath.Join(srcParent, linkTarget) linkTarget = filepath.Join(srcParent, linkTarget)
@ -301,20 +302,8 @@ func (s *composeService) copyFromContainer(ctx context.Context, containerID, src
return archive.CopyTo(preArchive, srcInfo, dstPath) return archive.CopyTo(preArchive, srcInfo, dstPath)
} }
// IsAbs is a platform-agnostic wrapper for filepath.IsAbs.
//
// On Windows, golang filepath.IsAbs does not consider a path \windows\system32
// as absolute as it doesn't start with a drive-letter/colon combination. However,
// in docker we need to verify things such as WORKDIR /windows/system32 in
// a Dockerfile (which gets translated to \windows\system32 when being processed
// by the daemon). This SHOULD be treated as absolute from a docker processing
// perspective.
func isAbs(path string) bool {
return filepath.IsAbs(path) || strings.HasPrefix(path, string(os.PathSeparator))
}
func splitCpArg(arg string) (ctr, path string) { func splitCpArg(arg string) (ctr, path string) {
if isAbs(arg) { if system.IsAbs(arg) {
// Explicit local absolute path, e.g., `C:\foo` or `/foo`. // Explicit local absolute path, e.g., `C:\foo` or `/foo`.
return "", arg return "", arg
} }

View File

@ -30,12 +30,13 @@ import (
"github.com/compose-spec/compose-go/v2/paths" "github.com/compose-spec/compose-go/v2/paths"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/docker/docker/api/types/blkiodev" "github.com/docker/docker/api/types/blkiodev"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
volumetypes "github.com/docker/docker/api/types/volume" volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
@ -82,11 +83,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
return err return err
} }
err = s.ensureModels(ctx, project, options.QuietPull)
if err != nil {
return err
}
prepareNetworks(project) prepareNetworks(project)
networks, err := s.ensureNetworks(ctx, project) networks, err := s.ensureNetworks(ctx, project)
@ -118,13 +114,6 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
"--remove-orphans flag to clean it up.", orphans.names()) "--remove-orphans flag to clean it up.", orphans.names())
} }
} }
// Temporary implementation of use_api_socket until we get actual support inside docker engine
project, err = s.useAPISocket(project)
if err != nil {
return err
}
return newConvergence(options.Services, observedState, networks, volumes, s).apply(ctx, project, options) return newConvergence(options.Services, observedState, networks, volumes, s).apply(ctx, project, options)
} }
@ -180,12 +169,15 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
return createConfigs{}, err return createConfigs{}, err
} }
var runCmd, entrypoint []string var (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
)
if service.Command != nil { if service.Command != nil {
runCmd = service.Command runCmd = strslice.StrSlice(service.Command)
} }
if service.Entrypoint != nil { if service.Entrypoint != nil {
entrypoint = service.Entrypoint entrypoint = strslice.StrSlice(service.Entrypoint)
} }
var ( var (
@ -282,8 +274,8 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
Annotations: service.Annotations, Annotations: service.Annotations,
Binds: binds, Binds: binds,
Mounts: mounts, Mounts: mounts,
CapAdd: service.CapAdd, CapAdd: strslice.StrSlice(service.CapAdd),
CapDrop: service.CapDrop, CapDrop: strslice.StrSlice(service.CapDrop),
NetworkMode: networkMode, NetworkMode: networkMode,
Init: service.Init, Init: service.Init,
IpcMode: container.IpcMode(service.Ipc), IpcMode: container.IpcMode(service.Ipc),
@ -1262,7 +1254,7 @@ func (s *composeService) ensureNetwork(ctx context.Context, project *types.Proje
} }
id, err := s.resolveOrCreateNetwork(ctx, project, name, n) id, err := s.resolveOrCreateNetwork(ctx, project, name, n)
if errdefs.IsConflict(err) { if cerrdefs.IsConflict(err) {
// Maybe another execution of `docker compose up|run` created same network // Maybe another execution of `docker compose up|run` created same network
// let's retry once // let's retry once
return s.resolveOrCreateNetwork(ctx, project, name, n) return s.resolveOrCreateNetwork(ctx, project, name, n)
@ -1428,7 +1420,7 @@ func (s *composeService) removeDivergedNetwork(ctx context.Context, project *typ
err := s.stop(ctx, project.Name, api.StopOptions{ err := s.stop(ctx, project.Name, api.StopOptions{
Services: services, Services: services,
Project: project, Project: project,
}, nil) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1497,7 +1489,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{}) sn, err := s.apiClient().NetworkInspect(ctx, n.Name, network.InspectOptions{})
if err == nil { if err == nil {
networks = append(networks, sn) networks = append(networks, sn)
} else if !errdefs.IsNotFound(err) { } else if !cerrdefs.IsNotFound(err) {
return "", err return "", err
} }
@ -1534,7 +1526,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) { func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name) inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
if err != nil { if err != nil {
if !errdefs.IsNotFound(err) { if !cerrdefs.IsNotFound(err) {
return "", err return "", err
} }
if volume.External { if volume.External {
@ -1599,7 +1591,7 @@ func (s *composeService) removeDivergedVolume(ctx context.Context, name string,
err := s.stop(ctx, project.Name, api.StopOptions{ err := s.stop(ctx, project.Name, api.StopOptions{
Services: services, Services: services,
Project: project, Project: project,
}, nil) })
if err != nil { if err != nil {
return err return err
} }
@ -1623,7 +1615,7 @@ func (s *composeService) removeDivergedVolume(ctx context.Context, name string,
} }
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error { func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
eventName := fmt.Sprintf("Volume %s", volume.Name) eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName)) w.Event(progress.CreatingEvent(eventName))
hash, err := VolumeHash(volume) hash, err := VolumeHash(volume)

View File

@ -23,7 +23,7 @@ import (
"time" "time"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils" "github.com/docker/compose/v2/pkg/utils"
@ -219,7 +219,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
continue continue
} }
nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{}) nw, err := s.apiClient().NetworkInspect(ctx, net.ID, network.InspectOptions{})
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
w.Event(progress.NewEvent(eventName, progress.Warning, "No resource found to remove")) w.Event(progress.NewEvent(eventName, progress.Warning, "No resource found to remove"))
return nil return nil
} }
@ -233,7 +233,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
} }
if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil { if err := s.apiClient().NetworkRemove(ctx, net.ID); err != nil {
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
continue continue
} }
w.Event(progress.ErrorEvent(eventName)) w.Event(progress.ErrorEvent(eventName))
@ -261,11 +261,11 @@ func (s *composeService) removeImage(ctx context.Context, image string, w progre
w.Event(progress.NewEvent(id, progress.Done, "Removed")) w.Event(progress.NewEvent(id, progress.Done, "Removed"))
return nil return nil
} }
if errdefs.IsConflict(err) { if cerrdefs.IsConflict(err) {
w.Event(progress.NewEvent(id, progress.Warning, "Resource is still in use")) w.Event(progress.NewEvent(id, progress.Warning, "Resource is still in use"))
return nil return nil
} }
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
w.Event(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove")) w.Event(progress.NewEvent(id, progress.Done, "Warning: No resource found to remove"))
return nil return nil
} }
@ -276,7 +276,7 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
resource := fmt.Sprintf("Volume %s", id) resource := fmt.Sprintf("Volume %s", id)
_, err := s.apiClient().VolumeInspect(ctx, id) _, err := s.apiClient().VolumeInspect(ctx, id)
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
// Already gone // Already gone
return nil return nil
} }
@ -287,33 +287,25 @@ func (s *composeService) removeVolume(ctx context.Context, id string, w progress
w.Event(progress.NewEvent(resource, progress.Done, "Removed")) w.Event(progress.NewEvent(resource, progress.Done, "Removed"))
return nil return nil
} }
if errdefs.IsConflict(err) { if cerrdefs.IsConflict(err) {
w.Event(progress.NewEvent(resource, progress.Warning, "Resource is still in use")) w.Event(progress.NewEvent(resource, progress.Warning, "Resource is still in use"))
return nil return nil
} }
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove")) w.Event(progress.NewEvent(resource, progress.Done, "Warning: No resource found to remove"))
return nil return nil
} }
return err return err
} }
func (s *composeService) stopContainer( func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, service *types.ServiceConfig, ctr containerType.Summary, timeout *time.Duration) error {
ctx context.Context, w progress.Writer,
service *types.ServiceConfig, ctr containerType.Summary,
timeout *time.Duration, listener api.ContainerEventListener,
) error {
eventName := getContainerProgressName(ctr) eventName := getContainerProgressName(ctr)
w.Event(progress.StoppingEvent(eventName)) w.Event(progress.StoppingEvent(eventName))
if service != nil { if service != nil {
for _, hook := range service.PreStop { for _, hook := range service.PreStop {
err := s.runHook(ctx, ctr, *service, hook, listener) err := s.runHook(ctx, ctr, *service, hook, nil)
if err != nil { if err != nil {
// Ignore errors indicating that some containers were already stopped or removed.
if errdefs.IsNotFound(err) || errdefs.IsConflict(err) {
return nil
}
return err return err
} }
} }
@ -329,15 +321,11 @@ func (s *composeService) stopContainer(
return nil return nil
} }
func (s *composeService) stopContainers( func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, serv *types.ServiceConfig, containers []containerType.Summary, timeout *time.Duration) error {
ctx context.Context, w progress.Writer,
serv *types.ServiceConfig, containers []containerType.Summary,
timeout *time.Duration, listener api.ContainerEventListener,
) error {
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, ctr := range containers { for _, ctr := range containers {
eg.Go(func() error { eg.Go(func() error {
return s.stopContainer(ctx, w, serv, ctr, timeout, listener) return s.stopContainer(ctx, w, serv, ctr, timeout)
}) })
} }
return eg.Wait() return eg.Wait()
@ -356,8 +344,8 @@ func (s *composeService) removeContainers(ctx context.Context, containers []cont
func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr containerType.Summary, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error { func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr containerType.Summary, service *types.ServiceConfig, timeout *time.Duration, volumes bool) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eventName := getContainerProgressName(ctr) eventName := getContainerProgressName(ctr)
err := s.stopContainer(ctx, w, service, ctr, timeout, nil) err := s.stopContainer(ctx, w, service, ctr, timeout)
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
w.Event(progress.RemovedEvent(eventName)) w.Event(progress.RemovedEvent(eventName))
return nil return nil
} }
@ -369,7 +357,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
Force: true, Force: true,
RemoveVolumes: volumes, RemoveVolumes: volumes,
}) })
if err != nil && !errdefs.IsNotFound(err) && !errdefs.IsConflict(err) { if err != nil && !cerrdefs.IsNotFound(err) && !cerrdefs.IsConflict(err) {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing")) w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
return err return err
} }

View File

@ -24,13 +24,13 @@ import (
"testing" "testing"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume" "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
@ -326,7 +326,7 @@ func TestDownRemoveImages(t *testing.T) {
if exists { if exists {
resp.RepoTags = []string{img} resp.RepoTags = []string{img}
} else { } else {
err = errdefs.ErrNotFound.WithMessage(fmt.Sprintf("test specified that image %q should not exist", img)) err = errdefs.NotFound(fmt.Errorf("test specified that image %q should not exist", img))
} }
api.EXPECT().ImageInspect(gomock.Any(), img). api.EXPECT().ImageInspect(gomock.Any(), img).

View File

@ -32,8 +32,6 @@ func (s *composeService) Events(ctx context.Context, projectName string, options
projectName = strings.ToLower(projectName) projectName = strings.ToLower(projectName)
evts, errors := s.apiClient().Events(ctx, events.ListOptions{ evts, errors := s.apiClient().Events(ctx, events.ListOptions{
Filters: filters.NewArgs(projectFilter(projectName)), Filters: filters.NewArgs(projectFilter(projectName)),
Since: options.Since,
Until: options.Until,
}) })
for { for {
select { select {

View File

@ -52,7 +52,7 @@ func (s *composeService) Exec(ctx context.Context, projectName string, options a
err = container.RunExec(ctx, s.dockerCli, target.ID, exec) err = container.RunExec(ctx, s.dockerCli, target.ID, exec)
var sterr cli.StatusError var sterr cli.StatusError
if errors.As(err, &sterr) { if errors.As(err, &sterr) {
return sterr.StatusCode, err return sterr.StatusCode, nil
} }
return 0, err return 0, err
} }

View File

@ -32,11 +32,11 @@ import (
func (s composeService) runHook(ctx context.Context, ctr container.Summary, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error { func (s composeService) runHook(ctx context.Context, ctr container.Summary, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error {
wOut := utils.GetWriter(func(line string) { wOut := utils.GetWriter(func(line string) {
listener(api.ContainerEvent{ listener(api.ContainerEvent{
Type: api.HookEventLog, Type: api.HookEventLog,
Source: getContainerNameWithoutProject(ctr) + " ->", Container: getContainerNameWithoutProject(ctr) + " ->",
ID: ctr.ID, ID: ctr.ID,
Service: service.Name, Service: service.Name,
Line: line, Line: line,
}) })
}) })
defer wOut.Close() //nolint:errcheck defer wOut.Close() //nolint:errcheck
@ -48,6 +48,7 @@ func (s composeService) runHook(ctx context.Context, ctr container.Summary, serv
Env: ToMobyEnv(hook.Environment), Env: ToMobyEnv(hook.Environment),
WorkingDir: hook.WorkingDir, WorkingDir: hook.WorkingDir,
Cmd: hook.Command, Cmd: hook.Command,
Detach: detached,
AttachStdout: !detached, AttachStdout: !detached,
AttachStderr: !detached, AttachStderr: !detached,
}) })

View File

@ -23,7 +23,7 @@ import (
"sync" "sync"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
@ -204,7 +204,7 @@ func (p *ImagePruner) filterImagesByExistence(ctx context.Context, imageNames []
for _, img := range imageNames { for _, img := range imageNames {
eg.Go(func() error { eg.Go(func() error {
_, err := p.client.ImageInspect(ctx, img) _, err := p.client.ImageInspect(ctx, img)
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
// err on the side of caution: only skip if we successfully // err on the side of caution: only skip if we successfully
// queried the API and got back a definitive "not exists" // queried the API and got back a definitive "not exists"
return nil return nil

View File

@ -22,9 +22,8 @@ import (
"slices" "slices"
"strings" "strings"
"sync" "sync"
"time"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/platforms" "github.com/containerd/platforms"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@ -91,11 +90,6 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
} }
} }
created, err := time.Parse(time.RFC3339Nano, image.Created)
if err != nil {
return err
}
mux.Lock() mux.Lock()
defer mux.Unlock() defer mux.Unlock()
summary[getCanonicalContainerName(c)] = api.ImageSummary{ summary[getCanonicalContainerName(c)] = api.ImageSummary{
@ -109,7 +103,6 @@ func (s *composeService) Images(ctx context.Context, projectName string, options
Variant: image.Variant, Variant: image.Variant,
}, },
Size: image.Size, Size: image.Size,
Created: created,
LastTagTime: image.Metadata.LastTagTime, LastTagTime: image.Metadata.LastTagTime,
} }
return nil return nil
@ -128,7 +121,7 @@ func (s *composeService) getImageSummaries(ctx context.Context, repoTags []strin
eg.Go(func() error { eg.Go(func() error {
inspect, err := s.apiClient().ImageInspect(ctx, repoTag) inspect, err := s.apiClient().ImageInspect(ctx, repoTag)
if err != nil { if err != nil {
if errdefs.IsNotFound(err) { if cerrdefs.IsNotFound(err) {
return nil return nil
} }
return fmt.Errorf("unable to get image '%s': %w", repoTag, err) return fmt.Errorf("unable to get image '%s': %w", repoTag, err)

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"strings" "strings"
"testing" "testing"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
@ -45,12 +44,8 @@ func TestImages(t *testing.T) {
args := filters.NewArgs(projectFilter(strings.ToLower(testProject))) args := filters.NewArgs(projectFilter(strings.ToLower(testProject)))
listOpts := container.ListOptions{All: true, Filters: args} listOpts := container.ListOptions{All: true, Filters: args}
api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes() api.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{APIVersion: "1.96"}, nil).AnyTimes()
timeStr1 := "2025-06-06T06:06:06.000000000Z" image1 := imageInspect("image1", "foo:1", 12345)
created1, _ := time.Parse(time.RFC3339Nano, timeStr1) image2 := imageInspect("image2", "bar:2", 67890)
timeStr2 := "2025-03-03T03:03:03.000000000Z"
created2, _ := time.Parse(time.RFC3339Nano, timeStr2)
image1 := imageInspect("image1", "foo:1", 12345, timeStr1)
image2 := imageInspect("image2", "bar:2", 67890, timeStr2)
api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2) api.EXPECT().ImageInspect(anyCancellableContext(), "foo:1").Return(image1, nil).MaxTimes(2)
api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil) api.EXPECT().ImageInspect(anyCancellableContext(), "bar:2").Return(image2, nil)
c1 := containerDetail("service1", "123", "running", "foo:1") c1 := containerDetail("service1", "123", "running", "foo:1")
@ -67,36 +62,32 @@ func TestImages(t *testing.T) {
Repository: "foo", Repository: "foo",
Tag: "1", Tag: "1",
Size: 12345, Size: 12345,
Created: created1,
}, },
"456": { "456": {
ID: "image2", ID: "image2",
Repository: "bar", Repository: "bar",
Tag: "2", Tag: "2",
Size: 67890, Size: 67890,
Created: created2,
}, },
"789": { "789": {
ID: "image1", ID: "image1",
Repository: "foo", Repository: "foo",
Tag: "1", Tag: "1",
Size: 12345, Size: 12345,
Created: created1,
}, },
} }
assert.NilError(t, err) assert.NilError(t, err)
assert.DeepEqual(t, images, expected) assert.DeepEqual(t, images, expected)
} }
func imageInspect(id string, imageReference string, size int64, created string) image.InspectResponse { func imageInspect(id string, imageReference string, size int64) image.InspectResponse {
return image.InspectResponse{ return image.InspectResponse{
ID: id, ID: id,
RepoTags: []string{ RepoTags: []string{
"someRepo:someTag", "someRepo:someTag",
imageReference, imageReference,
}, },
Size: size, Size: size,
Created: created,
} }
} }

View File

@ -111,7 +111,7 @@ func testContainer(service string, id string, oneOff bool) container.Summary {
ID: id, ID: id,
Names: []string{name}, Names: []string{name},
Labels: containerLabels(service, oneOff), Labels: containerLabels(service, oneOff),
State: container.StateExited, State: ContainerExited,
} }
} }

View File

@ -18,10 +18,12 @@ package compose
import ( import (
"context" "context"
"errors"
"io" "io"
"time"
"github.com/containerd/errdefs"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -61,8 +63,9 @@ func (s *composeService) Logs(
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for _, ctr := range containers { for _, ctr := range containers {
eg.Go(func() error { eg.Go(func() error {
err := s.logContainer(ctx, consumer, ctr, options) err := s.logContainers(ctx, consumer, ctr, options)
if errdefs.IsNotImplemented(err) { var notImplErr errdefs.ErrNotImplemented
if errors.As(err, &notImplErr) {
logrus.Warnf("Can't retrieve logs for %q: %s", getCanonicalContainerName(ctr), err.Error()) logrus.Warnf("Can't retrieve logs for %q: %s", getCanonicalContainerName(ctr), err.Error())
return nil return nil
} }
@ -71,58 +74,70 @@ func (s *composeService) Logs(
} }
if options.Follow { if options.Follow {
containers = containers.filter(isRunning())
printer := newLogPrinter(consumer) printer := newLogPrinter(consumer)
eg.Go(func() error {
_, err := printer.Run(api.CascadeIgnore, "", nil)
return err
})
monitor := newMonitor(s.apiClient(), projectName) for _, c := range containers {
if len(options.Services) > 0 { printer.HandleEvent(api.ContainerEvent{
monitor.withServices(options.Services) Type: api.ContainerEventAttach,
} else if options.Project != nil { Container: getContainerNameWithoutProject(c),
monitor.withServices(options.Project.ServiceNames()) ID: c.ID,
Service: c.Labels[api.ServiceLabel],
})
} }
monitor.withListener(printer.HandleEvent)
monitor.withListener(func(event api.ContainerEvent) {
if event.Type == api.ContainerEventStarted {
eg.Go(func() error {
ctr, err := s.apiClient().ContainerInspect(ctx, event.ID)
if err != nil {
return err
}
err = s.doLogContainer(ctx, consumer, event.Source, ctr, api.LogOptions{ eg.Go(func() error {
err := s.watchContainers(ctx, projectName, options.Services, nil, printer.HandleEvent, containers, func(c container.Summary, t time.Time) error {
printer.HandleEvent(api.ContainerEvent{
Type: api.ContainerEventAttach,
Container: getContainerNameWithoutProject(c),
ID: c.ID,
Service: c.Labels[api.ServiceLabel],
})
eg.Go(func() error {
err := s.logContainers(ctx, consumer, c, api.LogOptions{
Follow: options.Follow, Follow: options.Follow,
Since: ctr.State.StartedAt, Since: t.Format(time.RFC3339Nano),
Until: options.Until, Until: options.Until,
Tail: options.Tail, Tail: options.Tail,
Timestamps: options.Timestamps, Timestamps: options.Timestamps,
}) })
if errdefs.IsNotImplemented(err) { var notImplErr errdefs.ErrNotImplemented
if errors.As(err, &notImplErr) {
// ignore // ignore
return nil return nil
} }
return err return err
}) })
} return nil
}) }, func(c container.Summary, t time.Time) error {
eg.Go(func() error { printer.HandleEvent(api.ContainerEvent{
// pass ctx so monitor will immediately stop on SIGINT Type: api.ContainerEventAttach,
return monitor.Start(ctx) Container: "", // actual name will be set by start event
ID: c.ID,
Service: c.Labels[api.ServiceLabel],
})
return nil
})
printer.Stop()
return err
}) })
} }
return eg.Wait() return eg.Wait()
} }
func (s *composeService) logContainer(ctx context.Context, consumer api.LogConsumer, c container.Summary, options api.LogOptions) error { func (s *composeService) logContainers(ctx context.Context, consumer api.LogConsumer, c container.Summary, options api.LogOptions) error {
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID) cnt, err := s.apiClient().ContainerInspect(ctx, c.ID)
if err != nil { if err != nil {
return err return err
} }
name := getContainerNameWithoutProject(c)
return s.doLogContainer(ctx, consumer, name, ctr, options)
}
func (s *composeService) doLogContainer(ctx context.Context, consumer api.LogConsumer, name string, ctr container.InspectResponse, options api.LogOptions) error { r, err := s.apiClient().ContainerLogs(ctx, cnt.ID, container.LogsOptions{
r, err := s.apiClient().ContainerLogs(ctx, ctr.ID, container.LogsOptions{
ShowStdout: true, ShowStdout: true,
ShowStderr: true, ShowStderr: true,
Follow: options.Follow, Follow: options.Follow,
@ -136,10 +151,11 @@ func (s *composeService) doLogContainer(ctx context.Context, consumer api.LogCon
} }
defer r.Close() //nolint:errcheck defer r.Close() //nolint:errcheck
name := getContainerNameWithoutProject(c)
w := utils.GetWriter(func(line string) { w := utils.GetWriter(func(line string) {
consumer.Log(name, line) consumer.Log(name, line)
}) })
if ctr.Config.Tty { if cnt.Config.Tty {
_, err = io.Copy(w, r) _, err = io.Copy(w, r)
} else { } else {
_, err = stdcopy.StdCopy(w, w, r) _, err = stdcopy.StdCopy(w, w, r)

View File

@ -189,6 +189,8 @@ func (l *testLogConsumer) Err(containerName, message string) {
func (l *testLogConsumer) Status(containerName, msg string) {} func (l *testLogConsumer) Status(containerName, msg string) {}
func (l *testLogConsumer) Register(containerName string) {}
func (l *testLogConsumer) LogsForContainer(containerName string) []string { func (l *testLogConsumer) LogsForContainer(containerName string) []string {
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()

View File

@ -1,265 +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 compose
import (
"bufio"
"context"
"encoding/json"
"fmt"
"os/exec"
"slices"
"strconv"
"strings"
"github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli-plugins/manager"
"github.com/docker/compose/v2/pkg/progress"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
func (s *composeService) ensureModels(ctx context.Context, project *types.Project, quietPull bool) error {
if len(project.Models) == 0 {
return nil
}
api, err := s.newModelAPI(project)
if err != nil {
return err
}
defer api.Close()
availableModels, err := api.ListModels(ctx)
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
return api.SetModelVariables(ctx, project)
})
w := progress.ContextWriter(ctx)
for name, config := range project.Models {
if config.Name == "" {
config.Name = name
}
eg.Go(func() error {
if !slices.Contains(availableModels, config.Model) {
err = api.PullModel(ctx, config, quietPull, w)
if err != nil {
return err
}
}
return api.ConfigureModel(ctx, config, w)
})
}
return eg.Wait()
}
type modelAPI struct {
path string
env []string
prepare func(ctx context.Context, cmd *exec.Cmd) error
cleanup func()
}
func (s *composeService) newModelAPI(project *types.Project) (*modelAPI, error) {
dockerModel, err := manager.GetPlugin("model", s.dockerCli, &cobra.Command{})
if err != nil {
if errdefs.IsNotFound(err) {
return nil, fmt.Errorf("'models' support requires Docker Model plugin")
}
return nil, err
}
endpoint, cleanup, err := s.propagateDockerEndpoint()
if err != nil {
return nil, err
}
return &modelAPI{
path: dockerModel.Path,
prepare: func(ctx context.Context, cmd *exec.Cmd) error {
return s.prepareShellOut(ctx, project.Environment, cmd)
},
cleanup: cleanup,
env: append(project.Environment.Values(), endpoint...),
}, nil
}
func (m *modelAPI) Close() {
m.cleanup()
}
func (m *modelAPI) PullModel(ctx context.Context, model types.ModelConfig, quietPull bool, w progress.Writer) error {
w.Event(progress.Event{
ID: model.Name,
Status: progress.Working,
Text: "Pulling",
})
cmd := exec.CommandContext(ctx, m.path, "pull", model.Model)
err := m.prepare(ctx, cmd)
if err != nil {
return err
}
stream, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
msg := scanner.Text()
if msg == "" {
continue
}
if !quietPull {
w.Event(progress.Event{
ID: model.Name,
Status: progress.Working,
Text: "Pulling",
StatusText: msg,
})
}
}
err = cmd.Wait()
if err != nil {
w.Event(progress.ErrorMessageEvent(model.Name, err.Error()))
}
w.Event(progress.Event{
ID: model.Name,
Status: progress.Working,
Text: "Pulled",
})
return err
}
func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, w progress.Writer) error {
w.Event(progress.Event{
ID: config.Name,
Status: progress.Working,
Text: "Configuring",
})
// configure [--context-size=<n>] MODEL [-- <runtime-flags...>]
args := []string{"configure"}
if config.ContextSize > 0 {
args = append(args, "--context-size", strconv.Itoa(config.ContextSize))
}
args = append(args, config.Model)
if len(config.RuntimeFlags) != 0 {
args = append(args, "--")
args = append(args, config.RuntimeFlags...)
}
cmd := exec.CommandContext(ctx, m.path, args...)
err := m.prepare(ctx, cmd)
if err != nil {
return err
}
return cmd.Run()
}
func (m *modelAPI) SetModelVariables(ctx context.Context, project *types.Project) error {
cmd := exec.CommandContext(ctx, m.path, "status", "--json")
err := m.prepare(ctx, cmd)
if err != nil {
return err
}
statusOut, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error checking docker-model status: %w", err)
}
type Status struct {
Endpoint string `json:"endpoint"`
}
var status Status
err = json.Unmarshal(statusOut, &status)
if err != nil {
return err
}
for _, service := range project.Services {
for ref, modelConfig := range service.Models {
model := project.Models[ref]
varPrefix := strings.ReplaceAll(strings.ToUpper(ref), "-", "_")
var variable string
if modelConfig != nil && modelConfig.ModelVariable != "" {
variable = modelConfig.ModelVariable
} else {
variable = varPrefix + "_MODEL"
}
service.Environment[variable] = &model.Model
if modelConfig != nil && modelConfig.EndpointVariable != "" {
variable = modelConfig.EndpointVariable
} else {
variable = varPrefix + "_URL"
}
service.Environment[variable] = &status.Endpoint
}
}
return nil
}
type Model struct {
Id string `json:"id"`
Tags []string `json:"tags"`
Created int `json:"created"`
Config struct {
Format string `json:"format"`
Quantization string `json:"quantization"`
Parameters string `json:"parameters"`
Architecture string `json:"architecture"`
Size string `json:"size"`
} `json:"config"`
}
func (m *modelAPI) ListModels(ctx context.Context) ([]string, error) {
cmd := exec.CommandContext(ctx, m.path, "ls", "--json")
err := m.prepare(ctx, cmd)
if err != nil {
return nil, err
}
output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error checking available models: %w", err)
}
type AvailableModel struct {
Id string `json:"id"`
Tags []string `json:"tags"`
Created int `json:"created"`
}
models := []AvailableModel{}
err = json.Unmarshal(output, &models)
if err != nil {
return nil, fmt.Errorf("error unmarshalling available models: %w", err)
}
var availableModels []string
for _, model := range models {
availableModels = append(availableModels, model.Tags...)
}
return availableModels, nil
}

View File

@ -1,215 +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 compose
import (
"context"
"strconv"
"github.com/containerd/errdefs"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/utils"
)
type monitor struct {
api client.APIClient
project string
// services tells us which service to consider and those we can ignore, maybe ran by a concurrent compose command
services map[string]bool
listeners []api.ContainerEventListener
}
func newMonitor(api client.APIClient, project string) *monitor {
return &monitor{
api: api,
project: project,
services: map[string]bool{},
}
}
func (c *monitor) withServices(services []string) {
for _, name := range services {
c.services[name] = true
}
}
// Start runs monitor to detect application events and return after termination
//
//nolint:gocyclo
func (c *monitor) Start(ctx context.Context) error {
// collect initial application container
initialState, err := c.api.ContainerList(ctx, container.ListOptions{
All: true,
Filters: filters.NewArgs(
projectFilter(c.project),
oneOffFilter(false),
hasConfigHashLabel(),
),
})
if err != nil {
return err
}
// containers is the set if container IDs the application is based on
containers := utils.Set[string]{}
for _, ctr := range initialState {
if len(c.services) == 0 || c.services[ctr.Labels[api.ServiceLabel]] {
containers.Add(ctr.ID)
}
}
restarting := utils.Set[string]{}
evtCh, errCh := c.api.Events(ctx, events.ListOptions{
Filters: filters.NewArgs(
filters.Arg("type", "container"),
projectFilter(c.project)),
})
for {
if len(containers) == 0 {
return nil
}
select {
case <-ctx.Done():
return nil
case err := <-errCh:
return err
case event := <-evtCh:
if len(c.services) > 0 && !c.services[event.Actor.Attributes[api.ServiceLabel]] {
continue
}
ctr, err := c.getContainerSummary(event)
if err != nil {
return err
}
switch event.Action {
case events.ActionCreate:
if len(c.services) == 0 || c.services[ctr.Labels[api.ServiceLabel]] {
containers.Add(ctr.ID)
}
evtType := api.ContainerEventCreated
if _, ok := ctr.Labels[api.ContainerReplaceLabel]; ok {
evtType = api.ContainerEventRecreated
}
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, evtType))
}
logrus.Debugf("container %s created", ctr.Name)
case events.ActionStart:
restarted := restarting.Has(ctr.ID)
if restarted {
logrus.Debugf("container %s restarted", ctr.Name)
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, api.ContainerEventStarted, func(e *api.ContainerEvent) {
e.Restarting = restarted
}))
}
} else {
logrus.Debugf("container %s started", ctr.Name)
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, api.ContainerEventStarted))
}
}
if len(c.services) == 0 || c.services[ctr.Labels[api.ServiceLabel]] {
containers.Add(ctr.ID)
}
case events.ActionRestart:
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, api.ContainerEventRestarted))
}
logrus.Debugf("container %s restarted", ctr.Name)
case events.ActionDie:
logrus.Debugf("container %s exited with code %d", ctr.Name, ctr.ExitCode)
inspect, err := c.api.ContainerInspect(ctx, event.Actor.ID)
if errdefs.IsNotFound(err) {
// Source is already removed
} else if err != nil {
return err
}
if inspect.State != nil && inspect.State.Restarting || inspect.State.Running {
// State.Restarting is set by engine when container is configured to restart on exit
// on ContainerRestart it doesn't (see https://github.com/moby/moby/issues/45538)
// container state still is reported as "running"
logrus.Debugf("container %s is restarting", ctr.Name)
restarting.Add(ctr.ID)
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, api.ContainerEventExited, func(e *api.ContainerEvent) {
e.Restarting = true
}))
}
} else {
for _, listener := range c.listeners {
listener(newContainerEvent(event.TimeNano, ctr, api.ContainerEventExited))
}
containers.Remove(ctr.ID)
}
}
}
}
}
func newContainerEvent(timeNano int64, ctr *api.ContainerSummary, eventType int, opts ...func(e *api.ContainerEvent)) api.ContainerEvent {
name := ctr.Name
defaultName := getDefaultContainerName(ctr.Project, ctr.Labels[api.ServiceLabel], ctr.Labels[api.ContainerNumberLabel])
if name == defaultName {
// remove project- prefix
name = name[len(ctr.Project)+1:]
}
event := api.ContainerEvent{
Type: eventType,
Container: ctr,
Time: timeNano,
Source: name,
ID: ctr.ID,
Service: ctr.Service,
ExitCode: ctr.ExitCode,
}
for _, opt := range opts {
opt(&event)
}
return event
}
func (c *monitor) getContainerSummary(event events.Message) (*api.ContainerSummary, error) {
ctr := &api.ContainerSummary{
ID: event.Actor.ID,
Name: event.Actor.Attributes["name"],
Project: c.project,
Service: event.Actor.Attributes[api.ServiceLabel],
Labels: event.Actor.Attributes, // More than just labels, but that'c the closest the API gives us
}
if ec, ok := event.Actor.Attributes["exitCode"]; ok {
exitCode, err := strconv.Atoi(ec)
if err != nil {
return nil, err
}
ctr.ExitCode = exitCode
}
return ctr, nil
}
func (c *monitor) withListener(listener api.ContainerEventListener) {
c.listeners = append(c.listeners, listener)
}

View File

@ -27,15 +27,16 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs"
"github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/manager"
"github.com/docker/cli/cli-plugins/socket"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
) )
type JsonMessage struct { type JsonMessage struct {
@ -51,8 +52,6 @@ const (
providerMetadataDirectory = "compose/providers" providerMetadataDirectory = "compose/providers"
) )
var mux sync.Mutex
func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error { func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error {
provider := *service.Provider provider := *service.Provider
@ -71,8 +70,6 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project,
return err return err
} }
mux.Lock()
defer mux.Unlock()
for name, s := range project.Services { for name, s := range project.Services {
if _, ok := s.DependsOn[service.Name]; ok { if _, ok := s.DependsOn[service.Name]; ok {
prefix := strings.ToUpper(service.Name) + "_" prefix := strings.ToUpper(service.Name) + "_"
@ -164,14 +161,14 @@ func (s *composeService) getPluginBinaryPath(provider string) (path string, err
if err == nil { if err == nil {
path = plugin.Path path = plugin.Path
} }
if errdefs.IsNotFound(err) { if manager.IsNotFound(err) {
path, err = exec.LookPath(executable(provider)) path, err = exec.LookPath(executable(provider))
} }
return path, err return path, err
} }
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, service types.ServiceConfig, path, command string) (*exec.Cmd, error) { func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, service types.ServiceConfig, path, command string) (*exec.Cmd, error) {
cmdOptionsMetadata := s.getPluginMetadata(path, service.Provider.Type, project) cmdOptionsMetadata := s.getPluginMetadata(path, service.Provider.Type)
var currentCommandMetadata CommandMetadata var currentCommandMetadata CommandMetadata
switch command { switch command {
case "up": case "up":
@ -179,9 +176,8 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types.
case "down": case "down":
currentCommandMetadata = cmdOptionsMetadata.Down currentCommandMetadata = cmdOptionsMetadata.Down
} }
commandMetadataIsEmpty := len(currentCommandMetadata.Parameters) == 0
provider := *service.Provider provider := *service.Provider
commandMetadataIsEmpty := cmdOptionsMetadata.IsEmpty()
if err := currentCommandMetadata.CheckRequiredParameters(provider); !commandMetadataIsEmpty && err != nil { if err := currentCommandMetadata.CheckRequiredParameters(provider); !commandMetadataIsEmpty && err != nil {
return nil, err return nil, err
} }
@ -197,21 +193,35 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types.
args = append(args, service.Name) args = append(args, service.Name)
cmd := exec.CommandContext(ctx, path, args...) cmd := exec.CommandContext(ctx, path, args...)
// exec provider command with same environment Compose is running
err := s.prepareShellOut(ctx, project.Environment, cmd) env := types.NewMapping(os.Environ())
if err != nil { // but remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone
return nil, err delete(env, manager.ReexecEnvvar)
// and add the explicit environment variables set for service
for key, val := range service.Environment.RemoveEmpty().ToMapping() {
env[key] = val
} }
cmd.Env = env.Values()
// Use docker/cli mechanism to propagate termination signal to child process
server, err := socket.NewPluginServer(nil)
if err == nil {
defer server.Close() //nolint:errcheck
cmd.Cancel = server.Close
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
}
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
carrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, &carrier)
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
return cmd, nil return cmd, nil
} }
func (s *composeService) getPluginMetadata(path, command string, project *types.Project) ProviderMetadata { func (s *composeService) getPluginMetadata(path, command string) ProviderMetadata {
cmd := exec.Command(path, "compose", "metadata") cmd := exec.Command(path, "compose", "metadata")
err := s.prepareShellOut(context.Background(), project.Environment, cmd)
if err != nil {
logrus.Debugf("failed to prepare plugin metadata command: %v", err)
return ProviderMetadata{}
}
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
cmd.Stdout = stdout cmd.Stdout = stdout
@ -246,10 +256,6 @@ type ProviderMetadata struct {
Down CommandMetadata `json:"down"` Down CommandMetadata `json:"down"`
} }
func (p ProviderMetadata) IsEmpty() bool {
return p.Description == "" && p.Up.Parameters == nil && p.Down.Parameters == nil
}
type CommandMetadata struct { type CommandMetadata struct {
Parameters []ParameterMetadata `json:"parameters"` Parameters []ParameterMetadata `json:"parameters"`
} }

View File

@ -18,6 +18,7 @@ package compose
import ( import (
"fmt" "fmt"
"sync"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
) )
@ -25,33 +26,137 @@ import (
// logPrinter watch application containers and collect their logs // logPrinter watch application containers and collect their logs
type logPrinter interface { type logPrinter interface {
HandleEvent(event api.ContainerEvent) HandleEvent(event api.ContainerEvent)
Run(cascade api.Cascade, exitCodeFrom string, stopFn func() error) (int, error)
Cancel()
Stop()
} }
type printer struct { type printer struct {
queue chan api.ContainerEvent
consumer api.LogConsumer consumer api.LogConsumer
stopCh chan struct{} // stopCh is a signal channel for producers to stop sending events to the queue
stop sync.Once
} }
// newLogPrinter builds a LogPrinter passing containers logs to LogConsumer // newLogPrinter builds a LogPrinter passing containers logs to LogConsumer
func newLogPrinter(consumer api.LogConsumer) logPrinter { func newLogPrinter(consumer api.LogConsumer) logPrinter {
printer := printer{ printer := printer{
consumer: consumer, consumer: consumer,
queue: make(chan api.ContainerEvent),
stopCh: make(chan struct{}),
stop: sync.Once{},
} }
return &printer return &printer
} }
func (p *printer) HandleEvent(event api.ContainerEvent) { func (p *printer) Cancel() {
switch event.Type { // note: HandleEvent is used to ensure this doesn't deadlock
case api.ContainerEventExited: p.HandleEvent(api.ContainerEvent{Type: api.UserCancel})
if event.Restarting { }
p.consumer.Status(event.Source, fmt.Sprintf("exited with code %d (restarting)", event.ExitCode))
} else { func (p *printer) Stop() {
p.consumer.Status(event.Source, fmt.Sprintf("exited with code %d", event.ExitCode)) p.stop.Do(func() {
close(p.stopCh)
for {
select {
case <-p.queue:
// purge the queue to free producers goroutines
// p.queue will be garbage collected
default:
return
}
}
})
}
func (p *printer) HandleEvent(event api.ContainerEvent) {
select {
case <-p.stopCh:
return
default:
p.queue <- event
}
}
//nolint:gocyclo
func (p *printer) Run(cascade api.Cascade, exitCodeFrom string, stopFn func() error) (int, error) {
var (
aborting bool
exitCode int
)
defer p.Stop()
// containers we are tracking. Use true when container is running, false after we receive a stop|die signal
containers := map[string]bool{}
for {
select {
case <-p.stopCh:
return exitCode, nil
case event := <-p.queue:
container, id := event.Container, event.ID
switch event.Type {
case api.UserCancel:
aborting = true
case api.ContainerEventAttach:
if attached, ok := containers[id]; ok && attached {
continue
}
containers[id] = true
p.consumer.Register(container)
case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
if !aborting && containers[id] {
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
if event.Type == api.ContainerEventRecreated {
p.consumer.Status(container, "has been recreated")
}
}
containers[id] = false
if !event.Restarting {
delete(containers, id)
}
if cascade == api.CascadeStop {
if !aborting {
aborting = true
err := stopFn()
if err != nil {
return 0, err
}
}
}
if event.Type == api.ContainerEventExit {
if cascade == api.CascadeFail && event.ExitCode != 0 {
exitCodeFrom = event.Service
if !aborting {
aborting = true
err := stopFn()
if err != nil {
return 0, err
}
}
}
if cascade == api.CascadeStop && exitCodeFrom == "" {
exitCodeFrom = event.Service
}
}
if exitCodeFrom == event.Service && (event.Type == api.ContainerEventExit || event.Type == api.ContainerEventStopped) {
// Container was interrupted or exited, let's capture exit code
exitCode = event.ExitCode
}
if len(containers) == 0 {
// Last container terminated, done
return exitCode, nil
}
case api.ContainerEventLog, api.HookEventLog:
if !aborting {
p.consumer.Log(container, event.Line)
}
case api.ContainerEventErr:
if !aborting {
p.consumer.Err(container, event.Line)
}
}
} }
case api.ContainerEventRecreated:
p.consumer.Status(event.Container.Labels[api.ContainerReplaceLabel], "has been recreated")
case api.ContainerEventLog, api.HookEventLog:
p.consumer.Log(event.Source, event.Line)
case api.ContainerEventErr:
p.consumer.Err(event.Source, event.Line)
} }
} }

View File

@ -21,7 +21,6 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/docker/docker/api/types/container"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
@ -43,13 +42,13 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
} }
summary := make([]api.ContainerSummary, len(containers)) summary := make([]api.ContainerSummary, len(containers))
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
for i, ctr := range containers { for i, container := range containers {
eg.Go(func() error { eg.Go(func() error {
publishers := make([]api.PortPublisher, len(ctr.Ports)) publishers := make([]api.PortPublisher, len(container.Ports))
sort.Slice(ctr.Ports, func(i, j int) bool { sort.Slice(container.Ports, func(i, j int) bool {
return ctr.Ports[i].PrivatePort < ctr.Ports[j].PrivatePort return container.Ports[i].PrivatePort < container.Ports[j].PrivatePort
}) })
for i, p := range ctr.Ports { for i, p := range container.Ports {
publishers[i] = api.PortPublisher{ publishers[i] = api.PortPublisher{
URL: p.IP, URL: p.IP,
TargetPort: int(p.PrivatePort), TargetPort: int(p.PrivatePort),
@ -58,22 +57,22 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
} }
} }
inspect, err := s.apiClient().ContainerInspect(ctx, ctr.ID) inspect, err := s.apiClient().ContainerInspect(ctx, container.ID)
if err != nil { if err != nil {
return err return err
} }
var ( var (
health container.HealthStatus health string
exitCode int exitCode int
) )
if inspect.State != nil { if inspect.State != nil {
switch inspect.State.Status { switch inspect.State.Status {
case container.StateRunning: case "running":
if inspect.State.Health != nil { if inspect.State.Health != nil {
health = inspect.State.Health.Status health = inspect.State.Health.Status
} }
case container.StateExited, container.StateDead: case "exited", "dead":
exitCode = inspect.State.ExitCode exitCode = inspect.State.ExitCode
} }
} }
@ -82,7 +81,7 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
local int local int
mounts []string mounts []string
) )
for _, m := range ctr.Mounts { for _, m := range container.Mounts {
name := m.Name name := m.Name
if name == "" { if name == "" {
name = m.Source name = m.Source
@ -94,26 +93,26 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
} }
var networks []string var networks []string
if ctr.NetworkSettings != nil { if container.NetworkSettings != nil {
for k := range ctr.NetworkSettings.Networks { for k := range container.NetworkSettings.Networks {
networks = append(networks, k) networks = append(networks, k)
} }
} }
summary[i] = api.ContainerSummary{ summary[i] = api.ContainerSummary{
ID: ctr.ID, ID: container.ID,
Name: getCanonicalContainerName(ctr), Name: getCanonicalContainerName(container),
Names: ctr.Names, Names: container.Names,
Image: ctr.Image, Image: container.Image,
Project: ctr.Labels[api.ProjectLabel], Project: container.Labels[api.ProjectLabel],
Service: ctr.Labels[api.ServiceLabel], Service: container.Labels[api.ServiceLabel],
Command: ctr.Command, Command: container.Command,
State: ctr.State, State: container.State,
Status: ctr.Status, Status: container.Status,
Created: ctr.Created, Created: container.Created,
Labels: ctr.Labels, Labels: container.Labels,
SizeRw: ctr.SizeRw, SizeRw: container.SizeRw,
SizeRootFs: ctr.SizeRootFs, SizeRootFs: container.SizeRootFs,
Mounts: mounts, Mounts: mounts,
LocalVolumes: local, LocalVolumes: local,
Networks: networks, Networks: networks,

View File

@ -22,11 +22,11 @@ import (
"testing" "testing"
containerType "github.com/docker/docker/api/types/container" containerType "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
compose "github.com/docker/compose/v2/pkg/api" compose "github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/filters"
) )
func TestPs(t *testing.T) { func TestPs(t *testing.T) {
@ -42,10 +42,10 @@ func TestPs(t *testing.T) {
args := filters.NewArgs(projectFilter(strings.ToLower(testProject)), hasConfigHashLabel()) args := filters.NewArgs(projectFilter(strings.ToLower(testProject)), hasConfigHashLabel())
args.Add("label", "com.docker.compose.oneoff=False") args.Add("label", "com.docker.compose.oneoff=False")
listOpts := containerType.ListOptions{Filters: args, All: false} listOpts := containerType.ListOptions{Filters: args, All: false}
c1, inspect1 := containerDetails("service1", "123", containerType.StateRunning, containerType.Healthy, 0) c1, inspect1 := containerDetails("service1", "123", "running", "healthy", 0)
c2, inspect2 := containerDetails("service1", "456", containerType.StateRunning, "", 0) c2, inspect2 := containerDetails("service1", "456", "running", "", 0)
c2.Ports = []containerType.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}} c2.Ports = []containerType.Port{{PublicPort: 80, PrivatePort: 90, IP: "localhost"}}
c3, inspect3 := containerDetails("service2", "789", containerType.StateExited, "", 130) c3, inspect3 := containerDetails("service2", "789", "exited", "", 130)
api.EXPECT().ContainerList(ctx, listOpts).Return([]containerType.Summary{c1, c2, c3}, nil) api.EXPECT().ContainerList(ctx, listOpts).Return([]containerType.Summary{c1, c2, c3}, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "123").Return(inspect1, nil)
api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil) api.EXPECT().ContainerInspect(anyCancellableContext(), "456").Return(inspect2, nil)
@ -56,9 +56,7 @@ func TestPs(t *testing.T) {
expected := []compose.ContainerSummary{ expected := []compose.ContainerSummary{
{ {
ID: "123", Name: "123", Names: []string{"/123"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1", ID: "123", Name: "123", Names: []string{"/123"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
State: containerType.StateRunning, State: "running", Health: "healthy", Publishers: []compose.PortPublisher{},
Health: containerType.Healthy,
Publishers: []compose.PortPublisher{},
Labels: map[string]string{ Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject), compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml", compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
@ -68,8 +66,7 @@ func TestPs(t *testing.T) {
}, },
{ {
ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1", ID: "456", Name: "456", Names: []string{"/456"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service1",
State: containerType.StateRunning, State: "running", Health: "",
Health: "",
Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}}, Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90, PublishedPort: 80}},
Labels: map[string]string{ Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject), compose.ProjectLabel: strings.ToLower(testProject),
@ -80,10 +77,7 @@ func TestPs(t *testing.T) {
}, },
{ {
ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2", ID: "789", Name: "789", Names: []string{"/789"}, Image: "foo", Project: strings.ToLower(testProject), Service: "service2",
State: containerType.StateExited, State: "exited", Health: "", ExitCode: 130, Publishers: []compose.PortPublisher{},
Health: "",
ExitCode: 130,
Publishers: []compose.PortPublisher{},
Labels: map[string]string{ Labels: map[string]string{
compose.ProjectLabel: strings.ToLower(testProject), compose.ProjectLabel: strings.ToLower(testProject),
compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml", compose.ConfigFilesLabel: "/src/pkg/compose/testdata/compose.yaml",
@ -96,8 +90,8 @@ func TestPs(t *testing.T) {
assert.DeepEqual(t, containers, expected) assert.DeepEqual(t, containers, expected)
} }
func containerDetails(service string, id string, status containerType.ContainerState, health containerType.HealthStatus, exitCode int) (containerType.Summary, containerType.InspectResponse) { func containerDetails(service string, id string, status string, health string, exitCode int) (containerType.Summary, containerType.InspectResponse) {
ctr := containerType.Summary{ container := containerType.Summary{
ID: id, ID: id,
Names: []string{"/" + id}, Names: []string{"/" + id},
Image: "foo", Image: "foo",
@ -113,5 +107,5 @@ func containerDetails(service string, id string, status containerType.ContainerS
}, },
}, },
} }
return ctr, inspect return container, inspect
} }

View File

@ -236,24 +236,12 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp
if ok, err := s.checkOnlyBuildSection(project); !ok || err != nil { if ok, err := s.checkOnlyBuildSection(project); !ok || err != nil {
return false, err return false, err
} }
if ok, err := s.checkForBindMount(project); !ok || err != nil {
return false, err
}
if options.AssumeYes { if options.AssumeYes {
return true, nil return true, nil
} }
bindMounts := s.checkForBindMount(project)
if len(bindMounts) > 0 {
fmt.Println("you are about to publish bind mounts declaration within your OCI artifact.\n" +
"only the bind mount declarations will be added to the OCI artifact (not content)\n" +
"please double check that you are not mounting potential user's sensitive directories or data")
for key, val := range bindMounts {
_, _ = fmt.Fprintln(s.dockerCli.Out(), key)
for _, v := range val {
_, _ = fmt.Fprintf(s.dockerCli.Out(), "%s\n", v.String())
}
}
if ok, err := acceptPublishBindMountDeclarations(s.dockerCli); err != nil || !ok {
return false, err
}
}
detectedSecrets, err := s.checkForSensitiveData(project) detectedSecrets, err := s.checkForSensitiveData(project)
if err != nil { if err != nil {
return false, err return false, err
@ -337,12 +325,6 @@ func acceptPublishSensitiveData(cli command.Cli) (bool, error) {
return confirm, err return confirm, err
} }
func acceptPublishBindMountDeclarations(cli command.Cli) (bool, error) {
msg := "Are you ok to publish these bind mount declarations? [y/N]: "
confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false)
return confirm, err
}
func envFileLayers(project *types.Project) []ocipush.Pushable { func envFileLayers(project *types.Project) []ocipush.Pushable {
var layers []ocipush.Pushable var layers []ocipush.Pushable
for _, service := range project.Services { for _, service := range project.Services {
@ -379,20 +361,15 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
return true, nil return true, nil
} }
func (s *composeService) checkForBindMount(project *types.Project) map[string][]types.ServiceVolumeConfig { func (s *composeService) checkForBindMount(project *types.Project) (bool, error) {
allFindings := map[string][]types.ServiceVolumeConfig{} for name, config := range project.Services {
for serviceName, config := range project.Services {
bindMounts := []types.ServiceVolumeConfig{}
for _, volume := range config.Volumes { for _, volume := range config.Volumes {
if volume.Type == types.VolumeTypeBind { if volume.Type == types.VolumeTypeBind {
bindMounts = append(bindMounts, volume) return false, fmt.Errorf("cannot publish compose file: service %q relies on bind-mount. You should use volumes", name)
} }
} }
if len(bindMounts) > 0 {
allFindings[serviceName] = bindMounts
}
} }
return allFindings return true, nil
} }
func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) { func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) {

View File

@ -34,10 +34,11 @@ import (
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/hashicorp/go-multierror"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/internal/registry"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
) )
@ -151,7 +152,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
if opts.IgnoreFailures { if opts.IgnoreFailures {
return nil return nil
} }
return errors.Join(pullErrors...) return multierror.Append(nil, pullErrors...).ErrorOrNil()
} }
func imageAlreadyPresent(serviceImage string, localImages map[string]api.ImageSummary) bool { func imageAlreadyPresent(serviceImage string, localImages map[string]api.ImageSummary) bool {
@ -280,7 +281,13 @@ func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiCl
} }
func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) { func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
authConfig, err := configFile.GetAuthConfig(registry.GetAuthConfigKey(ref)) repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return "", err
}
key := registry.GetAuthConfigKey(repoInfo.Index)
authConfig, err := configFile.GetAuthConfig(key)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -29,10 +29,11 @@ import (
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/docker/compose/v2/internal/registry"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
) )
@ -50,6 +51,14 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
eg.SetLimit(s.maxConcurrency) eg.SetLimit(s.maxConcurrency)
info, err := s.apiClient().Info(ctx)
if err != nil {
return err
}
if info.IndexServerAddress == "" {
info.IndexServerAddress = registry.IndexServer
}
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
for _, service := range project.Services { for _, service := range project.Services {
if service.Build == nil || service.Image == "" { if service.Build == nil || service.Image == "" {
@ -70,7 +79,7 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
for _, tag := range tags { for _, tag := range tags {
eg.Go(func() error { eg.Go(func() error {
err := s.pushServiceImage(ctx, tag, s.configFile(), w, options.Quiet) err := s.pushServiceImage(ctx, tag, info, s.configFile(), w, options.Quiet)
if err != nil { if err != nil {
if !options.IgnoreFailures { if !options.IgnoreFailures {
return err return err
@ -84,13 +93,22 @@ func (s *composeService) push(ctx context.Context, project *types.Project, optio
return eg.Wait() return eg.Wait()
} }
func (s *composeService) pushServiceImage(ctx context.Context, tag string, configFile driver.Auth, w progress.Writer, quietPush bool) error { func (s *composeService) pushServiceImage(ctx context.Context, tag string, info system.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
ref, err := reference.ParseNormalizedNamed(tag) ref, err := reference.ParseNormalizedNamed(tag)
if err != nil { if err != nil {
return err return err
} }
authConfig, err := configFile.GetAuthConfig(registry.GetAuthConfigKey(ref)) repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
key := repoInfo.Index.Name
if repoInfo.Index.Official {
key = info.IndexServerAddress
}
authConfig, err := configFile.GetAuthConfig(key)
if err != nil { if err != nil {
return err return err
} }

View File

@ -28,7 +28,6 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
cmd "github.com/docker/cli/cli/command/container" cmd "github.com/docker/cli/cli/command/container"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
) )
@ -59,19 +58,6 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
} }
func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) { func (s *composeService) prepareRun(ctx context.Context, project *types.Project, opts api.RunOptions) (string, error) {
// Temporary implementation of use_api_socket until we get actual support inside docker engine
project, err := s.useAPISocket(project)
if err != nil {
return "", err
}
err = progress.Run(ctx, func(ctx context.Context) error {
return s.startDependencies(ctx, project, opts)
}, s.stdinfo())
if err != nil {
return "", err
}
service, err := project.GetService(opts.Service) service, err := project.GetService(opts.Service)
if err != nil { if err != nil {
return "", err return "", err
@ -123,23 +109,11 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
return "", err return "", err
} }
err = s.ensureModels(ctx, project, opts.QuietPull)
if err != nil {
return "", err
}
created, err := s.createContainer(ctx, project, service, service.ContainerName, -1, createOpts) created, err := s.createContainer(ctx, project, service, service.ContainerName, -1, createOpts)
if err != nil { if err != nil {
return "", err return "", err
} }
return created.ID, nil
err = s.injectSecrets(ctx, project, service, created.ID)
if err != nil {
return created.ID, err
}
err = s.injectConfigs(ctx, project, service, created.ID)
return created.ID, err
} }
func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) { func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts api.RunOptions) {
@ -186,24 +160,3 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
service.Labels = service.Labels.Add(k, v) service.Labels = service.Labels.Add(k, v)
} }
} }
func (s *composeService) startDependencies(ctx context.Context, project *types.Project, options api.RunOptions) error {
project = project.WithServicesDisabled(options.Service)
err := s.Create(ctx, project, api.CreateOptions{
Build: options.Build,
IgnoreOrphans: options.IgnoreOrphans,
RemoveOrphans: options.RemoveOrphans,
QuietPull: options.QuietPull,
})
if err != nil {
return err
}
if len(project.Services) > 0 {
return s.Start(ctx, project.Name, api.StartOptions{
Project: project,
})
}
return nil
}

View File

@ -1,86 +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 compose
import (
"context"
"os"
"os/exec"
"path/filepath"
"github.com/compose-spec/compose-go/v2/types"
"github.com/docker/cli/cli-plugins/metadata"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/internal"
"github.com/docker/docker/client"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
// prepareShellOut prepare a shell-out command to be ran by Compose
func (s *composeService) prepareShellOut(gctx context.Context, env types.Mapping, cmd *exec.Cmd) error {
env = env.Clone()
// remove DOCKER_CLI_PLUGIN... variable so a docker-cli plugin will detect it run standalone
delete(env, metadata.ReexecEnvvar)
// 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(gctx, &carrier)
env.Merge(types.Mapping(carrier))
cmd.Env = env.Values()
return nil
}
// propagateDockerEndpoint produces DOCKER_* env vars for a child CLI plugin to target the same docker endpoint
// `cleanup` func MUST be called after child process completion to enforce removal of cert files
func (s *composeService) propagateDockerEndpoint() ([]string, func(), error) {
cleanup := func() {}
env := types.Mapping{}
env[command.EnvOverrideContext] = s.dockerCli.CurrentContext()
env["USER_AGENT"] = "compose/" + internal.Version
endpoint := s.dockerCli.DockerEndpoint()
env[client.EnvOverrideHost] = endpoint.Host
if endpoint.TLSData != nil {
certs, err := os.MkdirTemp("", "compose")
if err != nil {
return nil, cleanup, err
}
cleanup = func() {
_ = os.RemoveAll(certs)
}
env[client.EnvOverrideCertPath] = certs
if !endpoint.SkipTLSVerify {
env[client.EnvTLSVerify] = "1"
}
err = os.WriteFile(filepath.Join(certs, flags.DefaultKeyFile), endpoint.TLSData.Key, 0o600)
if err != nil {
return nil, cleanup, err
}
err = os.WriteFile(filepath.Join(certs, flags.DefaultCaFile), endpoint.TLSData.Cert, 0o600)
if err != nil {
return nil, cleanup, err
}
err = os.WriteFile(filepath.Join(certs, flags.DefaultCaFile), endpoint.TLSData.CA, 0o600)
if err != nil {
return nil, cleanup, err
}
}
return env.Values(), cleanup, nil
}

View File

@ -20,14 +20,19 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"strings" "strings"
"time"
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/docker/compose/v2/pkg/utils"
containerType "github.com/docker/docker/api/types/container" containerType "github.com/docker/docker/api/types/container"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"golang.org/x/sync/errgroup"
) )
func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error { func (s *composeService) Start(ctx context.Context, projectName string, options api.StartOptions) error {
@ -51,6 +56,60 @@ func (s *composeService) start(ctx context.Context, projectName string, options
} }
} }
// use an independent context tied to the errgroup for background attach operations
// the primary context is still used for other operations
// this means that once any attach operation fails, all other attaches are cancelled,
// but an attach failing won't interfere with the rest of the start
eg, attachCtx := errgroup.WithContext(ctx)
if listener != nil {
_, err := s.attach(attachCtx, project, listener, options.AttachTo)
if err != nil {
return err
}
eg.Go(func() error {
// it's possible to have a required service whose log output is not desired
// (i.e. it's not in the attach set), so watch everything and then filter
// calls to attach; this ensures that `watchContainers` blocks until all
// required containers have exited, even if their output is not being shown
attachTo := utils.NewSet[string](options.AttachTo...)
required := utils.NewSet[string](options.Services...)
toWatch := attachTo.Union(required).Elements()
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true, toWatch...)
if err != nil {
return err
}
// N.B. this uses the parent context (instead of attachCtx) so that the watch itself can
// continue even if one of the log streams fails
return s.watchContainers(ctx, project.Name, toWatch, required.Elements(), listener, containers,
func(ctr containerType.Summary, _ time.Time) error {
svc := ctr.Labels[api.ServiceLabel]
if attachTo.Has(svc) {
return s.attachContainer(attachCtx, ctr, listener)
}
// HACK: simulate an "attach" event
listener(api.ContainerEvent{
Type: api.ContainerEventAttach,
Container: getContainerNameWithoutProject(ctr),
ID: ctr.ID,
Service: svc,
})
return nil
}, func(ctr containerType.Summary, _ time.Time) error {
listener(api.ContainerEvent{
Type: api.ContainerEventAttach,
Container: "", // actual name will be set by start event
ID: ctr.ID,
Service: ctr.Labels[api.ServiceLabel],
})
return nil
})
})
}
var containers Containers var containers Containers
containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{ containers, err := s.apiClient().ContainerList(ctx, containerType.ListOptions{
Filters: filters.NewArgs( Filters: filters.NewArgs(
@ -98,7 +157,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
} }
} }
return nil return eg.Wait()
} }
// getDependencyCondition checks if service is depended on by other services // getDependencyCondition checks if service is depended on by other services
@ -114,3 +173,182 @@ func getDependencyCondition(service types.ServiceConfig, project *types.Project)
} }
return ServiceConditionRunningOrHealthy return ServiceConditionRunningOrHealthy
} }
type containerWatchFn func(ctr containerType.Summary, t time.Time) error
// watchContainers uses engine events to capture container start/die and notify ContainerEventListener
func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
projectName string, services, required []string,
listener api.ContainerEventListener, containers Containers, onStart, onRecreate containerWatchFn,
) error {
if len(containers) == 0 {
return nil
}
if len(required) == 0 {
required = services
}
unexpected := utils.NewSet[string](required...).Diff(utils.NewSet[string](services...))
if len(unexpected) != 0 {
return fmt.Errorf(`required service(s) "%s" not present in watched service(s) "%s"`,
strings.Join(unexpected.Elements(), ", "),
strings.Join(services, ", "))
}
// predicate to tell if a container we receive event for should be considered or ignored
ofInterest := func(c containerType.Summary) bool {
if len(services) > 0 {
// we only watch some services
return slices.Contains(services, c.Labels[api.ServiceLabel])
}
return true
}
// predicate to tell if a container we receive event for should be watched until termination
isRequired := func(c containerType.Summary) bool {
if len(services) > 0 && len(required) > 0 {
// we only watch some services
return slices.Contains(required, c.Labels[api.ServiceLabel])
}
return true
}
var (
expected = utils.NewSet[string]()
watched = map[string]int{}
replaced []string
)
for _, c := range containers {
if isRequired(c) {
expected.Add(c.ID)
}
watched[c.ID] = 0
}
ctx, stop := context.WithCancel(ctx)
err := s.Events(ctx, projectName, api.EventsOptions{
Services: services,
Consumer: func(event api.Event) error {
defer func() {
// after consuming each event, check to see if we're done
if len(expected) == 0 {
stop()
}
}()
inspected, err := s.apiClient().ContainerInspect(ctx, event.Container)
if err != nil {
if cerrdefs.IsNotFound(err) {
// it's possible to get "destroy" or "kill" events but not
// 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.Remove(event.Container)
return nil
}
return err
}
container := containerType.Summary{
ID: inspected.ID,
Names: []string{inspected.Name},
Labels: inspected.Config.Labels,
}
name := getContainerNameWithoutProject(container)
service := container.Labels[api.ServiceLabel]
switch event.Status {
case "stop":
if inspected.State.Running {
// on sync+restart action the container stops -> dies -> start -> restart
// we do not want to stop the current container, we want to restart it
return nil
}
if _, ok := watched[container.ID]; ok {
eType := api.ContainerEventStopped
if slices.Contains(replaced, container.ID) {
replaced = slices.DeleteFunc(replaced, func(e string) bool { return e == container.ID })
eType = api.ContainerEventRecreated
}
listener(api.ContainerEvent{
Type: eType,
Container: name,
ID: container.ID,
Service: service,
ExitCode: inspected.State.ExitCode,
})
}
delete(watched, container.ID)
expected.Remove(container.ID)
case "die":
restarted := watched[container.ID]
watched[container.ID] = restarted + 1
// Container terminated.
willRestart := inspected.State.Restarting
if inspected.State.Running {
// on sync+restart action inspected.State.Restarting is false,
// however the container is already running before it restarts
willRestart = true
}
eType := api.ContainerEventExit
if slices.Contains(replaced, container.ID) {
replaced = slices.DeleteFunc(replaced, func(e string) bool { return e == container.ID })
eType = api.ContainerEventRecreated
}
listener(api.ContainerEvent{
Type: eType,
Container: name,
ID: container.ID,
Service: service,
ExitCode: inspected.State.ExitCode,
Restarting: willRestart,
})
if !willRestart {
// we're done with this one
delete(watched, container.ID)
expected.Remove(container.ID)
}
case "start":
count, ok := watched[container.ID]
mustAttach := ok && count > 0 // Container restarted, need to re-attach
if !ok {
// A new container has just been added to service by scale
watched[container.ID] = 0
expected.Add(container.ID)
mustAttach = true
}
if mustAttach {
// Container restarted, need to re-attach
err := onStart(container, event.Timestamp)
if err != nil {
return err
}
}
case "create":
if id, ok := container.Labels[api.ContainerReplaceLabel]; ok {
replaced = append(replaced, id)
err = onRecreate(container, event.Timestamp)
if err != nil {
return err
}
if expected.Has(id) {
expected.Add(inspected.ID)
expected.Add(container.ID)
}
watched[container.ID] = 1
} else if ofInterest(container) {
watched[container.ID] = 1
if isRequired(container) {
expected.Add(container.ID)
}
}
}
return nil
},
})
if errors.Is(ctx.Err(), context.Canceled) {
return nil
}
return err
}

View File

@ -27,11 +27,11 @@ import (
func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error { func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
return progress.RunWithTitle(ctx, func(ctx context.Context) error { return progress.RunWithTitle(ctx, func(ctx context.Context) error {
return s.stop(ctx, strings.ToLower(projectName), options, nil) return s.stop(ctx, strings.ToLower(projectName), options)
}, s.stdinfo(), "Stopping") }, s.stdinfo(), "Stopping")
} }
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions, event api.ContainerEventListener) error { func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true) containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
if err != nil { if err != nil {
return err return err
@ -55,6 +55,6 @@ func (s *composeService) stop(ctx context.Context, projectName string, options a
return nil return nil
} }
serv := project.Services[service] serv := project.Services[service]
return s.stopContainers(ctx, w, &serv, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout, event) return s.stopContainers(ctx, w, &serv, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout)
}) })
} }

View File

@ -18,25 +18,22 @@ package compose
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"slices"
"sync"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/containerd/errdefs" cerrdefs "github.com/containerd/errdefs"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/compose/v2/cmd/formatter" "github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/eiannone/keyboard" "github.com/eiannone/keyboard"
"github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
) )
func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo
@ -62,20 +59,29 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
return err return err
} }
var eg multierror.Group
// if we get a second signal during shutdown, we kill the services // if we get a second signal during shutdown, we kill the services
// immediately, so the channel needs to have sufficient capacity or // immediately, so the channel needs to have sufficient capacity or
// we might miss a signal while setting up the second channel read // we might miss a signal while setting up the second channel read
// (this is also why signal.Notify is used vs signal.NotifyContext) // (this is also why signal.Notify is used vs signal.NotifyContext)
signalChan := make(chan os.Signal, 2) signalChan := make(chan os.Signal, 2)
defer close(signalChan)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(signalChan) defer signal.Stop(signalChan)
var isTerminated atomic.Bool var isTerminated atomic.Bool
printer := newLogPrinter(options.Start.Attach)
var ( var watcher *Watcher
logConsumer = options.Start.Attach if options.Start.Watch {
navigationMenu *formatter.LogKeyboard watcher, err = NewWatcher(project, options, s.watch)
kEvents <-chan keyboard.KeyEvent if err != nil {
) return err
}
}
var navigationMenu *formatter.LogKeyboard
var kEvents <-chan keyboard.KeyEvent
if options.Start.NavigationMenu { if options.Start.NavigationMenu {
kEvents, err = keyboard.GetKeys(100) kEvents, err = keyboard.GetKeys(100)
if err != nil { if err != nil {
@ -84,62 +90,31 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
} else { } else {
defer keyboard.Close() //nolint:errcheck defer keyboard.Close() //nolint:errcheck
isDockerDesktopActive := s.isDesktopIntegrationActive() isDockerDesktopActive := s.isDesktopIntegrationActive()
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive) tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, watcher != nil)
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan) navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher)
logConsumer = navigationMenu.Decorate(logConsumer)
}
}
watcher, err := NewWatcher(project, options, s.watch, logConsumer)
if err != nil && options.Start.Watch {
return err
}
if navigationMenu != nil && watcher != nil {
navigationMenu.EnableWatch(options.Start.Watch, watcher)
}
printer := newLogPrinter(logConsumer)
// global context to handle canceling goroutines
globalCtx, cancel := context.WithCancel(ctx)
defer cancel()
var (
eg errgroup.Group
mu sync.Mutex
errs []error
)
appendErr := func(err error) {
if err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
} }
} }
doneCh := make(chan bool)
eg.Go(func() error { eg.Go(func() error {
first := true first := true
gracefulTeardown := func() { gracefulTeardown := func() {
first = false printer.Cancel()
fmt.Println("Gracefully Stopping... press Ctrl+C again to force") _, _ = fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
eg.Go(func() error { eg.Go(func() error {
err := progress.RunWithLog(context.WithoutCancel(globalCtx), func(c context.Context) error { err := s.Stop(context.WithoutCancel(ctx), project.Name, api.StopOptions{
return s.stop(c, project.Name, api.StopOptions{ Services: options.Create.Services,
Services: options.Create.Services, Project: project,
Project: project, })
}, printer.HandleEvent) isTerminated.Store(true)
}, s.stdinfo(), logConsumer) return err
appendErr(err)
return nil
}) })
isTerminated.Store(true) first = false
} }
for { for {
select { select {
case <-globalCtx.Done(): case <-doneCh:
if watcher != nil { if watcher != nil {
return watcher.Stop() return watcher.Stop()
} }
@ -150,145 +125,64 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
} }
case <-signalChan: case <-signalChan:
if first { if first {
_ = keyboard.Close() keyboard.Close() //nolint:errcheck
gracefulTeardown() gracefulTeardown()
break break
} }
eg.Go(func() error { eg.Go(func() error {
err := s.kill(context.WithoutCancel(globalCtx), project.Name, api.KillOptions{ err := s.kill(context.WithoutCancel(ctx), project.Name, api.KillOptions{
Services: options.Create.Services, Services: options.Create.Services,
Project: project, Project: project,
All: true, All: true,
}) })
// Ignore errors indicating that some of the containers were already stopped or removed. // Ignore errors indicating that some of the containers were already stopped or removed.
if errdefs.IsNotFound(err) || errdefs.IsConflict(err) { if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) {
return nil return nil
} }
appendErr(err) return err
return nil
}) })
return nil return nil
case event := <-kEvents: case event := <-kEvents:
navigationMenu.HandleKeyEvents(globalCtx, event, project, options) navigationMenu.HandleKeyEvents(ctx, event, project, options)
} }
} }
}) })
var exitCode int
eg.Go(func() error {
code, err := printer.Run(options.Start.OnExit, options.Start.ExitCodeFrom, func() error {
_, _ = fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
return progress.Run(ctx, func(ctx context.Context) error {
return s.Stop(ctx, project.Name, api.StopOptions{
Services: options.Create.Services,
Project: project,
})
}, s.stdinfo())
})
exitCode = code
return err
})
if options.Start.Watch && watcher != nil { if options.Start.Watch && watcher != nil {
if err := watcher.Start(globalCtx); err != nil { err = watcher.Start(ctx)
// cancel the global context to terminate background goroutines if err != nil {
cancel()
_ = eg.Wait()
return err return err
} }
} }
monitor := newMonitor(s.apiClient(), project.Name)
if len(options.Start.Services) > 0 {
monitor.withServices(options.Start.Services)
} else {
// Start.AttachTo have been already curated with only the services to monitor
monitor.withServices(options.Start.AttachTo)
}
monitor.withListener(printer.HandleEvent)
var exitCode int
if options.Start.OnExit != api.CascadeIgnore {
once := true
// detect first container to exit to trigger application shutdown
monitor.withListener(func(event api.ContainerEvent) {
if once && event.Type == api.ContainerEventExited {
if options.Start.OnExit == api.CascadeFail && event.ExitCode == 0 {
return
}
once = false
exitCode = event.ExitCode
_, _ = fmt.Fprintln(s.stdinfo(), progress.ErrorColor("Aborting on container exit..."))
eg.Go(func() error {
err := progress.RunWithLog(context.WithoutCancel(globalCtx), func(c context.Context) error {
return s.stop(c, project.Name, api.StopOptions{
Services: options.Create.Services,
Project: project,
}, printer.HandleEvent)
}, s.stdinfo(), logConsumer)
appendErr(err)
return nil
})
}
})
}
if options.Start.ExitCodeFrom != "" {
once := true
// capture exit code from first container to exit with selected service
monitor.withListener(func(event api.ContainerEvent) {
if once && event.Type == api.ContainerEventExited && event.Service == options.Start.ExitCodeFrom {
exitCode = event.ExitCode
once = false
}
})
}
containers, err := s.attach(globalCtx, project, printer.HandleEvent, options.Start.AttachTo)
if err != nil {
cancel()
_ = eg.Wait()
return err
}
attached := make([]string, len(containers))
for i, ctr := range containers {
attached[i] = ctr.ID
}
monitor.withListener(func(event api.ContainerEvent) {
if event.Type != api.ContainerEventStarted {
return
}
if slices.Contains(attached, event.ID) && !event.Restarting {
return
}
eg.Go(func() error {
ctr, err := s.apiClient().ContainerInspect(globalCtx, event.ID)
if err != nil {
appendErr(err)
return nil
}
err = s.doLogContainer(globalCtx, options.Start.Attach, event.Source, ctr, api.LogOptions{
Follow: true,
Since: ctr.State.StartedAt,
})
if errdefs.IsNotImplemented(err) {
// container may be configured with logging_driver: none
// as container already started, we might miss the very first logs. But still better than none
err := s.doAttachContainer(globalCtx, event.Service, event.ID, event.Source, printer.HandleEvent)
appendErr(err)
return nil
}
appendErr(err)
return nil
})
})
eg.Go(func() error {
err := monitor.Start(globalCtx)
// cancel the global context to terminate signal-handler goroutines
cancel()
appendErr(err)
return nil
})
// We use the parent context without cancellation as we manage sigterm to stop the stack // We use the parent context without cancellation as we manage sigterm to stop the stack
err = s.start(context.WithoutCancel(ctx), project.Name, options.Start, printer.HandleEvent) err = s.start(context.WithoutCancel(ctx), project.Name, options.Start, printer.HandleEvent)
if err != nil && !isTerminated.Load() { // Ignore error if the process is terminated if err != nil && !isTerminated.Load() { // Ignore error if the process is terminated
cancel()
_ = eg.Wait()
return err return err
} }
_ = eg.Wait() // Signal for the signal-handler goroutines to stop
err = errors.Join(errs...) close(doneCh)
printer.Stop()
err = eg.Wait().ErrorOrNil()
if exitCode != 0 { if exitCode != 0 {
errMsg := "" errMsg := ""
if err != nil { if err != nil {

View File

@ -1,82 +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 compose
import (
"context"
"slices"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
)
func (s *composeService) Volumes(ctx context.Context, project string, options api.VolumesOptions) ([]api.VolumesSummary, error) {
allContainers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
Filters: filters.NewArgs(projectFilter(project)),
})
if err != nil {
return nil, err
}
var containers []container.Summary
if len(options.Services) > 0 {
// filter service containers
for _, c := range allContainers {
if slices.Contains(options.Services, c.Labels[api.ServiceLabel]) {
containers = append(containers, c)
}
}
} else {
containers = allContainers
}
volumesResponse, err := s.apiClient().VolumeList(ctx, volume.ListOptions{
Filters: filters.NewArgs(projectFilter(project)),
})
if err != nil {
return nil, err
}
projectVolumes := volumesResponse.Volumes
if len(options.Services) == 0 {
return projectVolumes, nil
}
var volumes []api.VolumesSummary
// create a name lookup of volumes used by containers
serviceVolumes := make(map[string]bool)
for _, container := range containers {
for _, mount := range container.Mounts {
serviceVolumes[mount.Name] = true
}
}
// append if volumes in this project are in serviceVolumes
for _, v := range projectVolumes {
if serviceVolumes[v.Name] {
volumes = append(volumes, v)
}
}
return volumes, nil
}

View File

@ -1,87 +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 compose
import (
"context"
"testing"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"go.uber.org/mock/gomock"
"gotest.tools/v3/assert"
)
func TestVolumes(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockApi, mockCli := prepareMocks(mockCtrl)
tested := composeService{
dockerCli: mockCli,
}
// Create test volumes
vol1 := &volume.Volume{Name: testProject + "_vol1"}
vol2 := &volume.Volume{Name: testProject + "_vol2"}
vol3 := &volume.Volume{Name: testProject + "_vol3"}
// Create test containers with volume mounts
c1 := container.Summary{
Labels: map[string]string{api.ServiceLabel: "service1"},
Mounts: []container.MountPoint{
{Name: testProject + "_vol1"},
{Name: testProject + "_vol2"},
},
}
c2 := container.Summary{
Labels: map[string]string{api.ServiceLabel: "service2"},
Mounts: []container.MountPoint{
{Name: testProject + "_vol3"},
},
}
ctx := context.Background()
args := filters.NewArgs(projectFilter(testProject))
listOpts := container.ListOptions{Filters: args}
volumeListArgs := filters.NewArgs(projectFilter(testProject))
volumeListOpts := volume.ListOptions{Filters: volumeListArgs}
volumeReturn := volume.ListResponse{
Volumes: []*volume.Volume{vol1, vol2, vol3},
}
containerReturn := []container.Summary{c1, c2}
// Mock API calls
mockApi.EXPECT().ContainerList(ctx, listOpts).Times(2).Return(containerReturn, nil)
mockApi.EXPECT().VolumeList(ctx, volumeListOpts).Times(2).Return(volumeReturn, nil)
// Test without service filter - should return all project volumes
volumeOptions := api.VolumesOptions{}
volumes, err := tested.Volumes(ctx, testProject, volumeOptions)
expected := []api.VolumesSummary{vol1, vol2, vol3}
assert.NilError(t, err)
assert.DeepEqual(t, volumes, expected)
// Test with service filter - should only return volumes used by service1
volumeOptions = api.VolumesOptions{Services: []string{"service1"}}
volumes, err = tested.Volumes(ctx, testProject, volumeOptions)
expected = []api.VolumesSummary{vol1, vol2}
assert.NilError(t, err)
assert.DeepEqual(t, volumes, expected)
}

View File

@ -29,17 +29,14 @@ import (
gsync "sync" gsync "sync"
"time" "time"
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
ccli "github.com/docker/cli/cli/command/container"
pathutil "github.com/docker/compose/v2/internal/paths" pathutil "github.com/docker/compose/v2/internal/paths"
"github.com/docker/compose/v2/internal/sync" "github.com/docker/compose/v2/internal/sync"
"github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/internal/tracing"
"github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/progress"
cutils "github.com/docker/compose/v2/pkg/utils"
"github.com/docker/compose/v2/pkg/watch" "github.com/docker/compose/v2/pkg/watch"
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
ccli "github.com/docker/cli/cli/command/container"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
@ -58,16 +55,17 @@ type Watcher struct {
errCh chan error errCh chan error
} }
func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc, consumer api.LogConsumer) (*Watcher, error) { func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Watcher, error) {
for i := range project.Services { for i := range project.Services {
service := project.Services[i] service := project.Services[i]
if service.Develop != nil && service.Develop.Watch != nil { if service.Develop != nil && service.Develop.Watch != nil {
build := options.Create.Build build := options.Create.Build
build.Quiet = true
return &Watcher{ return &Watcher{
project: project, project: project,
options: api.WatchOptions{ options: api.WatchOptions{
LogTo: consumer, LogTo: options.Start.Attach,
Build: build, Build: build,
}, },
watchFn: w, watchFn: w,
@ -194,6 +192,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, opti
return nil, err return nil, err
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
options.LogTo.Register(api.WatchLogger)
var ( var (
rules []watchRule rules []watchRule
@ -235,7 +234,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, opti
var initialSync bool var initialSync bool
success, err := trigger.Extensions.Get("x-initialSync", &initialSync) success, err := trigger.Extensions.Get("x-initialSync", &initialSync)
if err == nil && success && initialSync && isSync(trigger) { if err == nil && success && initialSync && isSync(trigger) {
// Need to check initial files are in container that are meant to be synced from watch action // Need to check initial files are in container that are meant to be synched from watch action
err := s.initialSync(ctx, project, service, trigger, syncer) err := s.initialSync(ctx, project, service, trigger, syncer)
if err != nil { if err != nil {
return nil, err return nil, err
@ -396,7 +395,7 @@ func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project)
return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name) return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name)
} }
if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 { if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 {
return nil, fmt.Errorf("can't watch with action %q on service %s without a command", types.WatchActionSyncExec, service.Name) return nil, fmt.Errorf("can't watch with action %q on service %s wihtout a command", types.WatchActionSyncExec, service.Name)
} }
config.Watch[i] = trigger config.Watch[i] = trigger
@ -600,10 +599,6 @@ func (s *composeService) rebuild(ctx context.Context, project *types.Project, se
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service(s) %q after changes were detected...", services)) options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service(s) %q after changes were detected...", services))
// restrict the build to ONLY this service, not any of its dependencies // restrict the build to ONLY this service, not any of its dependencies
options.Build.Services = services options.Build.Services = services
options.Build.Progress = progress.ModePlain
options.Build.Out = cutils.GetWriter(func(line string) {
options.LogTo.Log(api.WatchLogger, line)
})
var ( var (
imageNameToIdMap map[string]string imageNameToIdMap map[string]string
@ -818,7 +813,7 @@ func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Pr
if err != nil { if err != nil {
return time.Now(), err return time.Now(), err
} }
// Need to get the oldest one? // Need to get oldest one?
timeCreated, err := time.Parse(time.RFC3339Nano, img.Created) timeCreated, err := time.Parse(time.RFC3339Nano, img.Created)
if err != nil { if err != nil {
return time.Now(), err return time.Now(), err

View File

@ -71,6 +71,9 @@ func (s stdLogger) Status(containerName, msg string) {
fmt.Printf("%s: %s\n", containerName, msg) fmt.Printf("%s: %s\n", containerName, msg)
} }
func (s stdLogger) Register(containerName string) {
}
func TestWatch_Sync(t *testing.T) { func TestWatch_Sync(t *testing.T) {
mockCtrl := gomock.NewController(t) mockCtrl := gomock.NewController(t)
cli := mocks.NewMockCli(mockCtrl) cli := mocks.NewMockCli(mockCtrl)

View File

@ -32,7 +32,7 @@ import (
) )
func TestLocalComposeBuild(t *testing.T) { func TestLocalComposeBuild(t *testing.T) {
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1,COMPOSE_BAKE=0", "DOCKER_BUILDKIT=1,COMPOSE_BAKE=1"} { for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1", "DOCKER_BUILDKIT=1,COMPOSE-BAKE=1"} {
c := NewCLI(t, WithEnv(strings.Split(env, ",")...)) c := NewCLI(t, WithEnv(strings.Split(env, ",")...))
t.Run(env+" build named and unnamed images", func(t *testing.T) { t.Run(env+" build named and unnamed images", func(t *testing.T) {
@ -536,75 +536,3 @@ func TestBuildDependsOn(t *testing.T) {
out := res.Combined() out := res.Combined()
assert.Check(t, strings.Contains(out, "test1 Built")) assert.Check(t, strings.Contains(out, "test1 Built"))
} }
func TestBuildSubset(t *testing.T) {
c := NewParallelCLI(t)
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/subset/compose.yaml", "down", "--rmi=local")
})
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/subset/compose.yaml", "build", "main")
out := res.Combined()
assert.Check(t, strings.Contains(out, "main Built"))
}
func TestBuildDependentImage(t *testing.T) {
c := NewParallelCLI(t)
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "down", "--rmi=local")
})
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "build", "firstbuild")
out := res.Combined()
assert.Check(t, strings.Contains(out, "firstbuild Built"))
res = c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/dependencies/compose.yaml", "build", "secondbuild")
out = res.Combined()
assert.Check(t, strings.Contains(out, "secondbuild Built"))
}
func TestBuildSubDependencies(t *testing.T) {
c := NewParallelCLI(t)
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "down", "--rmi=local")
})
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "build", "main")
out := res.Combined()
assert.Check(t, strings.Contains(out, "main Built"))
res = c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/sub-dependencies/compose.yaml", "up", "--build", "main")
out = res.Combined()
assert.Check(t, strings.Contains(out, "main Built"))
}
func TestBuildLongOutputLine(t *testing.T) {
c := NewParallelCLI(t)
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/long-output-line/compose.yaml", "down", "--rmi=local")
})
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/long-output-line/compose.yaml", "build", "long-line")
out := res.Combined()
assert.Check(t, strings.Contains(out, "long-line Built"))
res = c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/long-output-line/compose.yaml", "up", "--build", "long-line")
out = res.Combined()
assert.Check(t, strings.Contains(out, "long-line Built"))
}
func TestBuildDependentImageWithProfile(t *testing.T) {
c := NewParallelCLI(t)
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/profiles/compose.yaml", "down", "--rmi=local")
})
res := c.RunDockerComposeCmd(t, "-f", "fixtures/build-test/profiles/compose.yaml", "build", "secret-build-test")
out := res.Combined()
assert.Check(t, strings.Contains(out, "secret-build-test Built"))
}

View File

@ -196,30 +196,4 @@ func TestLocalComposeRun(t *testing.T) {
"front", "env") "front", "env")
res.Assert(t, icmd.Expected{Out: "FOO=BAR"}) res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
}) })
t.Run("compose run -rm with stop signal", func(t *testing.T) {
projectName := "run-test"
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "-f", "./fixtures/ps-test/compose.yaml", "run", "--rm", "-d", "nginx")
res.Assert(t, icmd.Success)
res = c.RunDockerCmd(t, "ps", "--quiet", "--filter", "name=run-test-nginx")
containerID := strings.TrimSpace(res.Stdout())
res = c.RunDockerCmd(t, "stop", containerID)
res.Assert(t, icmd.Success)
res = c.RunDockerCmd(t, "ps", "--all", "--filter", "name=run-test-nginx", "--format", "'{{.Names}}'")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test-nginx"), res.Stdout())
})
t.Run("compose run --env", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--env", "FOO=BAR",
"front", "env")
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
})
t.Run("compose run --build", func(t *testing.T) {
c.cleanupWithDown(t, "run-test", "--rmi=local")
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "build", "echo", "hello world")
res.Assert(t, icmd.Expected{Out: "hello world"})
})
} }

View File

@ -37,11 +37,6 @@ func TestUnusedMissingEnvFile(t *testing.T) {
defer c.cleanupWithDown(t, "unused_dotenv") defer c.cleanupWithDown(t, "unused_dotenv")
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "up", "-d", "serviceA") c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "up", "-d", "serviceA")
// Runtime operations should work even with missing env file
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "ps")
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "logs")
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "down")
} }
func TestRunEnvFile(t *testing.T) { func TestRunEnvFile(t *testing.T) {

View File

@ -1,47 +0,0 @@
/*
Copyright 2023 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"testing"
"gotest.tools/v3/icmd"
)
func TestExec(t *testing.T) {
const projectName = "e2e-exec"
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/exec/compose.yaml", "--project-name", projectName, "run", "-d", "test", "cat")
res := c.RunDockerComposeCmdNoCheck(t, "--project-name", projectName, "exec", "--index=1", "test", "ps")
res.Assert(t, icmd.Expected{Err: "service \"test\" is not running container #1", ExitCode: 1})
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "ps")
res.Assert(t, icmd.Expected{Out: "cat"}) // one-off container was selected
c.RunDockerComposeCmd(t, "-f", "./fixtures/exec/compose.yaml", "--project-name", projectName, "up", "-d")
res = c.RunDockerComposeCmd(t, "--project-name", projectName, "exec", "test", "ps")
res.Assert(t, icmd.Expected{Out: "tail"}) // service container was selected
}

View File

@ -3,6 +3,6 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: {{ .Values.namespace }} name: bridge
labels: labels:
com.docker.compose.project: bridge com.docker.compose.project: bridge

View File

@ -3,8 +3,8 @@
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: {{ .Values.projectName }} name: bridge
namespace: {{ .Values.namespace }} namespace: bridge
labels: labels:
com.docker.compose.project: bridge com.docker.compose.project: bridge
data: data:

View File

@ -10,13 +10,13 @@ metadata:
com.docker.compose.service: serviceA com.docker.compose.service: serviceA
app.kubernetes.io/managed-by: Helm app.kubernetes.io/managed-by: Helm
spec: spec:
replicas: {{ .Values.deployment.defaultReplicas }} replicas: 1
selector: selector:
matchLabels: matchLabels:
com.docker.compose.project: bridge com.docker.compose.project: bridge
com.docker.compose.service: serviceA com.docker.compose.service: serviceA
strategy: strategy:
type: {{ .Values.deployment.strategy }} type: Recreate
template: template:
metadata: metadata:
labels: labels:
@ -28,10 +28,6 @@ spec:
- name: servicea - name: servicea
image: {{ .Values.serviceA.image }} image: {{ .Values.serviceA.image }}
imagePullPolicy: {{ .Values.serviceA.imagePullPolicy }} imagePullPolicy: {{ .Values.serviceA.imagePullPolicy }}
resources:
limits:
cpu: {{ .Values.resources.defaultCpuLimit }}
memory: {{ .Values.resources.defaultMemoryLimit }}
ports: ports:
- name: servicea-8080 - name: servicea-8080
containerPort: 8080 containerPort: 8080
@ -43,7 +39,7 @@ spec:
volumes: volumes:
- name: etc-my-config1-txt - name: etc-my-config1-txt
configMap: configMap:
name: {{ .Values.projectName }} name: bridge
items: items:
- key: my-config - key: my-config
path: my-config path: my-config

Some files were not shown because too many files have changed in this diff Show More