mirror of
https://github.com/docker/compose.git
synced 2025-04-08 17:05:13 +02:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ee33143026 | ||
|
cb0b5f6e27 | ||
|
1384853538 | ||
|
096b1e32d3 | ||
|
bf71138df6 | ||
|
a1f673dcf5 | ||
|
02c747a7de | ||
|
88f4f265db | ||
|
e67348222f | ||
|
b543380708 | ||
|
2e75185a07 | ||
|
7bedb5a02c | ||
|
f9cd4d0b1d | ||
|
0badcf3c8d | ||
|
ec49db98d4 | ||
|
e5a353b34d | ||
|
43e456145c | ||
|
75368c7859 | ||
|
6e814eac35 | ||
|
a0d1c3f944 | ||
|
0c5bd16da1 | ||
|
b0badf1eb0 | ||
|
342a2a9e71 | ||
|
7814e5798c | ||
|
42b2e11094 | ||
|
6a8c0988cf | ||
|
9129abe516 | ||
|
f38f3f754c | ||
|
ea07ba8e2a | ||
|
432ae23b0e |
.golangci.ymlDockerfile
cmd
cmdtrace
compose
build.gobuild_test.gocompletion.gocompose.goconfig.goexec.gooptions.gorun.goscale.gotop_test.gowatch.go
formatter
docs/reference
compose.mdcompose_build.mdcompose_config.mdcompose_run.mdcompose_watch.mddocker_compose.yamldocker_compose_build.yamldocker_compose_config.yamldocker_compose_run.yamldocker_compose_watch.yaml
go.modgo.sumpkg
api
compose
build.gobuild_bake.goconvergence.gocreate.godown.gols.gols_test.goplugins.gopull.gorestart.gorun.gosecrets.gowatch.go
e2e
remote
watch
116
.golangci.yml
116
.golangci.yml
@ -1,9 +1,8 @@
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
linters:
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- copyloopvar
|
||||
- depguard
|
||||
@ -11,73 +10,74 @@ linters:
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: 'io/ioutil package has been deprecated'
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: 'compose-go uses yaml.v3'
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: "use distribution/reference"
|
||||
- gotest.tools:
|
||||
version: "< 3.0.0"
|
||||
reason: "deprecated, pre-modules version"
|
||||
gocritic:
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
lll:
|
||||
line-length: 200
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: io/ioutil package has been deprecated
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: compose-go uses yaml.v3
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: use distribution/reference
|
||||
- gotest.tools:
|
||||
version: < 3.0.0
|
||||
reason: deprecated, pre-modules version
|
||||
lll:
|
||||
line-length: 200
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
# golangci hides some golint warnings (the warning about exported things
|
||||
# without documentation for example), this will make it show them anyway.
|
||||
exclude-use-default: false
|
||||
# Maximum issues count per one linter.
|
||||
# Set to 0 to disable.
|
||||
# Default: 50
|
||||
max-issues-per-linter: 0
|
||||
# Maximum count of issues with the same text.
|
||||
# Set to 0 to disable.
|
||||
# Default: 3
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
@ -15,9 +15,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.23.6
|
||||
ARG GO_VERSION=1.23.8
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.63.4
|
||||
ARG GOLANGCI_LINT_VERSION=v2.0.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
ARG BUILD_TAGS="e2e"
|
||||
|
@ -115,13 +115,14 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
|
||||
}
|
||||
}
|
||||
|
||||
// commandName returns the path components for a given command.
|
||||
// commandName returns the path components for a given command,
|
||||
// in reverse alphabetical order for consistent usage metrics.
|
||||
//
|
||||
// The root Compose command and anything before (i.e. "docker")
|
||||
// are not included.
|
||||
//
|
||||
// For example:
|
||||
// - docker compose alpha watch -> [alpha, watch]
|
||||
// - docker compose alpha watch -> [watch, alpha]
|
||||
// - docker-compose up -> [up]
|
||||
func commandName(cmd *cobra.Command) []string {
|
||||
var name []string
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@ -61,3 +63,50 @@ func TestGetFlags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupCmd func() *cobra.Command
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "docker compose alpha watch -> [watch, alpha]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerCmd := &cobra.Command{Use: "docker"}
|
||||
composeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
alphaCmd := &cobra.Command{Use: "alpha"}
|
||||
watchCmd := &cobra.Command{Use: "watch"}
|
||||
|
||||
dockerCmd.AddCommand(composeCmd)
|
||||
composeCmd.AddCommand(alphaCmd)
|
||||
alphaCmd.AddCommand(watchCmd)
|
||||
|
||||
return watchCmd
|
||||
},
|
||||
want: []string{"watch", "alpha"},
|
||||
},
|
||||
{
|
||||
name: "docker-compose up -> [up]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerComposeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
upCmd := &cobra.Command{Use: "up"}
|
||||
|
||||
dockerComposeCmd.AddCommand(upCmd)
|
||||
|
||||
return upCmd
|
||||
},
|
||||
want: []string{"up"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := tt.setupCmd()
|
||||
got := commandName(cmd)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("commandName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@ -44,6 +45,7 @@ type buildOptions struct {
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
print bool
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
@ -76,6 +78,8 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
Memory: int64(opts.memory),
|
||||
Print: opts.print,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
}, nil
|
||||
@ -131,6 +135,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -141,6 +146,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
|
||||
return err
|
||||
}
|
||||
|
||||
services = addBuildDependencies(services, project)
|
||||
|
||||
if err := applyPlatforms(project, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,6 +157,23 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions.Memory = int64(opts.memory)
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
||||
func addBuildDependencies(services []string, project *types.Project) []string {
|
||||
servicesWithDependencies := utils.NewSet(services...)
|
||||
for _, service := range services {
|
||||
build := project.Services[service].Build
|
||||
if build != nil {
|
||||
for _, target := range build.AdditionalContexts {
|
||||
if s, found := strings.CutPrefix(target, types.ServicePrefix); found {
|
||||
servicesWithDependencies.Add(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(servicesWithDependencies) > len(services) {
|
||||
return addBuildDependencies(servicesWithDependencies.Elements(), project)
|
||||
}
|
||||
return servicesWithDependencies.Elements()
|
||||
}
|
||||
|
57
cmd/compose/build_test.go
Normal file
57
cmd/compose/build_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func Test_addBuildDependencies(t *testing.T) {
|
||||
project := &types.Project{Services: types.Services{
|
||||
"test": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"foo": "service:foo",
|
||||
"bar": "service:bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{
|
||||
AdditionalContexts: map[string]string{
|
||||
"zot": "service:zot",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
},
|
||||
"zot": types.ServiceConfig{
|
||||
Build: &types.BuildConfig{},
|
||||
},
|
||||
}}
|
||||
|
||||
services := addBuildDependencies([]string{"test"}, project)
|
||||
expected := []string{"test", "foo", "bar", "zot"}
|
||||
slices.Sort(services)
|
||||
slices.Sort(expected)
|
||||
assert.DeepEqual(t, services, expected)
|
||||
}
|
@ -90,3 +90,13 @@ func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeScaleArgs(cli command.Cli, p *ProjectOptions) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
completions, directive := completeServiceNames(cli, p)(cmd, args, toComplete)
|
||||
for i, completion := range completions {
|
||||
completions[i] = completion + "="
|
||||
}
|
||||
return completions, directive
|
||||
}
|
||||
}
|
||||
|
@ -72,17 +72,16 @@ const (
|
||||
)
|
||||
|
||||
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
|
||||
func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
|
||||
func rawEnv(r io.Reader, filename string, vars map[string]string, lookup func(key string) (string, bool)) error {
|
||||
lines, err := kvfile.ParseFromReader(r, lookup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
return fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
}
|
||||
vars := types.Mapping{}
|
||||
for _, line := range lines {
|
||||
key, value, _ := strings.Cut(line, "=")
|
||||
vars[key] = value
|
||||
}
|
||||
return vars, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -383,11 +382,18 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
// First apply os.Environment, always win
|
||||
cli.WithOsEnv,
|
||||
// set PWD as this variable is not consistently supported on Windows
|
||||
cli.WithEnv([]string{"PWD=" + pwd}),
|
||||
// Load PWD/.env if present and no explicit --env-file has been set
|
||||
cli.WithEnvFiles(o.EnvFiles...),
|
||||
// read dot env file to populate project environment
|
||||
@ -543,10 +549,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
composeCmd := cmd
|
||||
for {
|
||||
if composeCmd.Name() == PluginName {
|
||||
break
|
||||
}
|
||||
for composeCmd.Name() != PluginName {
|
||||
if !composeCmd.HasParent() {
|
||||
return fmt.Errorf("error parsing command line, expected %q", PluginName)
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ type configOptions struct {
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
noResolvePath bool
|
||||
noResolveEnv bool
|
||||
services bool
|
||||
volumes bool
|
||||
profiles bool
|
||||
@ -135,6 +136,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model")
|
||||
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths")
|
||||
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
|
||||
flags.BoolVar(&opts.noResolveEnv, "no-env-resolution", false, "Don't resolve service env files")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
@ -190,6 +192,13 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noResolveEnv {
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noConsistency {
|
||||
err := project.CheckContainerNameUnicity()
|
||||
if err != nil {
|
||||
|
@ -86,7 +86,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions()
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions() //nolint:staticcheck
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
|
||||
}
|
||||
|
||||
func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
|
||||
mainComposeFile := options.ProjectOptions.ConfigPaths[0]
|
||||
mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
|
||||
if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
|
||||
}
|
||||
@ -258,8 +258,8 @@ func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYe
|
||||
}
|
||||
|
||||
var remoteIncludes []string
|
||||
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli)
|
||||
for _, cf := range options.ProjectOptions.ConfigPaths {
|
||||
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) //nolint:staticcheck
|
||||
for _, cf := range options.ProjectOptions.ConfigPaths { //nolint:staticcheck
|
||||
for _, loader := range remoteLoaders {
|
||||
if loader.Accept(cf) {
|
||||
remoteIncludes = append(remoteIncludes, cf)
|
||||
|
@ -67,6 +67,7 @@ type runOptions struct {
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
removeOrphans bool
|
||||
quiet bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
@ -180,11 +181,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
}
|
||||
if options.quiet {
|
||||
progress.Mode = progress.ModeQuiet
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
createOpts.pullChanged = cmd.Flags().Changed("pull")
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -217,6 +231,8 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to")
|
||||
flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host")
|
||||
flags.StringVar(&createOpts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVar(&buildOpts.quiet, "quiet-build", false, "Suppress progress output from the build process")
|
||||
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
|
||||
flags.BoolVar(&options.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
|
@ -51,7 +51,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: completeScaleArgs(dockerCli, p),
|
||||
}
|
||||
flags := scaleCmd.Flags()
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")
|
||||
|
@ -220,8 +220,8 @@ func TestRunTopCore(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
header, entries := collectTop([]api.ContainerProcSummary{summary})
|
||||
assert.EqualValues(t, tc.header, header)
|
||||
assert.EqualValues(t, tc.entries, entries)
|
||||
assert.Equal(t, tc.header, header)
|
||||
assert.Equal(t, tc.entries, entries)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := topPrint(&buf, header, entries)
|
||||
@ -233,7 +233,7 @@ func TestRunTopCore(t *testing.T) {
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
header, entries := collectTop(all)
|
||||
assert.EqualValues(t, topHeader{
|
||||
assert.Equal(t, topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
@ -246,7 +246,7 @@ func TestRunTopCore(t *testing.T) {
|
||||
"GID": 9,
|
||||
"CMD": 10,
|
||||
}, header)
|
||||
assert.EqualValues(t, []topEntries{
|
||||
assert.Equal(t, []topEntries{
|
||||
{
|
||||
"SERVICE": "simple",
|
||||
"#": "1",
|
||||
|
@ -59,7 +59,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
|
||||
cmd.Flags().BoolVar(&watchOpts.prune, "prune", false, "Prune dangling images on rebuild")
|
||||
cmd.Flags().BoolVar(&watchOpts.prune, "prune", true, "Prune dangling images on rebuild")
|
||||
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
|
||||
return cmd
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop")
|
||||
err = fmt.Errorf("could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
@ -255,7 +255,7 @@ func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Proje
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
@ -269,7 +269,7 @@ func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Proje
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
err = fmt.Errorf("could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
@ -299,7 +299,7 @@ func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
if options.Create.Build == nil {
|
||||
err := fmt.Errorf("Cannot run watch mode with flag --no-build")
|
||||
err := fmt.Errorf("cannot run watch mode with flag --no-build")
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ to their predecessors.
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -92,7 +92,7 @@ services:
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
@ -207,4 +207,4 @@ $ docker compose --dry-run up --build -d
|
||||
From the example above, you can see that the first step is to pull the image defined by `db` service, then build the `backend` service.
|
||||
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
|
||||
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
|
@ -20,6 +20,7 @@ run `docker compose build` to rebuild it.
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||
| `--print` | `bool` | | Print equivalent bake file |
|
||||
| `--pull` | `bool` | | Always attempt to pull a newer version of the image |
|
||||
| `--push` | `bool` | | Push service images |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
|
@ -19,6 +19,7 @@ the canonical format.
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | `bool` | | Print the image names, one per line. |
|
||||
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-env-resolution` | `bool` | | Don't resolve service env files |
|
||||
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
|
||||
| `--no-normalize` | `bool` | | Don't normalize compose model |
|
||||
| `--no-path-resolution` | `bool` | | Don't resolve file paths |
|
||||
|
@ -74,6 +74,8 @@ specified in the service configuration.
|
||||
| `--no-deps` | `bool` | | Don't start linked services |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
| `--quiet-build` | `bool` | | Suppress progress output from the build process |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--rm` | `bool` | | Automatically remove the container when it exits |
|
||||
|
@ -9,7 +9,7 @@ Watch build context for service and rebuild/refresh containers when files are up
|
||||
|:------------|:-------|:--------|:----------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--no-up` | `bool` | | Do not build & start services before watching |
|
||||
| `--prune` | `bool` | | Prune dangling images on rebuild |
|
||||
| `--prune` | `bool` | `true` | Prune dangling images on rebuild |
|
||||
| `--quiet` | `bool` | | hide build output |
|
||||
|
||||
|
||||
|
@ -241,10 +241,10 @@ examples: |-
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -255,7 +255,7 @@ examples: |-
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
|
@ -96,6 +96,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: print
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Print equivalent bake file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
|
@ -59,6 +59,16 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-env-resolution
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Don't resolve service env files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-interpolate
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
@ -200,6 +200,27 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet
|
||||
shorthand: q
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Don't print anything to STDOUT
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-build
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Suppress progress output from the build process
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: quiet-pull
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
|
@ -19,7 +19,7 @@ options:
|
||||
swarm: false
|
||||
- option: prune
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
default_value: "true"
|
||||
description: Prune dangling images on rebuild
|
||||
deprecated: false
|
||||
hidden: false
|
||||
|
26
go.mod
26
go.mod
@ -1,22 +1,22 @@
|
||||
module github.com/docker/compose/v2
|
||||
|
||||
go 1.23.6
|
||||
go 1.23.8
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e
|
||||
github.com/Microsoft/go-winio v0.6.2
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/compose-spec/compose-go/v2 v2.4.9
|
||||
github.com/containerd/containerd/v2 v2.0.3
|
||||
github.com/compose-spec/compose-go/v2 v2.5.0
|
||||
github.com/containerd/containerd/v2 v2.0.4
|
||||
github.com/containerd/platforms v1.0.0-rc.1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/buildx v0.21.2
|
||||
github.com/docker/cli v28.0.1+incompatible
|
||||
github.com/docker/buildx v0.22.0
|
||||
github.com/docker/cli v28.0.4+incompatible
|
||||
github.com/docker/cli-docs-tool v0.9.0
|
||||
github.com/docker/docker v28.0.1+incompatible
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
||||
@ -53,12 +53,12 @@ require (
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
|
||||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/sys v0.32.0
|
||||
google.golang.org/grpc v1.71.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
tags.cncf.io/container-device-interface v1.0.0
|
||||
tags.cncf.io/container-device-interface v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@ -108,7 +108,7 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
@ -193,7 +193,7 @@ require (
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.31.2 // indirect
|
||||
k8s.io/apimachinery v0.31.2 // indirect
|
||||
@ -205,3 +205,5 @@ require (
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/compose-spec/compose-go/v2 => github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535
|
||||
|
44
go.sum
44
go.sum
@ -10,8 +10,8 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f h1:RTbUqLhPxejgK92ifVdMTIW9H23QLlscy8QXPDTfaL4=
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250108223530-c2b44d4c1f8f/go.mod h1:2UjtD/G/Sy2FxoHpxKnzHTXMpRURecwYal8HgbxcvkY=
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0=
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
@ -83,16 +83,14 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/compose-spec/compose-go/v2 v2.4.9 h1:2K4TDw+1ba2idiR6empXHKRXvWYpnvAKoNQy93/sSOs=
|
||||
github.com/compose-spec/compose-go/v2 v2.4.9/go.mod h1:6k5l/0TxCg0/2uLEhRVEsoBWBprS2uvZi32J7xub3lo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
|
||||
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
|
||||
github.com/containerd/containerd/v2 v2.0.3 h1:zBKgwgZsuu+LPCMzCLgA4sC4MiZzZ59ZT31XkmiISQM=
|
||||
github.com/containerd/containerd/v2 v2.0.3/go.mod h1:5j9QUUaV/cy9ZeAx4S+8n9ffpf+iYnEj4jiExgcbuLY=
|
||||
github.com/containerd/containerd/v2 v2.0.4 h1:+r7yJMwhTfMm3CDyiBjMBQO8a9CTBxL2Bg/JtqtIwB8=
|
||||
github.com/containerd/containerd/v2 v2.0.4/go.mod h1:5j9QUUaV/cy9ZeAx4S+8n9ffpf+iYnEj4jiExgcbuLY=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
@ -129,17 +127,17 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/buildx v0.21.2 h1:r09paH8q9nvAX2PR1ntRrc+C6FBH93bvKUsn1WOb/jU=
|
||||
github.com/docker/buildx v0.21.2/go.mod h1:8V4UMnlKsaGYwz83BygmIbJIFEAYGHT6KAv8akDZmqo=
|
||||
github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs=
|
||||
github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/buildx v0.22.0 h1:pGTcGZa+kxpYUlM/6ACsp1hXhkEDulz++RNXPdE8Afk=
|
||||
github.com/docker/buildx v0.22.0/go.mod h1:ThbnUe4kNiStlq6cLXruElyEdSTdPL3k/QerNUmPvHE=
|
||||
github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A=
|
||||
github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.9.0 h1:CVwQbE+ZziwlPqrJ7LRyUF6GvCA+6gj7MTCsayaK9t0=
|
||||
github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3Tr1ipoypjAUtc=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
|
||||
github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
@ -169,6 +167,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
|
||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535 h1:S/P6v3QxsMpkKn+2OSMPNkfSkadSjSHoMGAc/eBZgMU=
|
||||
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
@ -197,8 +197,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -576,8 +576,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -599,8 +599,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -648,8 +648,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
@ -682,5 +682,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
tags.cncf.io/container-device-interface v1.0.0 h1:fbwPQiWZNpXUb9Os6t6JW52rsOppTFUbeJOpNtN1TmI=
|
||||
tags.cncf.io/container-device-interface v1.0.0/go.mod h1:mmi2aRGmOjK/6NR3TXjLpEIarOJ9qwgZjQ3nTIRwAaA=
|
||||
tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc=
|
||||
tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0=
|
||||
|
@ -155,6 +155,8 @@ type BuildOptions struct {
|
||||
Memory int64
|
||||
// Builder name passed in the command line
|
||||
Builder string
|
||||
// Print don't actually run builder but print equivalent build config
|
||||
Print bool
|
||||
}
|
||||
|
||||
// Apply mutates project according to build options
|
||||
|
@ -105,7 +105,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bake {
|
||||
if bake || options.Print {
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "bake"))
|
||||
return s.doBuildBake(ctx, project, serviceToBuild, options)
|
||||
}
|
||||
@ -263,7 +263,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
||||
|
||||
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
|
||||
for name, service := range project.Services {
|
||||
if service.Image == "" && service.Build == nil {
|
||||
if service.Provider == nil && service.Image == "" && service.Build == nil {
|
||||
return fmt.Errorf("invalid service %q. Must specify either image or build", name)
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
Context: build.Context,
|
||||
Contexts: additionalContexts(build.AdditionalContexts),
|
||||
Dockerfile: dockerFilePath(build.Context, build.Dockerfile),
|
||||
DockerfileInline: build.DockerfileInline,
|
||||
DockerfileInline: strings.ReplaceAll(build.DockerfileInline, "${", "$${"),
|
||||
Args: args,
|
||||
Labels: build.Labels,
|
||||
Tags: append(build.Tags, image),
|
||||
@ -219,6 +219,10 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.Print {
|
||||
_, err = fmt.Fprintln(s.stdinfo(), string(b))
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("bake build config:\n%s", string(b))
|
||||
|
||||
metadata, err := os.CreateTemp(os.TempDir(), "compose")
|
||||
|
@ -47,7 +47,7 @@ import (
|
||||
const (
|
||||
doubledContainerNameWarning = "WARNING: The %q service is using the custom container name %q. " +
|
||||
"Docker requires each container to have a unique name. " +
|
||||
"Remove the custom name to scale the service.\n"
|
||||
"Remove the custom name to scale the service"
|
||||
)
|
||||
|
||||
// convergence manages service's container lifecycle.
|
||||
@ -110,6 +110,9 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
|
||||
}
|
||||
|
||||
func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { //nolint:gocyclo
|
||||
if service.Provider != nil {
|
||||
return c.service.runPlugin(ctx, project, service, "up")
|
||||
}
|
||||
expected, err := getScale(service)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -225,7 +228,9 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
|
||||
|
||||
func (c *convergence) stopDependentContainers(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||
// Stop dependent containers, so they will be restarted after service is re-created
|
||||
dependents := project.GetDependentsForService(service)
|
||||
dependents := project.GetDependentsForService(service, func(dependency types.ServiceDependency) bool {
|
||||
return dependency.Restart
|
||||
})
|
||||
if len(dependents) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool,
|
||||
if strings.Contains(opt, ":") {
|
||||
con = strings.SplitN(opt, ":", 2)
|
||||
} else {
|
||||
return securityOpts, false, fmt.Errorf("Invalid security-opt: %q", opt)
|
||||
return securityOpts, false, fmt.Errorf("invalid security-opt: %q", opt)
|
||||
}
|
||||
}
|
||||
if con[0] == "seccomp" && con[1] != "unconfined" && con[1] != "builtin" {
|
||||
@ -997,10 +997,10 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
}
|
||||
|
||||
if definedConfig.Driver != "" {
|
||||
return nil, errors.New("Docker Compose does not support configs.*.driver")
|
||||
return nil, errors.New("Docker Compose does not support configs.*.driver") //nolint:staticcheck
|
||||
}
|
||||
if definedConfig.TemplateDriver != "" {
|
||||
return nil, errors.New("Docker Compose does not support configs.*.template_driver")
|
||||
return nil, errors.New("Docker Compose does not support configs.*.template_driver") //nolint:staticcheck
|
||||
}
|
||||
|
||||
if definedConfig.Environment != "" || definedConfig.Content != "" {
|
||||
@ -1047,10 +1047,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
|
||||
}
|
||||
|
||||
if definedSecret.Driver != "" {
|
||||
return nil, errors.New("Docker Compose does not support secrets.*.driver")
|
||||
return nil, errors.New("Docker Compose does not support secrets.*.driver") //nolint:staticcheck
|
||||
}
|
||||
if definedSecret.TemplateDriver != "" {
|
||||
return nil, errors.New("Docker Compose does not support secrets.*.template_driver")
|
||||
return nil, errors.New("Docker Compose does not support secrets.*.template_driver") //nolint:staticcheck
|
||||
}
|
||||
|
||||
if definedSecret.Environment != "" {
|
||||
|
@ -83,8 +83,11 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
|
||||
}
|
||||
|
||||
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
serviceContainers := containers.filter(isService(service))
|
||||
serv := project.Services[service]
|
||||
if serv.Provider != nil {
|
||||
return s.runPlugin(ctx, project, serv, "down")
|
||||
}
|
||||
serviceContainers := containers.filter(isService(service))
|
||||
err := s.removeContainers(ctx, serviceContainers, &serv, options.Timeout, options.Volumes)
|
||||
return err
|
||||
}, WithRootNodesAndDown(options.Services))
|
||||
|
@ -70,7 +70,7 @@ func combinedConfigFiles(containers []container.Summary) (string, error) {
|
||||
for _, c := range containers {
|
||||
files, ok := c.Labels[api.ConfigFilesLabel]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
|
||||
return "", fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
|
||||
}
|
||||
|
||||
for _, f := range strings.Split(files, ",") {
|
||||
@ -120,7 +120,7 @@ func groupContainerByLabel(containers []container.Summary, labelName string) (ma
|
||||
for _, c := range containers {
|
||||
label, ok := c.Labels[labelName]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
|
||||
return nil, nil, fmt.Errorf("no label %q set on container %q of compose project", labelName, c.ID)
|
||||
}
|
||||
labelContainers, ok := containersByLabel[label]
|
||||
if !ok {
|
||||
|
@ -104,7 +104,7 @@ func TestCombinedConfigFiles(t *testing.T) {
|
||||
}{
|
||||
"project1": {ConfigFiles: "/home/docker-compose.yaml", Error: nil},
|
||||
"project2": {ConfigFiles: "/home/project2-docker-compose.yaml", Error: nil},
|
||||
"project3": {ConfigFiles: "", Error: fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
|
||||
"project3": {ConfigFiles: "", Error: fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
|
||||
}
|
||||
|
||||
for project, containers := range containersByLabel {
|
||||
|
179
pkg/compose/plugins.go
Normal file
179
pkg/compose/plugins.go
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/socket"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type JsonMessage struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
const (
|
||||
ErrorType = "error"
|
||||
InfoType = "info"
|
||||
SetEnvType = "setenv"
|
||||
)
|
||||
|
||||
func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error {
|
||||
provider := *service.Provider
|
||||
|
||||
plugin, err := s.getPluginBinaryPath(provider.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.checkPluginEnabledInDD(ctx, plugin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := s.setupPluginCommand(ctx, project, provider, plugin.Path, command)
|
||||
|
||||
eg := errgroup.Group{}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eg.Go(cmd.Wait)
|
||||
|
||||
decoder := json.NewDecoder(stdout)
|
||||
defer func() { _ = stdout.Close() }()
|
||||
|
||||
variables := types.Mapping{}
|
||||
|
||||
pw := progress.ContextWriter(ctx)
|
||||
pw.Event(progress.CreatingEvent(service.Name))
|
||||
for {
|
||||
var msg JsonMessage
|
||||
err = decoder.Decode(&msg)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch msg.Type {
|
||||
case ErrorType:
|
||||
pw.Event(progress.ErrorMessageEvent(service.Name, "error"))
|
||||
return errors.New(msg.Message)
|
||||
case InfoType:
|
||||
pw.Event(progress.ErrorMessageEvent(service.Name, msg.Message))
|
||||
case SetEnvType:
|
||||
key, val, found := strings.Cut(msg.Message, "=")
|
||||
if !found {
|
||||
return fmt.Errorf("invalid response from plugin: %s", msg.Message)
|
||||
}
|
||||
variables[key] = val
|
||||
default:
|
||||
return fmt.Errorf("invalid response from plugin: %s", msg.Type)
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
pw.Event(progress.ErrorMessageEvent(service.Name, err.Error()))
|
||||
return fmt.Errorf("failed to create external service: %s", err.Error())
|
||||
}
|
||||
pw.Event(progress.CreatedEvent(service.Name))
|
||||
|
||||
prefix := strings.ToUpper(service.Name) + "_"
|
||||
for name, s := range project.Services {
|
||||
if _, ok := s.DependsOn[service.Name]; ok {
|
||||
for key, val := range variables {
|
||||
s.Environment[prefix+key] = &val
|
||||
}
|
||||
project.Services[name] = s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *composeService) getPluginBinaryPath(providerType string) (*manager.Plugin, error) {
|
||||
// Only support Docker CLI plugins for first iteration. Could support any binary from PATH
|
||||
return manager.GetPlugin(providerType, s.dockerCli, &cobra.Command{})
|
||||
}
|
||||
|
||||
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, provider types.ServiceProviderConfig, path, command string) *exec.Cmd {
|
||||
args := []string{"compose", "--project-name", project.Name, command}
|
||||
for k, v := range provider.Options {
|
||||
args = append(args, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, path, args...)
|
||||
// Remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone
|
||||
cmd.Env = filter(os.Environ(), manager.ReexecEnvvar)
|
||||
|
||||
// Use docker/cli mechanism to propagate termination signal to child process
|
||||
server, err := socket.NewPluginServer(nil)
|
||||
if err == nil {
|
||||
defer server.Close() //nolint:errcheck
|
||||
cmd.Cancel = server.Close
|
||||
cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String())
|
||||
}
|
||||
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()))
|
||||
|
||||
// propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md
|
||||
carrier := propagation.MapCarrier{}
|
||||
otel.GetTextMapPropagator().Inject(ctx, &carrier)
|
||||
cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (s *composeService) checkPluginEnabledInDD(ctx context.Context, plugin *manager.Plugin) error {
|
||||
if integrationEnabled := s.isDesktopIntegrationActive(); !integrationEnabled {
|
||||
return fmt.Errorf("you should enable Docker Desktop integration to use %q provider services", plugin.Name)
|
||||
}
|
||||
|
||||
// Until we support more use cases, check explicitly status of model runner
|
||||
if plugin.Name == "model" {
|
||||
cmd := exec.CommandContext(ctx, "docker", "model", "status")
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
|
||||
return fmt.Errorf("you should enable model runner to use %q provider services: %s", plugin.Name, err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unsupported provider %q", plugin.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -335,6 +335,9 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
||||
}
|
||||
|
||||
func mustPull(service types.ServiceConfig, images map[string]api.ImageSummary) (bool, error) {
|
||||
if service.Provider != nil {
|
||||
return false, nil
|
||||
}
|
||||
if service.Image == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -77,13 +77,19 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
return InDependencyOrder(ctx, project, func(c context.Context, service string) error {
|
||||
config := project.Services[service]
|
||||
err = s.waitDependencies(ctx, project, service, config.DependsOn, containers, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, ctr := range containers.filter(isService(service)) {
|
||||
eg.Go(func() error {
|
||||
eventName := getContainerProgressName(ctr)
|
||||
w.Event(progress.RestartingEvent(eventName))
|
||||
timeout := utils.DurationSecondToInt(options.Timeout)
|
||||
err := s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
|
||||
err = s.apiClient().ContainerRestart(ctx, ctr.ID, container.StopOptions{Timeout: timeout})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -151,6 +151,9 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
||||
v, ok := envResolver(project.Environment)(s)
|
||||
return v, ok
|
||||
}).RemoveEmpty()
|
||||
if service.Environment == nil {
|
||||
service.Environment = types.MappingWithEquals{}
|
||||
}
|
||||
service.Environment.OverrideBy(serviceOverrideEnv)
|
||||
}
|
||||
for k, v := range opts.Labels {
|
||||
|
@ -45,11 +45,15 @@ func (s *composeService) injectSecrets(ctx context.Context, project *types.Proje
|
||||
config.Target = "/run/secrets/" + config.Target
|
||||
}
|
||||
|
||||
env, ok := project.Environment[file.Environment]
|
||||
if !ok {
|
||||
return fmt.Errorf("environment variable %q required by secret %q is not set", file.Environment, file.Name)
|
||||
content := file.Content
|
||||
if content == "" {
|
||||
env, ok := project.Environment[file.Environment]
|
||||
if !ok {
|
||||
return fmt.Errorf("environment variable %q required by secret %q is not set", file.Environment, file.Name)
|
||||
}
|
||||
content = env
|
||||
}
|
||||
b, err := createTar(env, types.FileReferenceConfig(config))
|
||||
b, err := createTar(content, types.FileReferenceConfig(config))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -106,7 +110,7 @@ func createTar(env string, config types.FileReferenceConfig) (bytes.Buffer, erro
|
||||
value := []byte(env)
|
||||
b := bytes.Buffer{}
|
||||
tarWriter := tar.NewWriter(&b)
|
||||
mode := types.FileMode(0o440)
|
||||
mode := types.FileMode(0o444)
|
||||
if config.Mode != nil {
|
||||
mode = *config.Mode
|
||||
}
|
||||
|
@ -353,8 +353,11 @@ func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project)
|
||||
|
||||
func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool {
|
||||
for _, volume := range volumes {
|
||||
if volume.Bind != nil && strings.HasPrefix(watchPath, volume.Source) {
|
||||
return true
|
||||
if volume.Bind != nil {
|
||||
relPath, err := filepath.Rel(volume.Source, watchPath)
|
||||
if err == nil && !strings.HasPrefix(relPath, "..") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -751,7 +754,7 @@ func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Pr
|
||||
return time.Now(), err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return time.Now(), fmt.Errorf("Could not get created time for service's image")
|
||||
return time.Now(), fmt.Errorf("could not get created time for service's image")
|
||||
}
|
||||
|
||||
img, err := s.apiClient().ImageInspect(ctx, containers[0].ImageID)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestRawEnvFile(t *testing.T) {
|
||||
@ -37,3 +38,11 @@ func TestUnusedMissingEnvFile(t *testing.T) {
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/env_file/compose.yaml", "up", "-d", "serviceA")
|
||||
}
|
||||
|
||||
func TestRunEnvFile(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
defer c.cleanupWithDown(t, "run_dotenv")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "./fixtures/env_file", "run", "serviceC", "env")
|
||||
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
|
||||
}
|
||||
|
10
pkg/e2e/fixtures/env-secret/child/compose.yaml
Normal file
10
pkg/e2e/fixtures/env-secret/child/compose.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
services:
|
||||
included:
|
||||
image: alpine
|
||||
secrets:
|
||||
- my-secret
|
||||
command: cat /run/secrets/my-secret
|
||||
|
||||
secrets:
|
||||
my-secret:
|
||||
environment: 'MY_SECRET'
|
@ -1,3 +1,8 @@
|
||||
include:
|
||||
- path: child/compose.yaml
|
||||
env_file:
|
||||
- secret.env
|
||||
|
||||
services:
|
||||
foo:
|
||||
image: alpine
|
||||
|
1
pkg/e2e/fixtures/env-secret/secret.env
Normal file
1
pkg/e2e/fixtures/env-secret/secret.env
Normal file
@ -0,0 +1 @@
|
||||
MY_SECRET='this-is-secret'
|
@ -5,4 +5,10 @@ services:
|
||||
serviceB:
|
||||
image: nginx:latest
|
||||
env_file:
|
||||
- /doesnotexist/.env
|
||||
- /doesnotexist/.env
|
||||
|
||||
serviceC:
|
||||
profiles: ["test"]
|
||||
image: alpine
|
||||
env_file: test.env
|
||||
|
||||
|
1
pkg/e2e/fixtures/env_file/test.env
Normal file
1
pkg/e2e/fixtures/env_file/test.env
Normal file
@ -0,0 +1 @@
|
||||
FOO=BAR
|
@ -1,13 +1,13 @@
|
||||
services:
|
||||
with-restart:
|
||||
image: alpine
|
||||
image: nginx:alpine
|
||||
init: true
|
||||
command: tail -f /dev/null
|
||||
depends_on:
|
||||
nginx: {condition: service_healthy, restart: true}
|
||||
|
||||
no-restart:
|
||||
image: alpine
|
||||
image: nginx:alpine
|
||||
init: true
|
||||
command: tail -f /dev/null
|
||||
depends_on:
|
||||
@ -15,6 +15,8 @@ services:
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
labels:
|
||||
TEST: ${LABEL:-test}
|
||||
healthcheck:
|
||||
test: "echo | nc -w 5 localhost:80"
|
||||
interval: 2s
|
||||
|
@ -37,4 +37,7 @@ services:
|
||||
RUN mkdir -p /app/config
|
||||
init: true
|
||||
command: sleep infinity
|
||||
volumes:
|
||||
- ./dat:/app/dat
|
||||
- ./data-logs:/app/data-logs
|
||||
develop: *x-dev
|
||||
|
1
pkg/e2e/fixtures/watch/dat/meow.dat
Normal file
1
pkg/e2e/fixtures/watch/dat/meow.dat
Normal file
@ -0,0 +1 @@
|
||||
i am a wannabe cat
|
1
pkg/e2e/fixtures/watch/data-logs/server.log
Normal file
1
pkg/e2e/fixtures/watch/data-logs/server.log
Normal file
@ -0,0 +1 @@
|
||||
[INFO] Server started successfully on port 8080
|
@ -65,7 +65,7 @@ func TestRestart(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRestartWithDependencies(t *testing.T) {
|
||||
c := NewParallelCLI(t, WithEnv(
|
||||
c := NewCLI(t, WithEnv(
|
||||
"COMPOSE_PROJECT_NAME=e2e-restart-deps",
|
||||
))
|
||||
baseService := "nginx"
|
||||
@ -80,9 +80,22 @@ func TestRestartWithDependencies(t *testing.T) {
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "restart", baseService)
|
||||
out := res.Combined()
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", baseService)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Restarting", baseService)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Healthy", baseService)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), out)
|
||||
assert.Assert(t, !strings.Contains(out, depNoRestart), out)
|
||||
|
||||
c = NewParallelCLI(t, WithEnv(
|
||||
"COMPOSE_PROJECT_NAME=e2e-restart-deps",
|
||||
"LABEL=recreate",
|
||||
))
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/restart-test/compose-depends-on.yaml", "up", "-d")
|
||||
out = res.Combined()
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Stopped", depWithRestart)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Recreated", baseService)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Healthy", baseService)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Started", depWithRestart)), out)
|
||||
assert.Assert(t, strings.Contains(out, fmt.Sprintf("Container e2e-restart-deps-%s-1 Running", depNoRestart)), out)
|
||||
}
|
||||
|
||||
func TestRestartWithProfiles(t *testing.T) {
|
||||
|
@ -41,3 +41,13 @@ func TestSecretFromEnv(t *testing.T) {
|
||||
res.Assert(t, icmd.Expected{Out: "-r--r----- 1 1005 1005"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecretFromInclude(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
defer c.cleanupWithDown(t, "env-secret-include")
|
||||
|
||||
t.Run("compose run", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/env-secret/compose.yaml", "run", "included")
|
||||
res.Assert(t, icmd.Expected{Out: "this-is-secret"})
|
||||
})
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ func TestUpDependenciesNotStopped(t *testing.T) {
|
||||
if exitErr.ExitCode() == -1 {
|
||||
t.Fatalf("`compose up` was killed: %v", err)
|
||||
}
|
||||
require.EqualValues(t, 130, exitErr.ExitCode())
|
||||
require.Equal(t, 130, exitErr.ExitCode())
|
||||
}
|
||||
|
||||
RequireServiceState(t, c, "app", "exited")
|
||||
|
@ -17,6 +17,8 @@
|
||||
package remote
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
@ -31,6 +33,7 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/moby/buildkit/util/gitutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const GIT_REMOTE_ENABLED = "COMPOSE_EXPERIMENTAL_GIT_REMOTE"
|
||||
@ -169,7 +172,8 @@ func (g gitRemoteLoader) checkout(ctx context.Context, path string, ref *gitutil
|
||||
cmd = exec.CommandContext(ctx, "git", "fetch", "--depth=1", "origin", ref.Commit)
|
||||
cmd.Env = g.gitCommandEnv()
|
||||
cmd.Dir = path
|
||||
err = cmd.Run()
|
||||
|
||||
err = g.run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -183,6 +187,19 @@ func (g gitRemoteLoader) checkout(ctx context.Context, path string, ref *gitutil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g gitRemoteLoader) run(cmd *exec.Cmd) error {
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
output, err := cmd.CombinedOutput()
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(output))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
logrus.Debug(line)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (g gitRemoteLoader) gitCommandEnv() []string {
|
||||
env := types.NewMapping(os.Environ())
|
||||
if env["GIT_TERMINAL_PROMPT"] == "" {
|
||||
|
@ -149,7 +149,7 @@ func TestGitBranchSwitch(t *testing.T) {
|
||||
f.assertEvents(path)
|
||||
|
||||
// Make sure there are no errors in the out stream
|
||||
assert.Equal(t, "", f.out.String())
|
||||
assert.Empty(t, f.out.String())
|
||||
}
|
||||
|
||||
func TestWatchesAreRecursive(t *testing.T) {
|
||||
|
@ -274,7 +274,7 @@ func newWatcher(paths []string) (Notify, error) {
|
||||
fsw, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "too many open files") && runtime.GOOS == "linux" {
|
||||
return nil, fmt.Errorf("Hit OS limits creating a watcher.\n" +
|
||||
return nil, fmt.Errorf("hit OS limits creating a watcher.\n" +
|
||||
"Run 'sysctl fs.inotify.max_user_instances' to check your inotify limits.\n" +
|
||||
"To raise them, run 'sudo sysctl fs.inotify.max_user_instances=1024'")
|
||||
}
|
||||
@ -317,7 +317,7 @@ func greatestExistingAncestors(paths []string) ([]string, error) {
|
||||
for _, p := range paths {
|
||||
newP, err := greatestExistingAncestor(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Finding ancestor of %s: %w", p, err)
|
||||
return nil, fmt.Errorf("finding ancestor of %s: %w", p, err)
|
||||
}
|
||||
result = append(result, newP)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user