Skip to content

Commit

Permalink
Support RunPlugin for Maven and Gradle plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhailshilkov committed Jan 29, 2025
1 parent 44a0778 commit c973271
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 7 deletions.
63 changes: 63 additions & 0 deletions pkg/cmd/pulumi-language-java/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"

pbempty "github.com/golang/protobuf/ptypes/empty"
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions pkg/internal/executors/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down
17 changes: 12 additions & 5 deletions pkg/internal/executors/executor_gradle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -103,21 +103,28 @@ 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
}

Expand Down
14 changes: 12 additions & 2 deletions pkg/internal/executors/executor_maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

Expand Down Expand Up @@ -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) {
Expand All @@ -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{
Expand All @@ -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(
Expand Down

0 comments on commit c973271

Please sign in to comment.