From 60ee6adcd2ed505749e7710a85b3d752bfe6c6ff Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 2 Jul 2025 10:18:41 +0200 Subject: [PATCH] a single place for shell-out command setup Signed-off-by: Nicolas De Loof --- pkg/compose/build_bake.go | 39 ++----------------------- pkg/compose/model.go | 52 ++++++++++++++------------------- pkg/compose/plugins.go | 36 +++++------------------ pkg/compose/shellout.go | 61 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 95 deletions(-) create mode 100644 pkg/compose/shellout.go diff --git a/pkg/compose/build_bake.go b/pkg/compose/build_bake.go index 58626990d..95a02c6e9 100644 --- a/pkg/compose/build_bake.go +++ b/pkg/compose/build_bake.go @@ -34,7 +34,6 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli-plugins/manager" - "github.com/docker/cli/cli-plugins/socket" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" @@ -45,8 +44,6 @@ import ( "github.com/moby/buildkit/util/progress/progressui" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/propagation" "golang.org/x/sync/errgroup" ) @@ -294,26 +291,12 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project logrus.Debugf("Executing bake with args: %v", args) cmd := exec.CommandContext(ctx, buildx.Path, args...) - // Remove DOCKER_CLI_PLUGIN... variable so buildx can detect it run standalone - cmd.Env = filter(project.Environment.Values(), 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()) + err = s.prepareShellOut(ctx, project, cmd) + if err != nil { + return nil, err } - cmd.Env = append(cmd.Env, - fmt.Sprintf("DOCKER_CONTEXT=%s", s.dockerCli.CurrentContext()), - fmt.Sprintf("DOCKER_HOST=%s", s.dockerCli.DockerEndpoint().Host), - ) - - // propagate opentelemetry context to child process, see https://github.com/open-telemetry/oteps/blob/main/text/0258-env-context-baggage-carriers.md - carrier := propagation.MapCarrier{} - otel.GetTextMapPropagator().Inject(ctx, &carrier) - cmd.Env = append(cmd.Env, types.Mapping(carrier).Values()...) - cmd.Stdout = s.stdout() cmd.Stdin = bytes.NewBuffer(b) pipe, err := cmd.StderrPipe() @@ -443,22 +426,6 @@ func toBakeSecrets(project *types.Project, secrets []types.ServiceSecretConfig) return s } -func filter(environ []string, variable string) []string { - prefix := variable + "=" - filtered := make([]string, 0, len(environ)) - for _, val := range environ { - if !strings.HasPrefix(val, prefix) { - filtered = append(filtered, val) - } - } - return filtered -} - -func replace(environ []string, variable, value string) []string { - filtered := filter(environ, variable) - return append(filtered, fmt.Sprintf("%s=%s", variable, value)) -} - func dockerFilePath(ctxName string, dockerfile string) string { if dockerfile == "" { return "" diff --git a/pkg/compose/model.go b/pkg/compose/model.go index 6bde75a70..4c88b1a85 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -21,7 +21,6 @@ import ( "context" "encoding/json" "fmt" - "os" "os/exec" "slices" "strconv" @@ -32,8 +31,6 @@ import ( "github.com/docker/cli/cli-plugins/manager" "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" ) @@ -51,7 +48,11 @@ func (s *composeService) ensureModels(ctx context.Context, project *types.Projec } cmd := exec.CommandContext(ctx, dockerModel.Path, "ls", "--json") - s.setupChildProcess(ctx, cmd) + err = s.prepareShellOut(ctx, project, cmd) + if err != nil { + return err + } + output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error checking available models: %w", err) @@ -85,23 +86,18 @@ func (s *composeService) ensureModels(ctx context.Context, project *types.Projec eg.Go(func() error { w := progress.ContextWriter(gctx) if !slices.Contains(availableModels, config.Model) { - err = s.pullModel(gctx, dockerModel, config, quietPull, w) + err = s.pullModel(gctx, dockerModel, project, config, quietPull, w) if err != nil { return err } } - err = s.configureModel(gctx, dockerModel, config, w) - if err != nil { - return err - } - w.Event(progress.CreatedEvent(config.Name)) - return nil + return s.configureModel(gctx, dockerModel, project, config, w) }) } return eg.Wait() } -func (s *composeService) pullModel(ctx context.Context, dockerModel *manager.Plugin, model types.ModelConfig, quietPull bool, w progress.Writer) error { +func (s *composeService) pullModel(ctx context.Context, dockerModel *manager.Plugin, project *types.Project, model types.ModelConfig, quietPull bool, w progress.Writer) error { w.Event(progress.Event{ ID: model.Name, Status: progress.Working, @@ -109,8 +105,10 @@ func (s *composeService) pullModel(ctx context.Context, dockerModel *manager.Plu }) cmd := exec.CommandContext(ctx, dockerModel.Path, "pull", model.Model) - s.setupChildProcess(ctx, cmd) - + err := s.prepareShellOut(ctx, project, cmd) + if err != nil { + return err + } stream, err := cmd.StdoutPipe() if err != nil { return err @@ -150,7 +148,7 @@ func (s *composeService) pullModel(ctx context.Context, dockerModel *manager.Plu return err } -func (s *composeService) configureModel(ctx context.Context, dockerModel *manager.Plugin, config types.ModelConfig, w progress.Writer) error { +func (s *composeService) configureModel(ctx context.Context, dockerModel *manager.Plugin, project *types.Project, config types.ModelConfig, w progress.Writer) error { w.Event(progress.Event{ ID: config.Name, Status: progress.Working, @@ -167,13 +165,20 @@ func (s *composeService) configureModel(ctx context.Context, dockerModel *manage args = append(args, config.RuntimeFlags...) } cmd := exec.CommandContext(ctx, dockerModel.Path, args...) - s.setupChildProcess(ctx, cmd) + err := s.prepareShellOut(ctx, project, cmd) + if err != nil { + return err + } return cmd.Run() } func (s *composeService) setModelVariables(ctx context.Context, dockerModel *manager.Plugin, project *types.Project) error { cmd := exec.CommandContext(ctx, dockerModel.Path, "status", "--json") - s.setupChildProcess(ctx, cmd) + err := s.prepareShellOut(ctx, project, cmd) + if err != nil { + return err + } + statusOut, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error checking docker-model status: %w", err) @@ -211,19 +216,6 @@ func (s *composeService) setModelVariables(ctx context.Context, dockerModel *man return nil } -func (s *composeService) setupChildProcess(gctx context.Context, cmd *exec.Cmd) { - // exec provider command with same environment Compose is running - env := types.NewMapping(os.Environ()) - // but remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone - delete(env, manager.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)) - env["DOCKER_CONTEXT"] = s.dockerCli.CurrentContext() - cmd.Env = env.Values() -} - type Model struct { Id string `json:"id"` Tags []string `json:"tags"` diff --git a/pkg/compose/plugins.go b/pkg/compose/plugins.go index 7ddf0b1f2..f49c18f99 100644 --- a/pkg/compose/plugins.go +++ b/pkg/compose/plugins.go @@ -29,16 +29,12 @@ import ( "strings" "sync" + "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/cli/cli-plugins/manager" + "github.com/docker/cli/cli/config" "github.com/docker/compose/v2/pkg/progress" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/propagation" - - "github.com/compose-spec/compose-go/v2/types" - "github.com/docker/cli/cli-plugins/manager" - "github.com/docker/cli/cli-plugins/socket" - "github.com/docker/cli/cli/config" ) type JsonMessage struct { @@ -199,29 +195,11 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types. args = append(args, service.Name) cmd := exec.CommandContext(ctx, path, args...) - // exec provider command with same environment Compose is running - env := types.NewMapping(os.Environ()) - // but remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone - delete(env, manager.ReexecEnvvar) - // and add the explicit environment variables set for service - for key, val := range service.Environment.RemoveEmpty().ToMapping() { - env[key] = val + + err := s.prepareShellOut(ctx, project, cmd) + if err != nil { + return nil, err } - 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 } diff --git a/pkg/compose/shellout.go b/pkg/compose/shellout.go new file mode 100644 index 000000000..3f8947e61 --- /dev/null +++ b/pkg/compose/shellout.go @@ -0,0 +1,61 @@ +/* + 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/exec" + + "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/cli/cli-plugins/manager" + "github.com/docker/cli/cli/context/docker" + "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, project *types.Project, cmd *exec.Cmd) error { + // exec command with same environment Compose is running + env := types.NewMapping(project.Environment.Values()) + + // remove DOCKER_CLI_PLUGIN... variable so a docker-cli plugin will detect it run standalone + delete(env, manager.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)) + + env["DOCKER_CONTEXT"] = s.dockerCli.CurrentContext() + + metadata, err := s.dockerCli.ContextStore().GetMetadata(s.dockerCli.CurrentContext()) + if err != nil { + return err + } + endpoint, err := docker.EndpointFromContext(metadata) + if err != nil { + return err + } + actualHost := s.dockerCli.DockerEndpoint().Host + if endpoint.Host != actualHost { + // We are running with `--host` or `DOCKER_HOST` which overrides selected context + env["DOCKER_HOST"] = actualHost + } + + cmd.Env = env.Values() + return nil +}