From 68ea05a11b8761046ce11bae9d756fd8a3d6adeb Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Thu, 3 Apr 2025 16:20:30 +0200 Subject: [PATCH 1/2] cleanup runPluging function Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/compose/plugins.go | 71 ++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/pkg/compose/plugins.go b/pkg/compose/plugins.go index c4fdbd2e61e..ed489d4a698 100644 --- a/pkg/compose/plugins.go +++ b/pkg/compose/plugins.go @@ -30,7 +30,6 @@ import ( "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/socket" "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/docker/errdefs" "github.com/spf13/cobra" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" @@ -48,41 +47,15 @@ const ( SetEnvType = "setenv" ) -func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error { //nolint:gocyclo - x := *service.Provider +func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error { + provider := *service.Provider - // Only support Docker CLI plugins for first iteration. Could support any binary from PATH - plugin, err := manager.GetPlugin(x.Type, s.dockerCli, &cobra.Command{}) + path, err := s.getPluginBinaryPath(provider.Type) if err != nil { - if errdefs.IsNotFound(err) { - return fmt.Errorf("unsupported external service type %s", x.Type) - } return err } - args := []string{"compose", "--project-name", project.Name, command} - for k, v := range x.Options { - args = append(args, fmt.Sprintf("--%s=%s", k, v)) - } - - cmd := exec.CommandContext(ctx, plugin.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()...) + cmd := s.setupPluginCommand(ctx, project, provider, path, command) eg := errgroup.Group{} stdout, err := cmd.StdoutPipe() @@ -147,3 +120,39 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project, } return nil } + +func (s *composeService) getPluginBinaryPath(providerType string) (string, error) { + // Only support Docker CLI plugins for first iteration. Could support any binary from PATH + plugin, err := manager.GetPlugin(providerType, s.dockerCli, &cobra.Command{}) + if err != nil { + return "", err + } + return plugin.Path, nil +} + +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 +} From 18e8b66d8c21b3cab525c3a8282bb11758a0e0d6 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:44:20 +0200 Subject: [PATCH 2/2] plugin provider support: check docker model runner status Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/compose/plugins.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/pkg/compose/plugins.go b/pkg/compose/plugins.go index ed489d4a698..a905546fe8d 100644 --- a/pkg/compose/plugins.go +++ b/pkg/compose/plugins.go @@ -50,12 +50,16 @@ const ( func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error { provider := *service.Provider - path, err := s.getPluginBinaryPath(provider.Type) + plugin, err := s.getPluginBinaryPath(provider.Type) if err != nil { return err } - cmd := s.setupPluginCommand(ctx, project, provider, path, command) + 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() @@ -121,13 +125,9 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project, return nil } -func (s *composeService) getPluginBinaryPath(providerType string) (string, error) { +func (s *composeService) getPluginBinaryPath(providerType string) (*manager.Plugin, error) { // Only support Docker CLI plugins for first iteration. Could support any binary from PATH - plugin, err := manager.GetPlugin(providerType, s.dockerCli, &cobra.Command{}) - if err != nil { - return "", err - } - return plugin.Path, nil + 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 { @@ -142,7 +142,7 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types. // Use docker/cli mechanism to propagate termination signal to child process server, err := socket.NewPluginServer(nil) - if err != nil { + if err == nil { defer server.Close() //nolint:errcheck cmd.Cancel = server.Close cmd.Env = replace(cmd.Env, socket.EnvKey, server.Addr().String()) @@ -156,3 +156,24 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types. 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 +}