From a786e3414535a1f3ad6f7feafb8e871ff7acc2ba Mon Sep 17 00:00:00 2001 From: Mikhail Shilkov Date: Wed, 29 Jan 2025 22:52:51 +0100 Subject: [PATCH] Support RunPlugin for Maven and Gradle plugins --- pkg/cmd/pulumi-language-java/main.go | 63 +++++++++++++++++++++++ pkg/internal/executors/executor.go | 7 +++ pkg/internal/executors/executor_gradle.go | 18 +++++-- pkg/internal/executors/executor_maven.go | 14 ++++- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/pulumi-language-java/main.go b/pkg/cmd/pulumi-language-java/main.go index 0da112f0c55..1f6a76f901a 100644 --- a/pkg/cmd/pulumi-language-java/main.go +++ b/pkg/cmd/pulumi-language-java/main.go @@ -16,6 +16,7 @@ import ( "os/signal" "path/filepath" "strings" + "syscall" "time" pbempty "github.com/golang/protobuf/ptypes/empty" @@ -404,6 +405,68 @@ func (host *javaLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest return &pulumirpc.RunResponse{Error: errResult}, nil } +// RunPlugin executes a plugin program and returns its result. +func (host *javaLanguageHost) RunPlugin( + req *pulumirpc.RunPluginRequest, server pulumirpc.LanguageRuntime_RunPluginServer, +) error { + logging.V(5).Infof("Attempting to run java plugin in %s", req.Pwd) + + closer, stdout, stderr, err := rpcutil.MakeRunPluginStreams(server, false) + if err != nil { + return err + } + // Best effort close, but we try an explicit close and error check at the end as well + defer closer.Close() + + // Create new executor options with the plugin directory and runtime args + pluginExecOptions := executors.JavaExecutorOptions{ + Binary: host.execOptions.Binary, + UseExecutor: host.execOptions.UseExecutor, + WD: fsys.DirFS(req.Info.ProgramDirectory), + ProgramArgs: req.Args, + } + + executor, err := executors.NewJavaExecutor(pluginExecOptions, false) + if err != nil { + return err + } + + executable := executor.Cmd + args := executor.RunPluginArgs + + if len(args) == 0 { + return errors.Errorf("executor %s does not currently support running plugins", executor.Cmd) + } + + commandStr := strings.Join(args, " ") + logging.V(5).Infof("Language host launching process: %s %s", executable, commandStr) + + cmd := exec.Command(executable, args...) + cmd.Dir = req.Pwd + cmd.Env = req.Env + cmd.Stdout = stdout + cmd.Stderr = stderr + + if err = cmd.Run(); err != nil { + var exiterr *exec.ExitError + if errors.As(err, &exiterr) { + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + return server.Send(&pulumirpc.RunPluginResponse{ + //nolint:gosec // WaitStatus always uses the lower 8 bits for the exit code. + Output: &pulumirpc.RunPluginResponse_Exitcode{Exitcode: int32(status.ExitStatus())}, + }) + } + if len(exiterr.Stderr) > 0 { + return fmt.Errorf("program exited unexpectedly: %w: %s", exiterr, exiterr.Stderr) + } + return fmt.Errorf("program exited unexpectedly: %w", exiterr) + } + return fmt.Errorf("problem executing plugin program (could not run language executor): %w", err) + } + + return closer.Close() +} + // constructEnv constructs an environ for `pulumi-language-java` // by enumerating all of the optional and non-optional evn vars present // in a RunRequest. diff --git a/pkg/internal/executors/executor.go b/pkg/internal/executors/executor.go index 1649f9350e8..a48ac3724a6 100644 --- a/pkg/internal/executors/executor.go +++ b/pkg/internal/executors/executor.go @@ -31,6 +31,10 @@ type JavaExecutor struct { // by the Java program. PluginArgs []string + // Command args to run a plugin (e.g. a provider). Optional if the executor + // does not support running plugins. + RunPluginArgs []string + // Returns a list of program dependencies as configured for the executor (e.g. in a `pom.xml` for Maven, or a // `build.gradle` for Gradle). GetProgramDependencies func( @@ -51,6 +55,9 @@ type JavaExecutorOptions struct { // The value of `runtime.options.use-executor` setting from // `Pulumi.yaml`. Optional. UseExecutor string + + // Additional runtime arguments to pass to the program. + ProgramArgs []string } type javaExecutorFactory interface { diff --git a/pkg/internal/executors/executor_gradle.go b/pkg/internal/executors/executor_gradle.go index 8fbaa3d8d35..e85f9fa453f 100644 --- a/pkg/internal/executors/executor_gradle.go +++ b/pkg/internal/executors/executor_gradle.go @@ -36,7 +36,7 @@ func (g gradle) NewJavaExecutor(opts JavaExecutorOptions) (*JavaExecutor, error) if err != nil { return nil, err } - executor, err := g.newGradleExecutor(gradleRoot, cmd, subproject) + executor, err := g.newGradleExecutor(gradleRoot, cmd, subproject, opts.ProgramArgs) if err != nil { return nil, err } @@ -103,21 +103,29 @@ func (gradle) isGradleProject(dir fs.FS, opts JavaExecutorOptions) (bool, error) return false, nil } -func (g gradle) newGradleExecutor(gradleRoot fsys.ParentFS, cmd, subproject string) (*JavaExecutor, error) { +func (g gradle) newGradleExecutor(gradleRoot fsys.ParentFS, + cmd, subproject string, args []string, +) (*JavaExecutor, error) { return &JavaExecutor{ Cmd: cmd, Dir: gradleRoot.Path(), BuildArgs: []string{g.prefix(subproject, "build"), "--console=plain"}, RunArgs: []string{g.prefix(subproject, "run"), "--console=plain"}, PluginArgs: []string{ - /* STDOUT needs to be clean of gradle output, - because we expect a JSON with plugin - results */ + // STDOUT needs to be clean of gradle output because we expect a JSON with plugin results. "-q", // must go first due to a bug https://github.com/gradle/gradle/issues/5098 g.prefix(subproject, "run"), "--console=plain", "-PmainClass=com.pulumi.bootstrap.internal.Main", "--args=packages", }, + RunPluginArgs: []string{ + // STDOUT needs to be clean of gradle output because we expect a port printed back. + "-q", // must go first due to a bug https://github.com/gradle/gradle/issues/5098 + "--project-dir", gradleRoot.Path(), // plugin's CWD is set to the Pulumi program's root + g.prefix(subproject, "run"), "--console=plain", + "-PmainClass=com.pulumi.bootstrap.internal.Main", + "--args", strings.Join(args, " "), + }, }, nil } diff --git a/pkg/internal/executors/executor_maven.go b/pkg/internal/executors/executor_maven.go index a1e2e172faf..460afb389ce 100644 --- a/pkg/internal/executors/executor_maven.go +++ b/pkg/internal/executors/executor_maven.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "regexp" "strings" @@ -35,7 +36,8 @@ func (m maven) NewJavaExecutor(opts JavaExecutorOptions) (*JavaExecutor, error) if err != nil { return nil, err } - return m.newMavenExecutor(cmd) + pomXMLPath := filepath.Join(opts.WD.Path(), "pom.xml") + return m.newMavenExecutor(cmd, opts.ProgramArgs, pomXMLPath) } func (maven) isMavenProject(opts JavaExecutorOptions) (bool, error) { @@ -45,7 +47,7 @@ func (maven) isMavenProject(opts JavaExecutorOptions) (bool, error) { return fsys.FileExists(opts.WD, "pom.xml") } -func (maven) newMavenExecutor(cmd string) (*JavaExecutor, error) { +func (maven) newMavenExecutor(cmd string, args []string, pomXMLPath string) (*JavaExecutor, error) { return &JavaExecutor{ Cmd: cmd, BuildArgs: []string{ @@ -68,6 +70,14 @@ func (maven) newMavenExecutor(cmd string) (*JavaExecutor, error) { "-DmainClass=com.pulumi.bootstrap.internal.Main", "-DmainArgs=packages", }, + RunPluginArgs: []string{ + /* move normal output to STDERR, because we need STDOUT for port printed back */ + "-Dorg.slf4j.simpleLogger.defaultLogLevel=warn", + "-Dorg.slf4j.simpleLogger.logFile=System.err", + "--no-transfer-progress", "compile", "exec:java", + "-f", pomXMLPath, // plugin's CWD is set to the Pulumi program's root + fmt.Sprintf("-Dexec.args=%s", strings.Join(args, " ")), + }, // Implements the GetProgramDependencies function to retrieve the dependencies of a Maven project. GetProgramDependencies: func(