mirror of
https://github.com/docker/compose.git
synced 2025-09-10 11:28:11 +02:00
Compare commits
159 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a473341058 | ||
|
385b3f5c96 | ||
|
2d482e61ce | ||
|
c75418ee07 | ||
|
0cdc5c9bff | ||
|
b768232c0e | ||
|
09689400e5 | ||
|
cb3691154b | ||
|
b387ba4a05 | ||
|
7cd569922e | ||
|
eec2bb7ea6 | ||
|
2c15aef2ed | ||
|
290366205b | ||
|
a91ca95a71 | ||
|
beb81a73f9 | ||
|
f217207876 | ||
|
02ffe2ac6c | ||
|
f48131fb66 | ||
|
4dd369bdcb | ||
|
ad73766bf2 | ||
|
3c1f5a1815 | ||
|
42d1e4c333 | ||
|
6ca8663bda | ||
|
b33ecf65e8 | ||
|
04b8ac5fe4 | ||
|
d09948da41 | ||
|
f1efbb8322 | ||
|
1d52012b82 | ||
|
1d69f4a68c | ||
|
6078b4d99d | ||
|
73e593e69a | ||
|
51499f645b | ||
|
5165b0f814 | ||
|
93dd1a4558 | ||
|
ba3f5664c0 | ||
|
c420bc44c4 | ||
|
60681a824c | ||
|
19ad737ee7 | ||
|
d3a260e533 | ||
|
e75329dce2 | ||
|
1dc0be2c30 | ||
|
3bac9ffd08 | ||
|
f266715dd0 | ||
|
c2cb0aef6b | ||
|
fbc62d111e | ||
|
0d40064ce8 | ||
|
91a6eafa1d | ||
|
f36ee00f71 | ||
|
29ede3ba7d | ||
|
bf6d7bf47e | ||
|
fc66da06db | ||
|
909211dd61 | ||
|
0dc9852c67 | ||
|
a478702236 | ||
|
2c12ad19db | ||
|
038ea8441a | ||
|
9e98e6101e | ||
|
52f04229c0 | ||
|
28895d0322 | ||
|
a926f7d717 | ||
|
fe046915eb | ||
|
adbd61e5d6 | ||
|
e37ac04329 | ||
|
cab2c2a44e | ||
|
1946de598d | ||
|
8e29a138aa | ||
|
3c8da0afee | ||
|
1b12c867c5 | ||
|
1a4fc55fd7 | ||
|
efc939dcee | ||
|
d6e9f79ba6 | ||
|
b4c44a431f | ||
|
fb5a8644c3 | ||
|
95660c5e5a | ||
|
f6ddd6ae88 | ||
|
4ae7066955 | ||
|
fd954f266c | ||
|
d62e21025c | ||
|
6a2d16bd10 | ||
|
4d47da6dc2 | ||
|
8f91793fb5 | ||
|
1d2223fb23 | ||
|
d4f6000712 | ||
|
c50d16cd78 | ||
|
3875e13fad | ||
|
c89f30170d | ||
|
41a9b91887 | ||
|
5fc2b2a71c | ||
|
b1cd40c316 | ||
|
362ab0733f | ||
|
f35d2cfb3b | ||
|
17ba6c7188 | ||
|
1c37f1abb6 | ||
|
485b6200ee | ||
|
8c17a35609 | ||
|
6b9667401a | ||
|
9a1e589ce8 | ||
|
5e147e852e | ||
|
29308cb97e | ||
|
0b0242d0ac | ||
|
5a704004d3 | ||
|
cb95910018 | ||
|
f42226e352 | ||
|
0cc3c7a550 | ||
|
f7ee9c8a0c | ||
|
35efa97b7d | ||
|
9e17a091be | ||
|
4bbc6c609f | ||
|
69f1430a49 | ||
|
7cf7c6414f | ||
|
0e0ed91a39 | ||
|
66524e7728 | ||
|
c626befee1 | ||
|
60ee6adcd2 | ||
|
8faf1eb808 | ||
|
3b0601b671 | ||
|
f42374bb18 | ||
|
b046a5ef72 | ||
|
1570c6c076 | ||
|
674e13fb6d | ||
|
6fa173124a | ||
|
2c69fc3d4d | ||
|
317ebcd3b0 | ||
|
5cf1f0e2a4 | ||
|
6198ed5bd2 | ||
|
00ccddbde8 | ||
|
63b441401e | ||
|
3a7982fe45 | ||
|
5430caa172 | ||
|
ee1b1e0a93 | ||
|
26e46d7cc8 | ||
|
a9e76943f6 | ||
|
b6a0df8d3c | ||
|
5a063b7510 | ||
|
ae49bba9be | ||
|
51acc58453 | ||
|
7c999d7f93 | ||
|
ad750d6143 | ||
|
fe382df507 | ||
|
6501d59efc | ||
|
33a782572f | ||
|
65803ea12e | ||
|
f613379373 | ||
|
3553aa26a6 | ||
|
257ea7b75d | ||
|
d219aa66f4 | ||
|
c9ebfad78e | ||
|
8e57362a0f | ||
|
29630f184e | ||
|
6514c680a5 | ||
|
3394bf031b | ||
|
832a08f579 | ||
|
aadce87b16 | ||
|
b3207c455d | ||
|
769b7391ba | ||
|
149b882ebf | ||
|
c97e40e2b8 | ||
|
22e23bd4dc | ||
|
2dde5faeb8 |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -12,6 +12,12 @@ 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
|
||||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -183,6 +183,11 @@ 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:
|
||||||
@ -190,6 +195,9 @@ 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:
|
||||||
|
@ -30,6 +30,8 @@ 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
|
||||||
|
@ -4,12 +4,15 @@
|
|||||||
* 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
|
||||||
|
|
||||||
|
@ -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.23.8
|
ARG GO_VERSION=1.24.7
|
||||||
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
|
||||||
|
6
Makefile
6
Makefile
@ -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: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
e2e-compose: example-provider ## 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,6 +87,10 @@ 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
|
||||||
|
@ -23,6 +23,12 @@ 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
|
||||||
|
@ -55,8 +55,10 @@ 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(attribute.StringSlice("cli.flags", getFlags(cmd.Flags())))
|
cmdSpan.SetAttributes(
|
||||||
cmdSpan.SetAttributes(attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()))
|
attribute.StringSlice("cli.flags", getFlags(cmd.Flags())),
|
||||||
|
attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()),
|
||||||
|
)
|
||||||
|
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
wrapRunE(cmd, cmdSpan, tracingShutdown)
|
wrapRunE(cmd, cmdSpan, tracingShutdown)
|
||||||
|
@ -45,7 +45,8 @@ type buildOptions struct {
|
|||||||
deps bool
|
deps bool
|
||||||
print bool
|
print bool
|
||||||
check bool
|
check bool
|
||||||
provenance bool
|
sbom string
|
||||||
|
provenance string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||||
@ -84,6 +85,7 @@ 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
|
||||||
}
|
}
|
||||||
@ -119,12 +121,14 @@ 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, "Don't print anything to STDOUT")
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the build output")
|
||||||
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
|
||||||
@ -145,6 +149,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
|
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
|
||||||
@ -155,7 +160,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||||
apiBuildOptions.Provenance = true
|
apiBuildOptions.Attestations = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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/manager"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
"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)
|
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution)
|
||||||
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] != manager.MetadataSubcommandName && os.Args[1] != PluginName
|
return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootCommand returns the compose command with its child commands
|
// RootCommand returns the compose command with its child commands
|
||||||
@ -636,6 +636,7 @@ 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)
|
||||||
@ -660,6 +661,10 @@ 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(¶llel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
|
c.Flags().IntVar(¶llel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
|
||||||
@ -688,7 +693,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 {
|
if _, ok := os.LookupEnv(k); !ok && strings.HasPrefix(k, "COMPOSE_") {
|
||||||
if err = os.Setenv(k, v); err != nil {
|
if err = os.Setenv(k, v); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ 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
|
||||||
@ -111,6 +113,12 @@ 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)
|
||||||
}
|
}
|
||||||
@ -147,6 +155,8 @@ 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.")
|
||||||
@ -367,6 +377,30 @@ 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 != "*" {
|
||||||
|
@ -29,7 +29,9 @@ 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 {
|
||||||
@ -48,6 +50,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +63,8 @@ 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{}{
|
||||||
|
@ -18,13 +18,18 @@ 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 {
|
||||||
@ -59,7 +64,15 @@ 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 {
|
||||||
return runExec(ctx, dockerCli, backend, opts)
|
err := 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),
|
||||||
}
|
}
|
||||||
@ -69,7 +82,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")
|
||||||
@ -78,6 +91,12 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,8 +128,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 := ""
|
errMsg := fmt.Sprintf("exit status %d", exitCode)
|
||||||
if err != nil {
|
if err != nil && err.Error() != "" {
|
||||||
errMsg = err.Error()
|
errMsg = err.Error()
|
||||||
}
|
}
|
||||||
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
||||||
|
@ -101,6 +101,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
|||||||
// Convert map to slice
|
// Convert map to slice
|
||||||
var imageList []img
|
var imageList []img
|
||||||
for ctr, i := range images {
|
for ctr, i := range images {
|
||||||
|
lastTagTime := i.LastTagTime
|
||||||
|
if lastTagTime.IsZero() {
|
||||||
|
lastTagTime = i.Created
|
||||||
|
}
|
||||||
imageList = append(imageList, img{
|
imageList = append(imageList, img{
|
||||||
ContainerName: ctr,
|
ContainerName: ctr,
|
||||||
ID: i.ID,
|
ID: i.ID,
|
||||||
@ -108,7 +112,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
|||||||
Tag: i.Tag,
|
Tag: i.Tag,
|
||||||
Platform: platforms.Format(i.Platform),
|
Platform: platforms.Format(i.Platform),
|
||||||
Size: i.Size,
|
Size: i.Size,
|
||||||
LastTagTime: i.LastTagTime,
|
LastTagTime: lastTagTime,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
json, err := formatter.ToJSON(imageList, "", "")
|
json, err := formatter.ToJSON(imageList, "", "")
|
||||||
|
@ -120,8 +120,8 @@ func (options runOptions) apply(project *types.Project) (*types.Project, error)
|
|||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (options runOptions) getEnvironment() (types.Mapping, error) {
|
func (options runOptions) getEnvironment(resolve func(string) (string, bool)) (types.Mapping, error) {
|
||||||
environment := types.NewMappingWithEquals(options.environment).Resolve(os.LookupEnv).ToMapping()
|
environment := types.NewMappingWithEquals(options.environment).Resolve(resolve).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 {
|
||||||
@ -282,14 +282,14 @@ 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(project.ServiceNames())
|
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
buildForRun = &bo
|
buildForRun = &bo
|
||||||
}
|
}
|
||||||
|
|
||||||
environment, err := options.getEnvironment()
|
environment, err := options.getEnvironment(project.Environment.Resolve)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,7 @@ 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")
|
||||||
@ -223,6 +224,7 @@ 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,
|
||||||
@ -330,7 +332,7 @@ func runUp(
|
|||||||
WaitTimeout: timeout,
|
WaitTimeout: timeout,
|
||||||
Watch: upOptions.watch,
|
Watch: upOptions.watch,
|
||||||
Services: services,
|
Services: services,
|
||||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
|
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
92
cmd/compose/volumes.go
Normal file
92
cmd/compose/volumes.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
}
|
@ -28,49 +28,42 @@ 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 HideCursor() {
|
func showCursor() {
|
||||||
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 MoveCursorX(pos int) {
|
func carriageReturn() {
|
||||||
if disableAnsi {
|
if disableAnsi {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
|
fmt.Print(ansi(fmt.Sprintf("[%dG", 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearLine() {
|
func clearLine() {
|
||||||
if disableAnsi {
|
if disableAnsi {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -78,7 +71,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
|
||||||
}
|
}
|
||||||
@ -86,7 +79,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
|
||||||
}
|
}
|
||||||
@ -94,7 +87,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")
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package formatter
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@ -58,6 +59,9 @@ 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) {
|
||||||
@ -91,11 +95,15 @@ 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 {
|
||||||
res := "\033["
|
var sb strings.Builder
|
||||||
|
sb.WriteString("\033[")
|
||||||
for _, c := range formatOpts {
|
for _, c := range formatOpts {
|
||||||
res = fmt.Sprintf("%s%s;", res, c)
|
sb.WriteString(c)
|
||||||
|
sb.WriteString(";")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s%sm", res, code)
|
sb.WriteString(code)
|
||||||
|
sb.WriteString("m")
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeColorFunc(code string) colorFunc {
|
func makeColorFunc(code string) colorFunc {
|
||||||
@ -122,8 +130,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(30 + i))
|
colors[name] = makeColorFunc(strconv.Itoa(ansiColorOffset + i))
|
||||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
|
colors["intense_"+name] = makeColorFunc(strconv.Itoa(ansiColorOffset+i) + ";1")
|
||||||
}
|
}
|
||||||
rainbow = []colorFunc{
|
rainbow = []colorFunc{
|
||||||
colors["cyan"],
|
colors["cyan"],
|
||||||
|
@ -56,10 +56,6 @@ 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, " ")
|
||||||
@ -73,9 +69,12 @@ func (l *logConsumer) register(name string) *presenter {
|
|||||||
} else {
|
} else {
|
||||||
cf := monochrome
|
cf := monochrome
|
||||||
if l.color {
|
if l.color {
|
||||||
if name == api.WatchLogger {
|
switch name {
|
||||||
|
case "":
|
||||||
|
cf = monochrome
|
||||||
|
case api.WatchLogger:
|
||||||
cf = makeColorFunc("92")
|
cf = makeColorFunc("92")
|
||||||
} else {
|
default:
|
||||||
cf = nextColor()
|
cf = nextColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,23 +117,15 @@ 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) {
|
||||||
@ -168,3 +159,27 @@ 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()
|
||||||
|
}
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -49,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,12 +69,12 @@ func (ke *KeyboardError) error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type KeyboardWatch struct {
|
type KeyboardWatch struct {
|
||||||
Watching bool
|
Watching bool
|
||||||
Watcher Toggle
|
Watcher Feature
|
||||||
IsConfigured bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Toggle interface {
|
// Feature is an compose feature that can be started/stopped by a menu command
|
||||||
|
type Feature interface {
|
||||||
Start(context.Context) error
|
Start(context.Context) error
|
||||||
Stop() error
|
Stop() error
|
||||||
}
|
}
|
||||||
@ -90,31 +89,26 @@ const (
|
|||||||
|
|
||||||
type LogKeyboard struct {
|
type LogKeyboard struct {
|
||||||
kError KeyboardError
|
kError KeyboardError
|
||||||
Watch KeyboardWatch
|
Watch *KeyboardWatch
|
||||||
IsDockerDesktopActive bool
|
IsDockerDesktopActive bool
|
||||||
logLevel KEYBOARD_LOG_LEVEL
|
logLevel KEYBOARD_LOG_LEVEL
|
||||||
signalChannel chan<- os.Signal
|
signalChannel chan<- os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(ndeloof) we should avoid use of such a global reference. see use in logConsumer
|
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard {
|
||||||
var KeyboardManager *LogKeyboard
|
return &LogKeyboard{
|
||||||
|
|
||||||
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal, w bool, watcher Toggle) *LogKeyboard {
|
|
||||||
KeyboardManager = &LogKeyboard{
|
|
||||||
Watch: KeyboardWatch{
|
|
||||||
Watching: w,
|
|
||||||
Watcher: watcher,
|
|
||||||
IsConfigured: !reflect.ValueOf(watcher).IsNil(),
|
|
||||||
},
|
|
||||||
IsDockerDesktopActive: isDockerDesktopActive,
|
IsDockerDesktopActive: isDockerDesktopActive,
|
||||||
logLevel: INFO,
|
logLevel: INFO,
|
||||||
signalChannel: sc,
|
signalChannel: sc,
|
||||||
}
|
}
|
||||||
return KeyboardManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lk *LogKeyboard) ClearKeyboardInfo() {
|
func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer {
|
||||||
lk.clearNavigationMenu()
|
return logDecorator{
|
||||||
|
decorated: l,
|
||||||
|
Before: lk.clearNavigationMenu,
|
||||||
|
After: lk.PrintKeyboardInfo,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lk *LogKeyboard) PrintKeyboardInfo() {
|
func (lk *LogKeyboard) PrintKeyboardInfo() {
|
||||||
@ -139,7 +133,7 @@ func (lk *LogKeyboard) createBuffer(lines int) {
|
|||||||
|
|
||||||
if lines > 0 {
|
if lines > 0 {
|
||||||
allocateSpace(lines)
|
allocateSpace(lines)
|
||||||
MoveCursorUp(lines)
|
moveCursorUp(lines)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,17 +146,17 @@ func (lk *LogKeyboard) printNavigationMenu() {
|
|||||||
height := goterm.Height()
|
height := goterm.Height()
|
||||||
menu := lk.navigationMenu()
|
menu := lk.navigationMenu()
|
||||||
|
|
||||||
MoveCursorX(0)
|
carriageReturn()
|
||||||
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)
|
||||||
|
|
||||||
MoveCursorX(0)
|
carriageReturn()
|
||||||
RestoreCursor()
|
restoreCursor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +179,7 @@ func (lk *LogKeyboard) navigationMenu() string {
|
|||||||
watchInfo = navColor(" ")
|
watchInfo = navColor(" ")
|
||||||
}
|
}
|
||||||
isEnabled := " Enable"
|
isEnabled := " Enable"
|
||||||
if lk.Watch.Watching {
|
if lk.Watch != nil && lk.Watch.Watching {
|
||||||
isEnabled = " Disable"
|
isEnabled = " Disable"
|
||||||
}
|
}
|
||||||
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
|
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
|
||||||
@ -194,15 +188,15 @@ func (lk *LogKeyboard) navigationMenu() string {
|
|||||||
|
|
||||||
func (lk *LogKeyboard) clearNavigationMenu() {
|
func (lk *LogKeyboard) clearNavigationMenu() {
|
||||||
height := goterm.Height()
|
height := goterm.Height()
|
||||||
MoveCursorX(0)
|
carriageReturn()
|
||||||
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) {
|
||||||
@ -268,7 +262,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.IsConfigured {
|
if lk.Watch == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lk.Watch.Watching {
|
if lk.Watch.Watching {
|
||||||
@ -299,7 +293,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.IsConfigured {
|
if lk.Watch == nil {
|
||||||
// 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)
|
||||||
@ -322,22 +316,29 @@ 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()
|
||||||
MoveCursorX(0)
|
carriageReturn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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/manager"
|
"github.com/docker/cli/cli-plugins/metadata"
|
||||||
"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
|
||||||
},
|
},
|
||||||
manager.Metadata{
|
metadata.Metadata{
|
||||||
SchemaVersion: "0.1.0",
|
SchemaVersion: "0.1.0",
|
||||||
Vendor: "Docker Inc.",
|
Vendor: "Docker Inc.",
|
||||||
Version: internal.Version,
|
Version: internal.Version,
|
||||||
|
@ -39,20 +39,30 @@ 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: up,
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
|
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().Int("size", 10, "Database size in GB")
|
upCmd.Flags().IntVar(&options.size, "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")
|
||||||
|
|
||||||
@ -71,13 +81,13 @@ func composeCommand() *cobra.Command {
|
|||||||
|
|
||||||
const lineSeparator = "\n"
|
const lineSeparator = "\n"
|
||||||
|
|
||||||
func up(_ *cobra.Command, args []string) {
|
func up(options options, 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 < 100; i += 10 {
|
for i := 0; i < options.size; i += 10 {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i, lineSeparator)
|
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, 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)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ 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 |
|
||||||
|
|
||||||
|
@ -22,9 +22,11 @@ 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` | | Don't print anything to STDOUT |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output |
|
||||||
|
| `--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) |
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ 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 |
|
||||||
|
@ -23,10 +23,12 @@ 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-->
|
||||||
|
@ -6,6 +6,12 @@ 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 |
|
||||||
@ -14,7 +20,7 @@ you can use a command such as `docker compose exec web sh` to get an interactive
|
|||||||
| `--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 |
|
||||||
@ -28,3 +34,9 @@ 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.
|
@ -44,6 +44,7 @@ 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 |
|
||||||
|
16
docs/reference/compose_volumes.md
Normal file
16
docs/reference/compose_volumes.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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-->
|
||||||
|
|
@ -37,6 +37,7 @@ 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:
|
||||||
@ -72,6 +73,7 @@ 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:
|
||||||
|
@ -125,6 +125,15 @@ 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"
|
||||||
@ -149,7 +158,16 @@ options:
|
|||||||
shorthand: q
|
shorthand: q
|
||||||
value_type: bool
|
value_type: bool
|
||||||
default_value: "false"
|
default_value: "false"
|
||||||
description: Don't print anything to STDOUT
|
description: Suppress the build output
|
||||||
|
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
|
||||||
|
@ -56,6 +56,26 @@ 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"
|
||||||
|
@ -34,6 +34,24 @@ 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
|
||||||
|
@ -5,6 +5,12 @@ 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
|
||||||
@ -52,7 +58,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"
|
||||||
|
@ -211,6 +211,16 @@ 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"
|
||||||
|
52
docs/reference/docker_compose_volumes.yaml
Normal file
52
docs/reference/docker_compose_volumes.yaml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
|
77
go.mod
77
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/docker/compose/v2
|
module github.com/docker/compose/v2
|
||||||
|
|
||||||
go 1.23.8
|
go 1.24.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
@ -8,28 +8,27 @@ 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.6.5
|
github.com/compose-spec/compose-go/v2 v2.8.2
|
||||||
github.com/containerd/containerd/v2 v2.1.2
|
github.com/containerd/containerd/v2 v2.1.4
|
||||||
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.25.0
|
github.com/docker/buildx v0.28.0
|
||||||
github.com/docker/cli v28.2.2+incompatible
|
github.com/docker/cli v28.4.0+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.10.0
|
github.com/docker/cli-docs-tool v0.10.0
|
||||||
github.com/docker/docker v28.2.2+incompatible
|
github.com/docker/docker v28.4.0+incompatible
|
||||||
github.com/docker/go-connections v0.5.0
|
github.com/docker/go-connections v0.6.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.23.0
|
github.com/moby/buildkit v0.24.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
|
||||||
@ -40,31 +39,30 @@ 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.9.1
|
github.com/spf13/cobra v1.10.1
|
||||||
github.com/spf13/pflag v1.0.6
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
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.35.0
|
go.opentelemetry.io/otel v1.36.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.35.0
|
go.opentelemetry.io/otel/metric v1.36.0
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0
|
go.opentelemetry.io/otel/sdk v1.36.0
|
||||||
go.opentelemetry.io/otel/trace v1.35.0
|
go.opentelemetry.io/otel/trace v1.36.0
|
||||||
go.uber.org/goleak v1.3.0
|
go.uber.org/goleak v1.3.0
|
||||||
go.uber.org/mock v0.5.2
|
go.uber.org/mock v0.6.0
|
||||||
golang.org/x/sync v0.15.0
|
golang.org/x/sync v0.17.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.36.0
|
||||||
google.golang.org/grpc v1.73.0
|
google.golang.org/grpc v1.74.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.2.1 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // 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
|
||||||
@ -89,7 +87,7 @@ require (
|
|||||||
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.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // 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
|
||||||
@ -98,12 +96,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.2 // indirect
|
github.com/go-logr/logr v1.4.3 // 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.0.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.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
|
||||||
@ -117,6 +115,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.9.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
|
||||||
@ -168,27 +167,25 @@ require (
|
|||||||
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.56.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.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.35.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
golang.org/x/oauth2 v0.29.0 // indirect
|
golang.org/x/net v0.40.0 // indirect
|
||||||
golang.org/x/term v0.31.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/term v0.32.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-20250324211829-b45e905df463 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // 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
|
||||||
@ -205,7 +202,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
exclude (
|
exclude (
|
||||||
// FIXME(thaJeztah): remoove this once kubernetes updated their dependencies to no longer need this.
|
// FIXME(thaJeztah): remove 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
|
||||||
|
151
go.sum
151
go.sum
@ -1,5 +1,3 @@
|
|||||||
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=
|
||||||
@ -10,8 +8,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.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
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=
|
||||||
@ -80,16 +78,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.6.5 h1:H7xP5OMKdkN2p0brx01slxIU6dE/q6ybbG+jozPtIqk=
|
github.com/compose-spec/compose-go/v2 v2.8.2 h1:A1iVoZJUex7buGv1CpnC5uwNuyTMBYpDAmBnAQmia9Q=
|
||||||
github.com/compose-spec/compose-go/v2 v2.6.5/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU=
|
github.com/compose-spec/compose-go/v2 v2.8.2/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
|
||||||
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.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
github.com/containerd/console v1.0.5/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.2 h1:4ZQxB+FVYmwXZgpBcKfar6ieppm3KC5C6FRKvtJ6DRU=
|
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ=
|
||||||
github.com/containerd/containerd/v2 v2.1.2/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
|
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
|
||||||
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=
|
||||||
@ -113,8 +111,9 @@ 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=
|
||||||
@ -127,24 +126,24 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docker/buildx v0.25.0 h1:qs5WxBo0wQKSXcQ+v6UhWaeM2Pu+95ZCymaimRzInaE=
|
github.com/docker/buildx v0.28.0 h1:ZnrVsZ/qQwSOQ4Fx3IgXjiurAwvocaF1YUaPbIXD89E=
|
||||||
github.com/docker/buildx v0.25.0/go.mod h1:xJcOeBhz49tgqN174MMGuOU4bxNmgfaLnZn7Gm641EE=
|
github.com/docker/buildx v0.28.0/go.mod h1:nLwx58w7xrQbLVSXiWiHpkVhY4ou4ci/hYomc139Vjk=
|
||||||
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
|
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
||||||
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
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.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.4.0+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.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
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=
|
||||||
@ -171,8 +170,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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/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=
|
||||||
@ -188,8 +187,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.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.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=
|
||||||
@ -317,8 +316,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.23.0 h1:HV+u7xM2IZhAjVautFR2l5FNhkxFR0jhF5ILXyc3398=
|
github.com/moby/buildkit v0.24.0 h1:qYfTl7W1SIJzWDIDCcPT8FboHIZCYfi++wvySi3eyFE=
|
||||||
github.com/moby/buildkit v0.23.0/go.mod h1:v5jMDvQgUyidk3wu3NvVAAd5JJo83nfet9Gf/o0+EAQ=
|
github.com/moby/buildkit v0.24.0/go.mod h1:4qovICAdR2H4C7+EGMRva5zgHW1gyhT4/flHI7F5F9k=
|
||||||
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=
|
||||||
@ -445,13 +444,14 @@ github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYec
|
|||||||
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.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
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,8 +468,8 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
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=
|
||||||
@ -488,13 +488,6 @@ 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=
|
||||||
@ -508,36 +501,38 @@ 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.56.0 h1:4BZHA+B1wXEQoGNHxW8mURaLhcdGwvRnmhGbm+odRbc=
|
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/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko=
|
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/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.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||||
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 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI=
|
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/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM=
|
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/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE=
|
||||||
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.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
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.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
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=
|
||||||
@ -546,13 +541,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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
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=
|
||||||
@ -562,10 +557,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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
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=
|
||||||
@ -573,8 +568,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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
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=
|
||||||
@ -596,19 +591,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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
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.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
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=
|
||||||
@ -616,19 +611,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.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
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-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
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.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||||
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=
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build linux || openbsd
|
//go:build linux || openbsd || freebsd
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Docker Compose CLI authors
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
38
internal/registry/registry.go
Normal file
38
internal/registry/registry.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
@ -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,7 +84,14 @@ 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)
|
||||||
@ -92,17 +99,23 @@ 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 {
|
||||||
return fmt.Errorf("deleting paths in %s: %w", containerID, err)
|
errMu.Lock()
|
||||||
|
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 {
|
||||||
return fmt.Errorf("copying files to %s: %w", containerID, err)
|
errMu.Lock()
|
||||||
|
errs = append(errs, fmt.Errorf("copying files to %s: %w", containerID, err))
|
||||||
|
errMu.Unlock()
|
||||||
}
|
}
|
||||||
return nil
|
return nil // don't fail-fast; collect all errors
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return eg.Wait().ErrorOrNil()
|
|
||||||
|
_ = eg.Wait()
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchiveBuilder struct {
|
type ArchiveBuilder struct {
|
||||||
@ -229,7 +242,7 @@ func (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tarPath writes the given source path into tarWriter at the given dest (recursively for directories).
|
// entriesForPath 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) {
|
||||||
|
@ -77,11 +77,13 @@ 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),
|
||||||
@ -110,6 +112,7 @@ 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))
|
||||||
|
@ -22,15 +22,12 @@ import (
|
|||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
)
|
)
|
||||||
|
|
||||||
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
|
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive 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),
|
||||||
|
@ -18,8 +18,9 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,23 +29,45 @@ 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 eg multierror.Group
|
var (
|
||||||
for i := range m.exporters {
|
wg sync.WaitGroup
|
||||||
exporter := m.exporters[i]
|
errMu sync.Mutex
|
||||||
eg.Go(func() error {
|
errs = make([]error, 0, len(m.exporters))
|
||||||
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()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
return eg.Wait()
|
wg.Wait()
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m MuxExporter) Shutdown(ctx context.Context) error {
|
func (m MuxExporter) Shutdown(ctx context.Context) error {
|
||||||
var eg multierror.Group
|
var (
|
||||||
for i := range m.exporters {
|
wg sync.WaitGroup
|
||||||
exporter := m.exporters[i]
|
errMu sync.Mutex
|
||||||
eg.Go(func() error {
|
errs = make([]error, 0, len(m.exporters))
|
||||||
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()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
return eg.Wait()
|
wg.Wait()
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -26,6 +27,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/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/docker/api/types/volume"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service manages a compose project
|
// Service manages a compose project
|
||||||
@ -98,8 +100,16 @@ 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
|
||||||
}
|
}
|
||||||
@ -161,8 +171,14 @@ type BuildOptions struct {
|
|||||||
Print bool
|
Print bool
|
||||||
// Check let builder validate build configuration
|
// Check let builder validate build configuration
|
||||||
Check bool
|
Check bool
|
||||||
// Provenance
|
// Attestations allows to enable attestations generation
|
||||||
Provenance bool
|
Attestations 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
|
||||||
@ -389,6 +405,8 @@ 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
|
||||||
@ -540,6 +558,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,7 +657,6 @@ 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
|
||||||
@ -646,16 +664,18 @@ 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
|
||||||
// Container is the name of the container _without the project prefix_.
|
Time int64
|
||||||
|
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.
|
||||||
Container string
|
Source string
|
||||||
ID string
|
ID string
|
||||||
Service string
|
Service string
|
||||||
Line string
|
Line string
|
||||||
// ContainerEventExit only
|
// ExitCode is only set on ContainerEventExited events
|
||||||
ExitCode int
|
ExitCode int
|
||||||
Restarting bool
|
Restarting bool
|
||||||
}
|
}
|
||||||
@ -665,17 +685,19 @@ 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
|
||||||
// ContainerEventAttach is a ContainerEvent of type attach. First event sent about a container
|
// ContainerEventStarted let consumer know a container has been started
|
||||||
ContainerEventAttach
|
ContainerEventStarted
|
||||||
|
// 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
|
||||||
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
|
// ContainerEventExited is a ContainerEvent of type exit. ExitCode is set
|
||||||
ContainerEventExit
|
ContainerEventExited
|
||||||
// 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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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: "running", // needed for --wait option
|
Status: containerType.StateRunning, // needed for --wait option
|
||||||
Health: &containerType.Health{
|
Health: &containerType.Health{
|
||||||
Status: containerType.Healthy, // needed for healthcheck control
|
Status: containerType.Healthy, // needed for healthcheck control
|
||||||
},
|
},
|
||||||
|
@ -20,6 +20,7 @@ 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
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
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"
|
||||||
@ -65,9 +63,6 @@ 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 {
|
||||||
segments := v.Segments()
|
ComposeVersion = v.Core().String()
|
||||||
if len(segments) > 2 {
|
|
||||||
ComposeVersion = fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
pkg/api/labels_test.go
Normal file
35
pkg/api/labels_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,11 @@ 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"
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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"
|
||||||
@ -112,15 +113,20 @@ func convert(ctx context.Context, dockerCli command.Cli, model map[string]any, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
usr, err := user.Current()
|
containerConfig := &container.Config{
|
||||||
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,
|
}
|
||||||
}, &container.HostConfig{
|
// On POSIX systems, this is a decimal number representing the uid.
|
||||||
|
// 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, "")
|
||||||
@ -198,7 +204,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 cerrdefs.IsNotFound(err) {
|
if errdefs.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 {
|
||||||
|
@ -33,6 +33,8 @@ 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 {
|
||||||
@ -73,7 +75,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, "/templates")
|
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, created.ID, templatesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -82,7 +84,7 @@ func CreateTransformer(ctx context.Context, dockerCli command.Cli, options Creat
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
srcInfo := archive.CopyInfo{
|
srcInfo := archive.CopyInfo{
|
||||||
Path: "/templates",
|
Path: templatesPath,
|
||||||
Exists: true,
|
Exists: true,
|
||||||
IsDir: stat.Mode.IsDir(),
|
IsDir: stat.Mode.IsDir(),
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ package compose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
@ -41,11 +41,9 @@ func (s *composeService) useAPISocket(project *types.Project) (*types.Project, e
|
|||||||
return project, nil
|
return project, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
socket := s.dockerCli.DockerEndpoint().Host
|
if s.dockerCli.ServerInfo().OSType == "windows" {
|
||||||
if !strings.HasPrefix(socket, "unix://") {
|
return nil, errors.New("use_api_socket can't be used with a Windows Docker Engine")
|
||||||
return nil, fmt.Errorf("use_api_socket can only be used with unix sockets: docker endpoint %s is incompatible", socket)
|
|
||||||
}
|
}
|
||||||
socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
|
|
||||||
|
|
||||||
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
|
creds, err := s.dockerCli.ConfigFile().GetAllCredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -64,9 +62,12 @@ func (s *composeService) useAPISocket(project *types.Project) (*types.Project, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, service := range project.Services {
|
for name, service := range project.Services {
|
||||||
|
if !service.UseAPISocket {
|
||||||
|
continue
|
||||||
|
}
|
||||||
service.Volumes = append(service.Volumes, types.ServiceVolumeConfig{
|
service.Volumes = append(service.Volumes, types.ServiceVolumeConfig{
|
||||||
Type: types.VolumeTypeBind,
|
Type: types.VolumeTypeBind,
|
||||||
Source: socket,
|
Source: "/var/run/docker.sock",
|
||||||
Target: "/var/run/docker.sock",
|
Target: "/var/run/docker.sock",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,41 +61,37 @@ 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 {
|
||||||
serviceName := container.Labels[api.ServiceLabel]
|
service := container.Labels[api.ServiceLabel]
|
||||||
containerName := getContainerNameWithoutProject(container)
|
name := getContainerNameWithoutProject(container)
|
||||||
|
return s.doAttachContainer(ctx, service, container.ID, name, listener)
|
||||||
|
}
|
||||||
|
|
||||||
listener(api.ContainerEvent{
|
func (s *composeService) doAttachContainer(ctx context.Context, service, id, name string, listener api.ContainerEventListener) error {
|
||||||
Type: api.ContainerEventAttach,
|
inspect, err := s.apiClient().ContainerInspect(ctx, id)
|
||||||
Container: containerName,
|
|
||||||
ID: container.ID,
|
|
||||||
Service: serviceName,
|
|
||||||
})
|
|
||||||
|
|
||||||
wOut := utils.GetWriter(func(line string) {
|
|
||||||
listener(api.ContainerEvent{
|
|
||||||
Type: api.ContainerEventLog,
|
|
||||||
Container: containerName,
|
|
||||||
ID: container.ID,
|
|
||||||
Service: serviceName,
|
|
||||||
Line: line,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
wErr := utils.GetWriter(func(line string) {
|
|
||||||
listener(api.ContainerEvent{
|
|
||||||
Type: api.ContainerEventErr,
|
|
||||||
Container: containerName,
|
|
||||||
ID: container.ID,
|
|
||||||
Service: serviceName,
|
|
||||||
Line: line,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
inspect, err := s.apiClient().ContainerInspect(ctx, container.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = s.attachContainerStreams(ctx, container.ID, inspect.Config.Tty, nil, wOut, wErr)
|
wOut := utils.GetWriter(func(line string) {
|
||||||
|
listener(api.ContainerEvent{
|
||||||
|
Type: api.ContainerEventLog,
|
||||||
|
Source: name,
|
||||||
|
ID: id,
|
||||||
|
Service: service,
|
||||||
|
Line: line,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
wErr := utils.GetWriter(func(line string) {
|
||||||
|
listener(api.ContainerEvent{
|
||||||
|
Type: api.ContainerEventErr,
|
||||||
|
Source: name,
|
||||||
|
ID: id,
|
||||||
|
Service: service,
|
||||||
|
Line: line,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = s.attachContainerStreams(ctx, id, inspect.Config.Tty, nil, wOut, wErr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -78,16 +79,19 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
|||||||
policy = types.IncludeDependencies
|
policy = types.IncludeDependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
if len(options.Services) == 0 {
|
||||||
if len(options.Services) > 0 {
|
options.Services = project.ServiceNames()
|
||||||
// As user requested some services to be built, also include those used as additional_contexts
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
@ -172,7 +176,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 == "" {
|
if options.Progress == progress.ModeAuto {
|
||||||
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),
|
||||||
@ -397,6 +401,7 @@ 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 {
|
||||||
@ -471,8 +476,19 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
attests := map[string]*string{}
|
attests := map[string]*string{}
|
||||||
if !options.Provenance {
|
if options.Attestations {
|
||||||
attests["provenance"] = nil
|
if service.Build.Provenance != "" {
|
||||||
|
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{
|
||||||
@ -502,6 +518,16 @@ 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) {
|
||||||
|
@ -20,9 +20,11 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -32,20 +34,18 @@ 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"
|
||||||
"github.com/moby/buildkit/util/gitutil"
|
gitutil "github.com/moby/buildkit/frontend/dockerfile/dfgitutil"
|
||||||
"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,6 +59,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +76,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 manager.IsNotFound(err) {
|
if errdefs.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
|
||||||
}
|
}
|
||||||
@ -116,6 +119,7 @@ type bakeTarget struct {
|
|||||||
Entitlements []string `json:"entitlements,omitempty"`
|
Entitlements []string `json:"entitlements,omitempty"`
|
||||||
ExtraHosts map[string]string `json:"extra-hosts,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
|
||||||
@ -128,7 +132,18 @@ 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)
|
||||||
display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress))
|
if options.Progress == progress.ModeAuto {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@ -142,10 +157,11 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||||||
Targets: map[string]bakeTarget{},
|
Targets: map[string]bakeTarget{},
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
group bakeGroup
|
group bakeGroup
|
||||||
privileged bool
|
privileged bool
|
||||||
read []string
|
read []string
|
||||||
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
|
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
|
// produce a unique ID for service used as bake target
|
||||||
@ -165,6 +181,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||||||
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) {
|
||||||
@ -192,17 +209,27 @@ 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:
|
||||||
outputs = []string{fmt.Sprintf("type=docker,load=true,push=%t", push)}
|
if 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)
|
||||||
|
expectedImages[serviceName] = image
|
||||||
|
|
||||||
|
pull := service.Build.Pull || options.Pull
|
||||||
|
noCache := service.Build.NoCache || options.NoCache
|
||||||
|
|
||||||
target := targets[serviceName]
|
target := targets[serviceName]
|
||||||
cfg.Targets[target] = bakeTarget{
|
cfg.Targets[target] = bakeTarget{
|
||||||
Context: build.Context,
|
Context: build.Context,
|
||||||
@ -210,17 +237,18 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||||||
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: build.Labels,
|
Labels: labels,
|
||||||
Tags: append(build.Tags, api.GetImageNameOrDefault(service, project.Name)),
|
Tags: append(build.Tags, image),
|
||||||
|
|
||||||
CacheFrom: build.CacheFrom,
|
CacheFrom: build.CacheFrom,
|
||||||
// CacheTo: TODO
|
CacheTo: build.CacheTo,
|
||||||
|
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: options.Pull,
|
Pull: pull,
|
||||||
NoCache: options.NoCache,
|
NoCache: noCache,
|
||||||
ShmSize: build.ShmSize,
|
ShmSize: build.ShmSize,
|
||||||
Ulimits: toBakeUlimits(build.Ulimits),
|
Ulimits: toBakeUlimits(build.Ulimits),
|
||||||
Entitlements: entitlements,
|
Entitlements: entitlements,
|
||||||
@ -228,6 +256,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||||||
|
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
Call: call,
|
Call: call,
|
||||||
|
Attest: toBakeAttest(build),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,6 +310,12 @@ 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)
|
||||||
@ -291,23 +326,21 @@ 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)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, buildx.Path, args...)
|
if s.dryRun {
|
||||||
// Remove DOCKER_CLI_PLUGIN... variable so buildx can detect it run standalone
|
return dryRunBake(ctx, cfg), nil
|
||||||
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
|
|
||||||
|
|
||||||
// Use docker/cli mechanism to propagate termination signal to child process
|
|
||||||
server, err := socket.NewPluginServer(nil)
|
|
||||||
if err == nil {
|
|
||||||
defer server.Close() //nolint:errcheck
|
|
||||||
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
|
|
||||||
}
|
}
|
||||||
|
cmd := exec.CommandContext(ctx, buildx.Path, args...)
|
||||||
|
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
|
err = s.prepareShellOut(ctx, types.NewMapping(os.Environ()), cmd)
|
||||||
|
if err != nil {
|
||||||
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
|
return nil, err
|
||||||
carrier := propagation.MapCarrier{}
|
}
|
||||||
otel.GetTextMapPropagator().Inject(ctx, &carrier)
|
endpoint, cleanup, err := s.propagateDockerEndpoint()
|
||||||
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Env = append(cmd.Env, endpoint...)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
cmd.Stdout = s.stdout()
|
cmd.Stdout = s.stdout()
|
||||||
cmd.Stdin = bytes.NewBuffer(b)
|
cmd.Stdin = bytes.NewBuffer(b)
|
||||||
@ -317,16 +350,22 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errMessage []string
|
var errMessage []string
|
||||||
scanner := bufio.NewScanner(pipe)
|
reader := bufio.NewReader(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 scanner.Scan() {
|
for {
|
||||||
line := scanner.Text()
|
line, readErr := reader.ReadString('\n')
|
||||||
|
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)
|
||||||
@ -364,13 +403,14 @@ 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 name := range serviceToBeBuild {
|
||||||
|
image := expectedImages[name]
|
||||||
target := targets[name]
|
target := targets[name]
|
||||||
built, ok := md[target]
|
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", name)
|
||||||
}
|
}
|
||||||
results[name] = built.Digest
|
results[image] = built.Digest
|
||||||
cw.Event(progress.BuiltEvent(name))
|
cw.Event(progress.BuiltEvent(image))
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
@ -432,20 +472,28 @@ func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig)
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter(environ []string, variable string) []string {
|
func toBakeAttest(build types.BuildConfig) []string {
|
||||||
prefix := variable + "="
|
var attests []string
|
||||||
filtered := make([]string, 0, len(environ))
|
|
||||||
for _, val := range environ {
|
// Handle per-service provenance configuration (only from build config, not global options)
|
||||||
if !strings.HasPrefix(val, prefix) {
|
if build.Provenance != "" {
|
||||||
filtered = append(filtered, val)
|
if build.Provenance == "true" {
|
||||||
|
attests = append(attests, "type=provenance")
|
||||||
|
} else if build.Provenance != "false" {
|
||||||
|
attests = append(attests, fmt.Sprintf("type=provenance,%s", build.Provenance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
func replace(environ []string, variable, value string) []string {
|
// Handle per-service SBOM configuration (only from build config, not global options)
|
||||||
filtered := filter(environ, variable)
|
if build.SBOM != "" {
|
||||||
return append(filtered, fmt.Sprintf("%s=%s", variable, value))
|
if build.SBOM == "true" {
|
||||||
|
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 {
|
||||||
@ -458,9 +506,37 @@ 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)
|
||||||
}
|
}
|
||||||
symlinks, err := filepath.EvalSymlinks(dockerfile)
|
dir := filepath.Dir(dockerfile)
|
||||||
|
symlinks, err := filepath.EvalSymlinks(dir)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return symlinks
|
return filepath.Join(symlinks, filepath.Base(dockerfile))
|
||||||
}
|
}
|
||||||
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -70,16 +70,7 @@ 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)))
|
||||||
w.Event(progress.Event{
|
displayDryRunBuildEvent(w, name, dryRunUUID, options.Tags[0])
|
||||||
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,
|
||||||
}}
|
}}
|
||||||
|
@ -22,23 +22,6 @@ 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
|
||||||
|
@ -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 numberLabelX == numberLabelY {
|
if IsOneOffLabelTrueX || IsOneOffLabelTrueY {
|
||||||
return !IsOneOffLabelTrueX && IsOneOffLabelTrueY
|
return !IsOneOffLabelTrueX && IsOneOffLabelTrueY
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,12 +128,6 @@ 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()...)
|
||||||
@ -141,7 +135,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 == ContainerExited || c.State == ContainerDead
|
return c.State == container.StateExited || c.State == container.StateDead
|
||||||
}
|
}
|
||||||
// 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]
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -29,7 +30,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"
|
||||||
containerType "github.com/docker/docker/api/types/container"
|
"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"
|
||||||
@ -151,19 +152,19 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
|||||||
})
|
})
|
||||||
|
|
||||||
slices.Reverse(containers)
|
slices.Reverse(containers)
|
||||||
for i, container := range containers {
|
for i, ctr := 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
|
||||||
container := container
|
ctr := ctr
|
||||||
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(container)...)
|
traceOpts := append(tracing.ServiceOptions(service), tracing.ContainerOptions(ctr)...)
|
||||||
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, container, &service, timeout, false)
|
return c.service.stopAndRemoveContainer(ctx, ctr, &service, timeout, false)
|
||||||
}))
|
}))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mustRecreate, err := c.mustRecreate(service, container, recreate)
|
mustRecreate, err := c.mustRecreate(service, ctr, recreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -173,9 +174,9 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i, container := i, container
|
i, ctr := i, ctr
|
||||||
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "container/recreate", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
eg.Go(tracing.SpanWrapFuncForErrGroup(ctx, "container/recreate", tracing.ContainerOptions(ctr), func(ctx context.Context) error {
|
||||||
recreated, err := c.service.recreateContainer(ctx, project, service, container, inherit, timeout)
|
recreated, err := c.service.recreateContainer(ctx, project, service, ctr, inherit, timeout)
|
||||||
updated[i] = recreated
|
updated[i] = recreated
|
||||||
return err
|
return err
|
||||||
}))
|
}))
|
||||||
@ -184,20 +185,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(container)
|
name := getContainerProgressName(ctr)
|
||||||
switch container.State {
|
switch ctr.State {
|
||||||
case ContainerRunning:
|
case container.StateRunning:
|
||||||
w.Event(progress.RunningEvent(name))
|
w.Event(progress.RunningEvent(name))
|
||||||
case ContainerCreated:
|
case container.StateCreated:
|
||||||
case ContainerRestarting:
|
case container.StateRestarting:
|
||||||
case ContainerExited:
|
case container.StateExited:
|
||||||
default:
|
default:
|
||||||
container := container
|
ctr := ctr
|
||||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(container), func(ctx context.Context) error {
|
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "service/start", tracing.ContainerOptions(ctr), func(ctx context.Context) error {
|
||||||
return c.service.startContainer(ctx, container)
|
return c.service.startContainer(ctx, ctr)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
updated[i] = container
|
updated[i] = ctr
|
||||||
}
|
}
|
||||||
|
|
||||||
next := nextContainerNumber(containers)
|
next := nextContainerNumber(containers)
|
||||||
@ -213,8 +214,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),
|
||||||
}
|
}
|
||||||
container, err := c.service.createContainer(ctx, project, service, name, number, opts)
|
ctr, err := c.service.createContainer(ctx, project, service, name, number, opts)
|
||||||
updated[actual+i] = container
|
updated[actual+i] = ctr
|
||||||
return err
|
return err
|
||||||
}))
|
}))
|
||||||
continue
|
continue
|
||||||
@ -236,7 +237,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
|
||||||
}
|
}
|
||||||
@ -244,7 +245,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 = ContainerExited
|
dependent.State = container.StateExited
|
||||||
dependentStates[i] = dependent
|
dependentStates[i] = dependent
|
||||||
}
|
}
|
||||||
c.setObservedState(name, dependentStates)
|
c.setObservedState(name, dependentStates)
|
||||||
@ -327,7 +328,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual containerType.Summary, policy string) (bool, error) {
|
func (c *convergence) mustRecreate(expected types.ServiceConfig, actual container.Summary, policy string) (bool, error) {
|
||||||
if policy == api.RecreateNever {
|
if policy == api.RecreateNever {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -359,7 +360,7 @@ func (c *convergence) mustRecreate(expected types.ServiceConfig, actual containe
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkExpectedNetworks(expected types.ServiceConfig, actual containerType.Summary, networks map[string]string) bool {
|
func checkExpectedNetworks(expected types.ServiceConfig, actual container.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]
|
||||||
@ -382,7 +383,7 @@ func checkExpectedNetworks(expected types.ServiceConfig, actual containerType.Su
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkExpectedVolumes(expected types.ServiceConfig, actual containerType.Summary, volumes map[string]string) bool {
|
func checkExpectedVolumes(expected types.ServiceConfig, actual container.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) {
|
||||||
@ -422,7 +423,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 containerType.Summary) string {
|
func getContainerProgressName(ctr container.Summary) string {
|
||||||
return "Container " + getCanonicalContainerName(ctr)
|
return "Container " + getCanonicalContainerName(ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,11 +564,14 @@ 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 []containerType.Summary) int {
|
func nextContainerNumber(containers []container.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]
|
||||||
@ -588,7 +592,7 @@ func nextContainerNumber(containers []containerType.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 containerType.Summary, err error) {
|
) (ctr container.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))
|
||||||
@ -608,8 +612,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 containerType.Summary, inherit bool, timeout *time.Duration,
|
replaced container.Summary, inherit bool, timeout *time.Duration,
|
||||||
) (created containerType.Summary, err error) {
|
) (created container.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"))
|
||||||
@ -628,17 +632,22 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
|||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var inherited *containerType.Summary
|
var inherited *container.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, replaced.ID),
|
Labels: mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replacedContainerName),
|
||||||
}
|
}
|
||||||
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 {
|
||||||
@ -646,17 +655,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, containerType.StopOptions{Timeout: timeoutInSecond})
|
err = s.apiClient().ContainerStop(ctx, replaced.ID, container.StopOptions{Timeout: timeoutInSecond})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.apiClient().ContainerRemove(ctx, replaced.ID, containerType.RemoveOptions{})
|
err = s.apiClient().ContainerRemove(ctx, replaced.ID, container.RemoveOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.apiClient().ContainerRename(ctx, created.ID, name)
|
err = s.apiClient().ContainerRename(ctx, tmpName, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
@ -668,12 +677,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 containerType.Summary) error {
|
func (s *composeService) startContainer(ctx context.Context, ctr container.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, containerType.StartOptions{})
|
err := s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -686,11 +695,11 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
|||||||
service types.ServiceConfig,
|
service types.ServiceConfig,
|
||||||
name string,
|
name string,
|
||||||
number int,
|
number int,
|
||||||
inherit *containerType.Summary,
|
inherit *container.Summary,
|
||||||
opts createOptions,
|
opts createOptions,
|
||||||
w progress.Writer,
|
w progress.Writer,
|
||||||
) (containerType.Summary, error) {
|
) (container.Summary, error) {
|
||||||
var created containerType.Summary
|
var created container.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
|
||||||
@ -724,11 +733,11 @@ func (s *composeService) createMobyContainer(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return created, err
|
return created, err
|
||||||
}
|
}
|
||||||
created = containerType.Summary{
|
created = container.Summary{
|
||||||
ID: inspectedContainer.ID,
|
ID: inspectedContainer.ID,
|
||||||
Labels: inspectedContainer.Config.Labels,
|
Labels: inspectedContainer.Config.Labels,
|
||||||
Names: []string{inspectedContainer.Name},
|
Names: []string{inspectedContainer.Name},
|
||||||
NetworkSettings: &containerType.NetworkSettingsSummary{
|
NetworkSettings: &container.NetworkSettingsSummary{
|
||||||
Networks: inspectedContainer.NetworkSettings.Networks,
|
Networks: inspectedContainer.NetworkSettings.Networks,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -756,14 +765,7 @@ 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()
|
||||||
@ -826,33 +828,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 {
|
||||||
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
name := container.Name[1:]
|
name := ctr.Name[1:]
|
||||||
|
|
||||||
if container.State.Status == "exited" {
|
if ctr.State.Status == container.StateExited {
|
||||||
return false, fmt.Errorf("container %s exited (%d)", name, container.State.ExitCode)
|
return false, fmt.Errorf("container %s exited (%d)", name, ctr.State.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Config.Healthcheck == nil && fallbackRunning {
|
if ctr.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 container.State != nil && container.State.Status == "running", nil
|
return ctr.State != nil && ctr.State.Status == container.StateRunning, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State == nil || container.State.Health == nil {
|
if ctr.State == nil || ctr.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 container.State.Health.Status {
|
switch ctr.State.Health.Status {
|
||||||
case containerType.Healthy:
|
case container.Healthy:
|
||||||
// Continue by checking the next container.
|
// Continue by checking the next container.
|
||||||
case containerType.Unhealthy:
|
case container.Unhealthy:
|
||||||
return false, fmt.Errorf("container %s is unhealthy", name)
|
return false, fmt.Errorf("container %s is unhealthy", name)
|
||||||
case containerType.Starting:
|
case container.Starting:
|
||||||
return false, nil
|
return false, nil
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("container %s had unexpected health status %q", name, container.State.Health.Status)
|
return false, fmt.Errorf("container %s had unexpected health status %q", name, ctr.State.Health.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -860,12 +862,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 {
|
||||||
container, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
ctr, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, err
|
return false, 0, err
|
||||||
}
|
}
|
||||||
if container.State != nil && container.State.Status == "exited" {
|
if ctr.State != nil && ctr.State.Status == container.StateExited {
|
||||||
return true, container.State.ExitCode, nil
|
return true, ctr.State.ExitCode, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, 0, nil
|
return false, 0, nil
|
||||||
@ -894,12 +896,23 @@ 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 == ContainerRunning {
|
if ctr.State == container.StateRunning {
|
||||||
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, containerType.StartOptions{})
|
err = s.apiClient().ContainerStart(ctx, ctr.ID, container.StartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -919,9 +932,7 @@ 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 {
|
||||||
for k, v := range l {
|
maps.Copy(merged, l)
|
||||||
merged[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -161,7 +160,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 !system.IsAbs(linkTarget) {
|
if !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)
|
||||||
@ -264,7 +263,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 !system.IsAbs(linkTarget) {
|
if !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)
|
||||||
@ -302,8 +301,20 @@ 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 system.IsAbs(arg) {
|
if 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
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,12 @@ 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"
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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"
|
||||||
@ -83,6 +82,11 @@ 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)
|
||||||
@ -176,15 +180,12 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
|
|||||||
return createConfigs{}, err
|
return createConfigs{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var runCmd, entrypoint []string
|
||||||
runCmd strslice.StrSlice
|
|
||||||
entrypoint strslice.StrSlice
|
|
||||||
)
|
|
||||||
if service.Command != nil {
|
if service.Command != nil {
|
||||||
runCmd = strslice.StrSlice(service.Command)
|
runCmd = service.Command
|
||||||
}
|
}
|
||||||
if service.Entrypoint != nil {
|
if service.Entrypoint != nil {
|
||||||
entrypoint = strslice.StrSlice(service.Entrypoint)
|
entrypoint = service.Entrypoint
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -281,8 +282,8 @@ func (s *composeService) getCreateConfigs(ctx context.Context,
|
|||||||
Annotations: service.Annotations,
|
Annotations: service.Annotations,
|
||||||
Binds: binds,
|
Binds: binds,
|
||||||
Mounts: mounts,
|
Mounts: mounts,
|
||||||
CapAdd: strslice.StrSlice(service.CapAdd),
|
CapAdd: service.CapAdd,
|
||||||
CapDrop: strslice.StrSlice(service.CapDrop),
|
CapDrop: service.CapDrop,
|
||||||
NetworkMode: networkMode,
|
NetworkMode: networkMode,
|
||||||
Init: service.Init,
|
Init: service.Init,
|
||||||
IpcMode: container.IpcMode(service.Ipc),
|
IpcMode: container.IpcMode(service.Ipc),
|
||||||
@ -1261,7 +1262,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 cerrdefs.IsConflict(err) {
|
if errdefs.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)
|
||||||
@ -1427,7 +1428,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
|
||||||
}
|
}
|
||||||
@ -1496,7 +1497,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 !cerrdefs.IsNotFound(err) {
|
} else if !errdefs.IsNotFound(err) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1533,7 +1534,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 !cerrdefs.IsNotFound(err) {
|
if !errdefs.IsNotFound(err) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if volume.External {
|
if volume.External {
|
||||||
@ -1598,7 +1599,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
|
||||||
}
|
}
|
||||||
@ -1622,7 +1623,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 %q", volume.Name)
|
eventName := fmt.Sprintf("Volume %s", 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)
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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 cerrdefs.IsNotFound(err) {
|
if errdefs.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 cerrdefs.IsNotFound(err) {
|
if errdefs.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 cerrdefs.IsConflict(err) {
|
if errdefs.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 cerrdefs.IsNotFound(err) {
|
if errdefs.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 cerrdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
// Already gone
|
// Already gone
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -287,25 +287,33 @@ 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 cerrdefs.IsConflict(err) {
|
if errdefs.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 cerrdefs.IsNotFound(err) {
|
if errdefs.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(ctx context.Context, w progress.Writer, service *types.ServiceConfig, ctr containerType.Summary, timeout *time.Duration) error {
|
func (s *composeService) stopContainer(
|
||||||
|
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, nil)
|
err := s.runHook(ctx, ctr, *service, hook, listener)
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,11 +329,15 @@ func (s *composeService) stopContainer(ctx context.Context, w progress.Writer, s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) stopContainers(ctx context.Context, w progress.Writer, serv *types.ServiceConfig, containers []containerType.Summary, timeout *time.Duration) error {
|
func (s *composeService) stopContainers(
|
||||||
|
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)
|
return s.stopContainer(ctx, w, serv, ctr, timeout, listener)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
@ -344,8 +356,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)
|
err := s.stopContainer(ctx, w, service, ctr, timeout, nil)
|
||||||
if cerrdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
w.Event(progress.RemovedEvent(eventName))
|
w.Event(progress.RemovedEvent(eventName))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -357,7 +369,7 @@ func (s *composeService) stopAndRemoveContainer(ctx context.Context, ctr contain
|
|||||||
Force: true,
|
Force: true,
|
||||||
RemoveVolumes: volumes,
|
RemoveVolumes: volumes,
|
||||||
})
|
})
|
||||||
if err != nil && !cerrdefs.IsNotFound(err) && !cerrdefs.IsConflict(err) {
|
if err != nil && !errdefs.IsNotFound(err) && !errdefs.IsConflict(err) {
|
||||||
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
|
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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.NotFound(fmt.Errorf("test specified that image %q should not exist", img))
|
err = errdefs.ErrNotFound.WithMessage(fmt.Sprintf("test specified that image %q should not exist", img))
|
||||||
}
|
}
|
||||||
|
|
||||||
api.EXPECT().ImageInspect(gomock.Any(), img).
|
api.EXPECT().ImageInspect(gomock.Any(), img).
|
||||||
|
@ -32,6 +32,8 @@ 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 {
|
||||||
|
@ -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, nil
|
return sterr.StatusCode, err
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
Container: getContainerNameWithoutProject(ctr) + " ->",
|
Source: 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,7 +48,6 @@ 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,
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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 cerrdefs.IsNotFound(err) {
|
if errdefs.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
|
||||||
|
@ -22,8 +22,9 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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"
|
||||||
@ -90,6 +91,11 @@ 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{
|
||||||
@ -103,6 +109,7 @@ 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
|
||||||
@ -121,7 +128,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 cerrdefs.IsNotFound(err) {
|
if errdefs.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)
|
||||||
|
@ -20,6 +20,7 @@ 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"
|
||||||
@ -44,8 +45,12 @@ 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()
|
||||||
image1 := imageInspect("image1", "foo:1", 12345)
|
timeStr1 := "2025-06-06T06:06:06.000000000Z"
|
||||||
image2 := imageInspect("image2", "bar:2", 67890)
|
created1, _ := time.Parse(time.RFC3339Nano, timeStr1)
|
||||||
|
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")
|
||||||
@ -62,32 +67,36 @@ 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) image.InspectResponse {
|
func imageInspect(id string, imageReference string, size int64, created string) 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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: ContainerExited,
|
State: container.StateExited,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,12 +18,10 @@ 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"
|
||||||
@ -63,9 +61,8 @@ 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.logContainers(ctx, consumer, ctr, options)
|
err := s.logContainer(ctx, consumer, ctr, options)
|
||||||
var notImplErr errdefs.ErrNotImplemented
|
if errdefs.IsNotImplemented(err) {
|
||||||
if errors.As(err, ¬ImplErr) {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@ -74,70 +71,58 @@ 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
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, c := range containers {
|
monitor := newMonitor(s.apiClient(), projectName)
|
||||||
printer.HandleEvent(api.ContainerEvent{
|
if len(options.Services) > 0 {
|
||||||
Type: api.ContainerEventAttach,
|
monitor.withServices(options.Services)
|
||||||
Container: getContainerNameWithoutProject(c),
|
} else if options.Project != nil {
|
||||||
ID: c.ID,
|
monitor.withServices(options.Project.ServiceNames())
|
||||||
Service: c.Labels[api.ServiceLabel],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
monitor.withListener(printer.HandleEvent)
|
||||||
eg.Go(func() error {
|
monitor.withListener(func(event api.ContainerEvent) {
|
||||||
err := s.watchContainers(ctx, projectName, options.Services, nil, printer.HandleEvent, containers, func(c container.Summary, t time.Time) error {
|
if event.Type == api.ContainerEventStarted {
|
||||||
printer.HandleEvent(api.ContainerEvent{
|
|
||||||
Type: api.ContainerEventAttach,
|
|
||||||
Container: getContainerNameWithoutProject(c),
|
|
||||||
ID: c.ID,
|
|
||||||
Service: c.Labels[api.ServiceLabel],
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
err := s.logContainers(ctx, consumer, c, api.LogOptions{
|
ctr, err := s.apiClient().ContainerInspect(ctx, event.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.doLogContainer(ctx, consumer, event.Source, ctr, api.LogOptions{
|
||||||
Follow: options.Follow,
|
Follow: options.Follow,
|
||||||
Since: t.Format(time.RFC3339Nano),
|
Since: ctr.State.StartedAt,
|
||||||
Until: options.Until,
|
Until: options.Until,
|
||||||
Tail: options.Tail,
|
Tail: options.Tail,
|
||||||
Timestamps: options.Timestamps,
|
Timestamps: options.Timestamps,
|
||||||
})
|
})
|
||||||
var notImplErr errdefs.ErrNotImplemented
|
if errdefs.IsNotImplemented(err) {
|
||||||
if errors.As(err, ¬ImplErr) {
|
|
||||||
// ignore
|
// ignore
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return nil
|
}
|
||||||
}, func(c container.Summary, t time.Time) error {
|
})
|
||||||
printer.HandleEvent(api.ContainerEvent{
|
eg.Go(func() error {
|
||||||
Type: api.ContainerEventAttach,
|
// pass ctx so monitor will immediately stop on SIGINT
|
||||||
Container: "", // actual name will be set by start event
|
return monitor.Start(ctx)
|
||||||
ID: c.ID,
|
|
||||||
Service: c.Labels[api.ServiceLabel],
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
printer.Stop()
|
|
||||||
return err
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return eg.Wait()
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) logContainers(ctx context.Context, consumer api.LogConsumer, c container.Summary, options api.LogOptions) error {
|
func (s *composeService) logContainer(ctx context.Context, consumer api.LogConsumer, c container.Summary, options api.LogOptions) error {
|
||||||
cnt, err := s.apiClient().ContainerInspect(ctx, c.ID)
|
ctr, 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)
|
||||||
|
}
|
||||||
|
|
||||||
r, err := s.apiClient().ContainerLogs(ctx, cnt.ID, container.LogsOptions{
|
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, ctr.ID, container.LogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Follow: options.Follow,
|
Follow: options.Follow,
|
||||||
@ -151,11 +136,10 @@ func (s *composeService) logContainers(ctx context.Context, consumer api.LogCons
|
|||||||
}
|
}
|
||||||
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 cnt.Config.Tty {
|
if ctr.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)
|
||||||
|
@ -189,8 +189,6 @@ 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()
|
||||||
|
265
pkg/compose/model.go
Normal file
265
pkg/compose/model.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
215
pkg/compose/monitor.go
Normal file
215
pkg/compose/monitor.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
}
|
@ -27,16 +27,15 @@ 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 {
|
||||||
@ -52,6 +51,8 @@ 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
|
||||||
|
|
||||||
@ -70,6 +71,8 @@ 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) + "_"
|
||||||
@ -161,14 +164,14 @@ func (s *composeService) getPluginBinaryPath(provider string) (path string, err
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
path = plugin.Path
|
path = plugin.Path
|
||||||
}
|
}
|
||||||
if manager.IsNotFound(err) {
|
if errdefs.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)
|
cmdOptionsMetadata := s.getPluginMetadata(path, service.Provider.Type, project)
|
||||||
var currentCommandMetadata CommandMetadata
|
var currentCommandMetadata CommandMetadata
|
||||||
switch command {
|
switch command {
|
||||||
case "up":
|
case "up":
|
||||||
@ -176,8 +179,9 @@ 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
|
||||||
}
|
}
|
||||||
@ -193,34 +197,21 @@ 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
|
|
||||||
env := types.NewMapping(os.Environ())
|
err := s.prepareShellOut(ctx, project.Environment, cmd)
|
||||||
// but remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone
|
if err != nil {
|
||||||
delete(env, manager.ReexecEnvvar)
|
return nil, err
|
||||||
// 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.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) ProviderMetadata {
|
func (s *composeService) getPluginMetadata(path, command string, project *types.Project) 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
|
||||||
|
|
||||||
@ -255,6 +246,10 @@ 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"`
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package compose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
)
|
)
|
||||||
@ -26,137 +25,33 @@ 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) Cancel() {
|
|
||||||
// note: HandleEvent is used to ensure this doesn't deadlock
|
|
||||||
p.HandleEvent(api.ContainerEvent{Type: api.UserCancel})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *printer) Stop() {
|
|
||||||
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) {
|
func (p *printer) HandleEvent(event api.ContainerEvent) {
|
||||||
select {
|
switch event.Type {
|
||||||
case <-p.stopCh:
|
case api.ContainerEventExited:
|
||||||
return
|
if event.Restarting {
|
||||||
default:
|
p.consumer.Status(event.Source, fmt.Sprintf("exited with code %d (restarting)", event.ExitCode))
|
||||||
p.queue <- event
|
} else {
|
||||||
}
|
p.consumer.Status(event.Source, fmt.Sprintf("exited with code %d", event.ExitCode))
|
||||||
}
|
|
||||||
|
|
||||||
//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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ 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"
|
||||||
@ -42,13 +43,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, container := range containers {
|
for i, ctr := range containers {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
publishers := make([]api.PortPublisher, len(container.Ports))
|
publishers := make([]api.PortPublisher, len(ctr.Ports))
|
||||||
sort.Slice(container.Ports, func(i, j int) bool {
|
sort.Slice(ctr.Ports, func(i, j int) bool {
|
||||||
return container.Ports[i].PrivatePort < container.Ports[j].PrivatePort
|
return ctr.Ports[i].PrivatePort < ctr.Ports[j].PrivatePort
|
||||||
})
|
})
|
||||||
for i, p := range container.Ports {
|
for i, p := range ctr.Ports {
|
||||||
publishers[i] = api.PortPublisher{
|
publishers[i] = api.PortPublisher{
|
||||||
URL: p.IP,
|
URL: p.IP,
|
||||||
TargetPort: int(p.PrivatePort),
|
TargetPort: int(p.PrivatePort),
|
||||||
@ -57,22 +58,22 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inspect, err := s.apiClient().ContainerInspect(ctx, container.ID)
|
inspect, err := s.apiClient().ContainerInspect(ctx, ctr.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
health string
|
health container.HealthStatus
|
||||||
exitCode int
|
exitCode int
|
||||||
)
|
)
|
||||||
if inspect.State != nil {
|
if inspect.State != nil {
|
||||||
switch inspect.State.Status {
|
switch inspect.State.Status {
|
||||||
case "running":
|
case container.StateRunning:
|
||||||
if inspect.State.Health != nil {
|
if inspect.State.Health != nil {
|
||||||
health = inspect.State.Health.Status
|
health = inspect.State.Health.Status
|
||||||
}
|
}
|
||||||
case "exited", "dead":
|
case container.StateExited, container.StateDead:
|
||||||
exitCode = inspect.State.ExitCode
|
exitCode = inspect.State.ExitCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
|||||||
local int
|
local int
|
||||||
mounts []string
|
mounts []string
|
||||||
)
|
)
|
||||||
for _, m := range container.Mounts {
|
for _, m := range ctr.Mounts {
|
||||||
name := m.Name
|
name := m.Name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = m.Source
|
name = m.Source
|
||||||
@ -93,26 +94,26 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
|
|||||||
}
|
}
|
||||||
|
|
||||||
var networks []string
|
var networks []string
|
||||||
if container.NetworkSettings != nil {
|
if ctr.NetworkSettings != nil {
|
||||||
for k := range container.NetworkSettings.Networks {
|
for k := range ctr.NetworkSettings.Networks {
|
||||||
networks = append(networks, k)
|
networks = append(networks, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
summary[i] = api.ContainerSummary{
|
summary[i] = api.ContainerSummary{
|
||||||
ID: container.ID,
|
ID: ctr.ID,
|
||||||
Name: getCanonicalContainerName(container),
|
Name: getCanonicalContainerName(ctr),
|
||||||
Names: container.Names,
|
Names: ctr.Names,
|
||||||
Image: container.Image,
|
Image: ctr.Image,
|
||||||
Project: container.Labels[api.ProjectLabel],
|
Project: ctr.Labels[api.ProjectLabel],
|
||||||
Service: container.Labels[api.ServiceLabel],
|
Service: ctr.Labels[api.ServiceLabel],
|
||||||
Command: container.Command,
|
Command: ctr.Command,
|
||||||
State: container.State,
|
State: ctr.State,
|
||||||
Status: container.Status,
|
Status: ctr.Status,
|
||||||
Created: container.Created,
|
Created: ctr.Created,
|
||||||
Labels: container.Labels,
|
Labels: ctr.Labels,
|
||||||
SizeRw: container.SizeRw,
|
SizeRw: ctr.SizeRw,
|
||||||
SizeRootFs: container.SizeRootFs,
|
SizeRootFs: ctr.SizeRootFs,
|
||||||
Mounts: mounts,
|
Mounts: mounts,
|
||||||
LocalVolumes: local,
|
LocalVolumes: local,
|
||||||
Networks: networks,
|
Networks: networks,
|
||||||
|
@ -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", "running", "healthy", 0)
|
c1, inspect1 := containerDetails("service1", "123", containerType.StateRunning, containerType.Healthy, 0)
|
||||||
c2, inspect2 := containerDetails("service1", "456", "running", "", 0)
|
c2, inspect2 := containerDetails("service1", "456", containerType.StateRunning, "", 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", "exited", "", 130)
|
c3, inspect3 := containerDetails("service2", "789", containerType.StateExited, "", 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,7 +56,9 @@ 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: "running", Health: "healthy", Publishers: []compose.PortPublisher{},
|
State: containerType.StateRunning,
|
||||||
|
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",
|
||||||
@ -66,7 +68,8 @@ 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: "running", Health: "",
|
State: containerType.StateRunning,
|
||||||
|
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),
|
||||||
@ -77,7 +80,10 @@ 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: "exited", Health: "", ExitCode: 130, Publishers: []compose.PortPublisher{},
|
State: containerType.StateExited,
|
||||||
|
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",
|
||||||
@ -90,8 +96,8 @@ func TestPs(t *testing.T) {
|
|||||||
assert.DeepEqual(t, containers, expected)
|
assert.DeepEqual(t, containers, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerDetails(service string, id string, status string, health string, exitCode int) (containerType.Summary, containerType.InspectResponse) {
|
func containerDetails(service string, id string, status containerType.ContainerState, health containerType.HealthStatus, exitCode int) (containerType.Summary, containerType.InspectResponse) {
|
||||||
container := containerType.Summary{
|
ctr := containerType.Summary{
|
||||||
ID: id,
|
ID: id,
|
||||||
Names: []string{"/" + id},
|
Names: []string{"/" + id},
|
||||||
Image: "foo",
|
Image: "foo",
|
||||||
@ -107,5 +113,5 @@ func containerDetails(service string, id string, status string, health string, e
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return container, inspect
|
return ctr, inspect
|
||||||
}
|
}
|
||||||
|
@ -236,12 +236,24 @@ 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
|
||||||
@ -325,6 +337,12 @@ 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 {
|
||||||
@ -361,15 +379,20 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) checkForBindMount(project *types.Project) (bool, error) {
|
func (s *composeService) checkForBindMount(project *types.Project) map[string][]types.ServiceVolumeConfig {
|
||||||
for name, config := range project.Services {
|
allFindings := map[string][]types.ServiceVolumeConfig{}
|
||||||
|
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 {
|
||||||
return false, fmt.Errorf("cannot publish compose file: service %q relies on bind-mount. You should use volumes", name)
|
bindMounts = append(bindMounts, volume)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(bindMounts) > 0 {
|
||||||
|
allFindings[serviceName] = bindMounts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return allFindings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) {
|
func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) {
|
||||||
|
@ -34,11 +34,10 @@ 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"
|
||||||
)
|
)
|
||||||
@ -152,7 +151,7 @@ func (s *composeService) pull(ctx context.Context, project *types.Project, opts
|
|||||||
if opts.IgnoreFailures {
|
if opts.IgnoreFailures {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return multierror.Append(nil, pullErrors...).ErrorOrNil()
|
return errors.Join(pullErrors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageAlreadyPresent(serviceImage string, localImages map[string]api.ImageSummary) bool {
|
func imageAlreadyPresent(serviceImage string, localImages map[string]api.ImageSummary) bool {
|
||||||
@ -281,13 +280,7 @@ 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) {
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
authConfig, err := configFile.GetAuthConfig(registry.GetAuthConfigKey(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
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,10 @@ 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"
|
||||||
)
|
)
|
||||||
@ -51,14 +50,6 @@ 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 == "" {
|
||||||
@ -79,7 +70,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, info, s.configFile(), w, options.Quiet)
|
err := s.pushServiceImage(ctx, tag, s.configFile(), w, options.Quiet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !options.IgnoreFailures {
|
if !options.IgnoreFailures {
|
||||||
return err
|
return err
|
||||||
@ -93,22 +84,13 @@ 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, info system.Info, configFile driver.Auth, w progress.Writer, quietPush bool) error {
|
func (s *composeService) pushServiceImage(ctx context.Context, tag string, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
authConfig, err := configFile.GetAuthConfig(registry.GetAuthConfigKey(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
|
||||||
}
|
}
|
||||||
|
@ -123,11 +123,23 @@ 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) {
|
||||||
@ -176,18 +188,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) startDependencies(ctx context.Context, project *types.Project, options api.RunOptions) error {
|
func (s *composeService) startDependencies(ctx context.Context, project *types.Project, options api.RunOptions) error {
|
||||||
var dependencies []string
|
project = project.WithServicesDisabled(options.Service)
|
||||||
for name := range project.Services {
|
|
||||||
if name != options.Service {
|
|
||||||
dependencies = append(dependencies, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project, err := project.WithSelectedServices(dependencies)
|
err := s.Create(ctx, project, api.CreateOptions{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Create(ctx, project, api.CreateOptions{
|
|
||||||
Build: options.Build,
|
Build: options.Build,
|
||||||
IgnoreOrphans: options.IgnoreOrphans,
|
IgnoreOrphans: options.IgnoreOrphans,
|
||||||
RemoveOrphans: options.RemoveOrphans,
|
RemoveOrphans: options.RemoveOrphans,
|
||||||
@ -197,7 +200,7 @@ func (s *composeService) startDependencies(ctx context.Context, project *types.P
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(dependencies) > 0 {
|
if len(project.Services) > 0 {
|
||||||
return s.Start(ctx, project.Name, api.StartOptions{
|
return s.Start(ctx, project.Name, api.StartOptions{
|
||||||
Project: project,
|
Project: project,
|
||||||
})
|
})
|
||||||
|
86
pkg/compose/shellout.go
Normal file
86
pkg/compose/shellout.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
@ -20,19 +20,14 @@ 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 {
|
||||||
@ -56,60 +51,6 @@ 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(
|
||||||
@ -157,7 +98,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return eg.Wait()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDependencyCondition checks if service is depended on by other services
|
// getDependencyCondition checks if service is depended on by other services
|
||||||
@ -173,182 +114,3 @@ 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
|
|
||||||
}
|
|
||||||
|
@ -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)
|
return s.stop(ctx, strings.ToLower(projectName), options, nil)
|
||||||
}, s.stdinfo(), "Stopping")
|
}, s.stdinfo(), "Stopping")
|
||||||
}
|
}
|
||||||
|
|
||||||
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, event api.ContainerEventListener) 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)
|
return s.stopContainers(ctx, w, &serv, containers.filter(isService(service)).filter(isNotOneOff), options.Timeout, event)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,22 +18,25 @@ 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"
|
||||||
cerrdefs "github.com/containerd/errdefs"
|
"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
|
||||||
@ -59,26 +62,20 @@ 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)
|
|
||||||
|
|
||||||
watcher, err := NewWatcher(project, options, s.watch)
|
var (
|
||||||
if err != nil && options.Start.Watch {
|
logConsumer = options.Start.Attach
|
||||||
return err
|
navigationMenu *formatter.LogKeyboard
|
||||||
}
|
kEvents <-chan keyboard.KeyEvent
|
||||||
|
)
|
||||||
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 {
|
||||||
@ -87,31 +84,62 @@ 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, watcher != nil)
|
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive)
|
||||||
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher)
|
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan)
|
||||||
|
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() {
|
||||||
printer.Cancel()
|
|
||||||
_, _ = fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
|
|
||||||
eg.Go(func() error {
|
|
||||||
err := s.Stop(context.WithoutCancel(ctx), project.Name, api.StopOptions{
|
|
||||||
Services: options.Create.Services,
|
|
||||||
Project: project,
|
|
||||||
})
|
|
||||||
isTerminated.Store(true)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
first = false
|
first = false
|
||||||
|
fmt.Println("Gracefully Stopping... press Ctrl+C again to force")
|
||||||
|
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
|
||||||
|
})
|
||||||
|
isTerminated.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-doneCh:
|
case <-globalCtx.Done():
|
||||||
if watcher != nil {
|
if watcher != nil {
|
||||||
return watcher.Stop()
|
return watcher.Stop()
|
||||||
}
|
}
|
||||||
@ -122,64 +150,145 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
|||||||
}
|
}
|
||||||
case <-signalChan:
|
case <-signalChan:
|
||||||
if first {
|
if first {
|
||||||
keyboard.Close() //nolint:errcheck
|
_ = keyboard.Close()
|
||||||
gracefulTeardown()
|
gracefulTeardown()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
err := s.kill(context.WithoutCancel(ctx), project.Name, api.KillOptions{
|
err := s.kill(context.WithoutCancel(globalCtx), 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 cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) {
|
if errdefs.IsNotFound(err) || errdefs.IsConflict(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
appendErr(err)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
case event := <-kEvents:
|
case event := <-kEvents:
|
||||||
navigationMenu.HandleKeyEvents(ctx, event, project, options)
|
navigationMenu.HandleKeyEvents(globalCtx, 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 {
|
||||||
err = watcher.Start(ctx)
|
if err := watcher.Start(globalCtx); err != nil {
|
||||||
if err != nil {
|
// cancel the global context to terminate background goroutines
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal for the signal-handler goroutines to stop
|
_ = eg.Wait()
|
||||||
close(doneCh)
|
err = errors.Join(errs...)
|
||||||
|
|
||||||
printer.Stop()
|
|
||||||
|
|
||||||
err = eg.Wait().ErrorOrNil()
|
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
82
pkg/compose/volumes.go
Normal file
82
pkg/compose/volumes.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
87
pkg/compose/volumes_test.go
Normal file
87
pkg/compose/volumes_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Docker Compose CLI authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package compose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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)
|
||||||
|
}
|
@ -29,14 +29,17 @@ 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"
|
||||||
@ -55,17 +58,16 @@ type Watcher struct {
|
|||||||
errCh chan error
|
errCh chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Watcher, error) {
|
func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc, consumer api.LogConsumer) (*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: options.Start.Attach,
|
LogTo: consumer,
|
||||||
Build: build,
|
Build: build,
|
||||||
},
|
},
|
||||||
watchFn: w,
|
watchFn: w,
|
||||||
@ -192,7 +194,6 @@ 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
|
||||||
@ -234,7 +235,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 synched from watch action
|
// Need to check initial files are in container that are meant to be synced 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
|
||||||
@ -395,7 +396,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 wihtout a command", types.WatchActionSyncExec, service.Name)
|
return nil, fmt.Errorf("can't watch with action %q on service %s without a command", types.WatchActionSyncExec, service.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Watch[i] = trigger
|
config.Watch[i] = trigger
|
||||||
@ -599,6 +600,10 @@ 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
|
||||||
@ -813,7 +818,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 oldest one?
|
// Need to get the 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
|
||||||
|
@ -71,9 +71,6 @@ 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)
|
||||||
|
@ -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", "DOCKER_BUILDKIT=1,COMPOSE-BAKE=1"} {
|
for _, env := range []string{"DOCKER_BUILDKIT=0", "DOCKER_BUILDKIT=1,COMPOSE_BAKE=0", "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) {
|
||||||
@ -580,3 +580,31 @@ func TestBuildSubDependencies(t *testing.T) {
|
|||||||
out = res.Combined()
|
out = res.Combined()
|
||||||
assert.Check(t, strings.Contains(out, "main Built"))
|
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"))
|
||||||
|
}
|
||||||
|
@ -196,4 +196,30 @@ 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"})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,11 @@ 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) {
|
||||||
|
47
pkg/e2e/exec_test.go
Normal file
47
pkg/e2e/exec_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
@ -3,6 +3,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
metadata:
|
metadata:
|
||||||
name: bridge
|
name: {{ .Values.namespace }}
|
||||||
labels:
|
labels:
|
||||||
com.docker.compose.project: bridge
|
com.docker.compose.project: bridge
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: bridge
|
name: {{ .Values.projectName }}
|
||||||
namespace: bridge
|
namespace: {{ .Values.namespace }}
|
||||||
labels:
|
labels:
|
||||||
com.docker.compose.project: bridge
|
com.docker.compose.project: bridge
|
||||||
data:
|
data:
|
||||||
|
@ -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: 1
|
replicas: {{ .Values.deployment.defaultReplicas }}
|
||||||
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: Recreate
|
type: {{ .Values.deployment.strategy }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@ -28,6 +28,10 @@ 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
|
||||||
@ -39,7 +43,7 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: etc-my-config1-txt
|
- name: etc-my-config1-txt
|
||||||
configMap:
|
configMap:
|
||||||
name: bridge
|
name: {{ .Values.projectName }}
|
||||||
items:
|
items:
|
||||||
- key: my-config
|
- key: my-config
|
||||||
path: my-config
|
path: my-config
|
||||||
|
@ -12,7 +12,7 @@ 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:
|
||||||
type: LoadBalancer
|
type: {{ .Values.service.type }}
|
||||||
selector:
|
selector:
|
||||||
com.docker.compose.project: bridge
|
com.docker.compose.project: bridge
|
||||||
com.docker.compose.service: serviceA
|
com.docker.compose.service: serviceA
|
||||||
|
@ -10,13 +10,13 @@ metadata:
|
|||||||
com.docker.compose.service: serviceB
|
com.docker.compose.service: serviceB
|
||||||
app.kubernetes.io/managed-by: Helm
|
app.kubernetes.io/managed-by: Helm
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: {{ .Values.deployment.defaultReplicas }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
com.docker.compose.project: bridge
|
com.docker.compose.project: bridge
|
||||||
com.docker.compose.service: serviceB
|
com.docker.compose.service: serviceB
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: {{ .Values.deployment.strategy }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@ -29,6 +29,10 @@ spec:
|
|||||||
- name: serviceb
|
- name: serviceb
|
||||||
image: {{ .Values.serviceB.image }}
|
image: {{ .Values.serviceB.image }}
|
||||||
imagePullPolicy: {{ .Values.serviceB.imagePullPolicy }}
|
imagePullPolicy: {{ .Values.serviceB.imagePullPolicy }}
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: {{ .Values.resources.defaultCpuLimit }}
|
||||||
|
memory: {{ .Values.resources.defaultMemoryLimit }}
|
||||||
ports:
|
ports:
|
||||||
- name: serviceb-8082
|
- name: serviceb-8082
|
||||||
containerPort: 8082
|
containerPort: 8082
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user