Skip to content

Commit

Permalink
Implement GetRequiredPackages for Java (#1535)
Browse files Browse the repository at this point in the history
As part of a Pulumi deployment, the Pulumi engine will ask the language
host running the program for the set of plugins required to run that
program successfully by making a `GetRequiredPlugins` gRPC call. For
instance, if one has a TypeScript program whose `index.ts` `import`s
`@pulumi/aws`, the NodeJS language host will find the `@pulumi/aws`
package and report that the `aws` resource plugin is required. This
enables features such as pre-emptively downloading a plugin ahead of
time, as opposed to waiting for the first resource registration against
that plugin.

With the introduction of parameterized providers, `GetRequiredPlugins`
falls a little short -- parameterization means that it is no longer
necessarily the case that a package named `x` is provided by a plugin of
the same name. For instance, with a dynamically bridged Terraform
provider X, the provider name will be X, but the plugin name will be
e.g. `pulumi-terraform-provider`. In pulumi/pulumi#17894,
`GetRequiredPackages` was added to the language host interface to
provide the additional context of a plugin's parameterization, and is
now used if present instead of `GetRequiredPlugins`. This change
implements `GetRequiredPackages` for Java as follows:

* Presently, `GetRequiredPlugins` is implemented by running a small Java
program (located in the internal `bootstrap` package of the core SDK)
that walks its class path looking for `pulumi-plugin.json` files. This
code is extended to parse `parameterization` blocks from those JSON
files, as we do in other languages using this mechanism.
* When we generate SDKs, we generate Gradle build files which generate
`pulumi-plugin.json` files as part of the build process. We extend the
`build.gradle` template used when generating SDK build files so that it
emits `parameterization` blocks appropriately. As part of this, we also
clean up the emission of package names and versions.
* We now have enough to run and pass the `l2-parameterized-resource`
conformance test, which in theory confirms that all of this works, so we
include that as part of this work.
* We remove the now-deprecated `GetRequiredPlugins` implementation.

Fixes #1510
  • Loading branch information
lunaris authored Jan 2, 2025
1 parent 348b6f1 commit 7fdb427
Show file tree
Hide file tree
Showing 71 changed files with 1,280 additions and 366 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Update to Pulumi 3.143.0

- Implement `GetRequiredPackages` for the Java language host

### Bug Fixes

- [Convert] Emit the `Deployment` class when using Pulumi built-in functions in PCL `stack()` and `projectName()`
1 change: 0 additions & 1 deletion pkg/cmd/pulumi-language-java/language_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ var expectedFailures = map[string]string{
"l2-invoke-variants": "unimplemented for Java",
"l2-large-string": "unimplemented for Java",
"l2-map-keys": "unimplemented for Java",
"l2-parameterized-resource": "unimplemented for Java",
"l2-plain": "unimplemented for Java",
"l2-primitive-ref": "unimplemented for Java",
"l2-provider-grpc-config-schema-secret": "unimplemented for Java",
Expand Down
58 changes: 34 additions & 24 deletions pkg/cmd/pulumi-language-java/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"

codegen "github.com/pulumi/pulumi-java/pkg/codegen/java"
Expand Down Expand Up @@ -158,49 +160,57 @@ func (host *javaLanguageHost) Executor(attachDebugger bool) (*executors.JavaExec
return executor, nil
}

// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
func (host *javaLanguageHost) GetRequiredPlugins(
// GetRequiredPackages computes the complete set of anticipated packages required by a program.
func (host *javaLanguageHost) GetRequiredPackages(
ctx context.Context,
req *pulumirpc.GetRequiredPluginsRequest,
) (*pulumirpc.GetRequiredPluginsResponse, error) {
logging.V(5).Infof("GetRequiredPlugins: program=%v", req.GetProgram()) //nolint:staticcheck
req *pulumirpc.GetRequiredPackagesRequest,
) (*pulumirpc.GetRequiredPackagesResponse, error) {
logging.V(5).Infof("GetRequiredPackages: programDirectory=%v", req.Info.ProgramDirectory)

// now, introspect the user project to see which pulumi resource packages it references.
pulumiPackages, err := host.determinePulumiPackages(ctx, req)
pulumiPackages, err := host.determinePulumiPackages(ctx, req.Info.ProgramDirectory)
if err != nil {
return nil, errors.Wrapf(err, "language host could not determine Pulumi packages")
}

// Now that we know the set of pulumi packages referenced, and we know where packages have been restored to,
// we can examine each package to determine the corresponding resource-plugin for it.

plugins := []*pulumirpc.PluginDependency{}
pkgs := []*pulumirpc.PackageDependency{}
for _, pulumiPackage := range pulumiPackages {
logging.V(3).Infof(
"GetRequiredPlugins: Determining plugin dependency: %v, %v",
pulumiPackage.Name, pulumiPackage.Version,
)

// Skip over any packages that don't correspond to Pulumi resource plugins.
if !pulumiPackage.Resource {
continue // the package has no associated resource plugin
continue
}

plugins = append(plugins, &pulumirpc.PluginDependency{
pkg := &pulumirpc.PackageDependency{
Kind: "resource",
Name: pulumiPackage.Name,
Version: pulumiPackage.Version,
Server: pulumiPackage.Server,
Kind: "resource",
})
}
if pulumiPackage.Parameterization != nil {
pkg.Parameterization = &pulumirpc.PackageParameterization{
Name: pulumiPackage.Parameterization.Name,
Version: pulumiPackage.Parameterization.Version,
Value: pulumiPackage.Parameterization.Value,
}
}

pkgs = append(pkgs, pkg)
}

logging.V(5).Infof("GetRequiredPlugins: plugins=%v", plugins)
logging.V(5).Infof("GetRequiredPackages: packages=%v", pkgs)
return &pulumirpc.GetRequiredPackagesResponse{Packages: pkgs}, nil
}

return &pulumirpc.GetRequiredPluginsResponse{Plugins: plugins}, nil
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
func (host *javaLanguageHost) GetRequiredPlugins(
context.Context,
*pulumirpc.GetRequiredPluginsRequest,
) (*pulumirpc.GetRequiredPluginsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRequiredPlugins not implemented")
}

func (host *javaLanguageHost) determinePulumiPackages(
ctx context.Context,
req *pulumirpc.GetRequiredPluginsRequest,
programDirectory string,
) ([]plugin.PulumiPluginJSON, error) {
logging.V(3).Infof("GetRequiredPlugins: Determining Pulumi plugins")

Expand All @@ -213,7 +223,7 @@ func (host *javaLanguageHost) determinePulumiPackages(
cmd := exec.Cmd
args := exec.PluginArgs
quiet := true
output, err := host.runJavaCommand(ctx, req.Info.ProgramDirectory, cmd, args, quiet)
output, err := host.runJavaCommand(ctx, programDirectory, cmd, args, quiet)
if err != nil {
// Plugin determination is an advisory feature so it does not need to escalate to an error.
logging.V(3).Infof("language host could not run plugin discovery command successfully, "+
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: l2-parameterized-resource
runtime: java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.pulumi</groupId>
<artifactId>l2-parameterized-resource</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<encoding>UTF-8</encoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<mainClass>generated_program.App</mainClass>
<mainArgs/>
</properties>

<repositories>
<repository>
<id>repository-0</id>
<url>REPOSITORY</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>com.pulumi</groupId>
<artifactId>pulumi</artifactId>
<version>CORE.VERSION</version>
</dependency>
<dependency>
<groupId>com.pulumi</groupId>
<artifactId>subpackage</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${mainClass}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${mainClass}</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-my-jar-with-dependencies</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>${mainClass}</mainClass>
<commandlineArgs>${mainArgs}</commandlineArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-wrapper-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<mavenVersion>3.8.5</mavenVersion>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.subpackage.HelloWorld;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}

public static void stack(Context ctx) {
// The resource name is based on the parameter value
var example = new HelloWorld("example");

ctx.export("parameterValue", example.parameterValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ def genPulumiResources = tasks.register('genPulumiResources') {
def outDir = file("$resourcesDir/$subDir")
outDir.mkdirs()
new File(outDir, "version.txt").text = resolvedVersion
def info = new Object()
info.metaClass.resource = true
info.metaClass.name = "alpha"
info.metaClass.version = resolvedVersion
def infoJson = new groovy.json.JsonBuilder(info).toPrettyString()
def builder = new groovy.json.JsonBuilder()
builder {
resource true
name "alpha"
version resolvedVersion
}
def infoJson = builder.toPrettyString()
new File(outDir, "plugin.json").text = infoJson
}
}
Expand Down Expand Up @@ -152,4 +154,4 @@ if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mainPublication
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ def genPulumiResources = tasks.register('genPulumiResources') {
def outDir = file("$resourcesDir/$subDir")
outDir.mkdirs()
new File(outDir, "version.txt").text = resolvedVersion
def info = new Object()
info.metaClass.resource = true
info.metaClass.name = "simple"
info.metaClass.version = resolvedVersion
def infoJson = new groovy.json.JsonBuilder(info).toPrettyString()
def builder = new groovy.json.JsonBuilder()
builder {
resource true
name "simple"
version resolvedVersion
}
def infoJson = builder.toPrettyString()
new File(outDir, "plugin.json").text = infoJson
}
}
Expand Down Expand Up @@ -152,4 +154,4 @@ if (signingKey) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.mainPublication
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading

0 comments on commit 7fdb427

Please sign in to comment.