Skip to content

Commit 41b0d90

Browse files
authored
CustomResource for Java SDK (#3020)
<!--Thanks for your contribution. See [CONTRIBUTING](CONTRIBUTING.md) for Pulumi's contribution guidelines. Help us merge your changes more quickly by adding more details such as labels, milestones, and reviewers.--> ### Proposed changes <!--Give us a brief description of what you've done and what it solves. --> Implements `CustomResource` for Java SDK as an overlay, for parity with other SDKs. An overlay is necessary for [this reason](#2787 (comment)). #### Features Supports two usage modes. Note that each SDK is slightly different in which mode(s) it supports. - **untyped**, where you use `CustomResource` directly and set arbitrary fields on the object. - **typed**, where you subclass `CustomResource` to create a strongly-typed wrapper representing a CRD. A "Patch" variant is also provided. Provides a "getter" method in both untyped and typed mode. Note that the patch variant doesn't have a getter in most SDKs. #### Summary of Changes - new example: `examples/java/customresource` - new resource: `apiextensions.CustomResource` - new resource: `apiextensions.CustomResourcePatch` - new dependency: `net.bytebuddy:byte-buddy:1.14.15` - new dependency:`com.google.guava:guava:32.1.2-jre` (used by core already) TODOs: - [x] Prerequisite: Pulumi Java 0.12 (pulumi/pulumi-java#1361) - [x] `Get` method (untyped and typed) - [x] Patch variant - [x] Integration test or in-tree example ### Related issues (optional) <!--Refer to related PRs or issues: #1234, or 'Fixes #1234' or 'Closes #1234'. Or link to full URLs to issues or pull requests in other GitHub repositories. --> Closes #2787 ### API - `CustomResource` - to be used directly or subclassed to expose typed output properties - `CustomResourceArgs` - the final class to be used directly in the untyped use-case - `CustomResourceArgsBase` - the abstract base class for custom resource args, to expose typed input properties - `CustomResourceArgsBase.Builder<T,B>` - the base class for your custom args builder - `CustomResourcePatch` - to be used directly or subclassed to expose typed output properties - `CustomResourcePatchArgs` - the final class to be used directly in the untyped use-case - `CustomResourcePatchArgsBase` - the abstract base class for custom resource args, to expose typed input properties - `CustomResourcePatchArgsBase.Builder<T,B>` - the base class for your custom args builder ### Implementation Details #### Working with Untyped Inputs The core Pulumi Java SDK has no support for dynamic inputs; it relies exclusively on reflection of the supplied `InputArgs` subclass (see: [`InputArgs::toMapAsync`](https://github.com/pulumi/pulumi-java/blob/f887fbc869974ae7d9049cb4a5b62f51b1151dcb/sdk/java/pulumi/src/main/java/com/pulumi/resources/InputArgs.java#L63)). To support the "untyped" mode, this implementation codegens a class at runtime using bytebuddy. #### Builder Inheritance The Java SDK leans on the fluent builder pattern, and there are special challenges in designing a builder that is amenable to inheritance. This implementation uses generics as seen [here](https://egalluzzo.blogspot.com/2010/06/using-inheritance-with-fluent.html). ## Example Here's an example program to deploy two [cert-manager issuers](https://cert-manager.io/docs/concepts/issuer/). - `issuer1` is untyped, and calls `otherFields(...)` on the builder to set the `spec`. - `issuer2` is typed, calls `spec(...)` on a subclassed builder to set the `spec`, and uses the typed `spec` output. Note that the `apiVersion` and `kind` are set automatically. The code seen in `Inputs` and `Outputs` section would, in practice, be generated by pulumi-java-gen based on a schema file. The untyped and typed getter variants are also demonstrated. ```java package myproject; import java.util.Map; import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; import com.pulumi.Pulumi; import com.pulumi.core.Output; import com.pulumi.core.annotations.CustomType; import com.pulumi.core.annotations.Export; import com.pulumi.core.annotations.Import; import com.pulumi.kubernetes.apiextensions.CustomResource; import com.pulumi.kubernetes.apiextensions.CustomResourceArgs; import com.pulumi.kubernetes.apiextensions.CustomResourceArgsBase; import com.pulumi.kubernetes.meta.v1.inputs.ObjectMetaArgs; public class App { public static void main(String[] args) { Pulumi.run(ctx -> { var issuer1 = new CustomResource("issuer1", CustomResourceArgs.builder() .apiVersion("cert-manager.io/v1") .kind("Issuer") .metadata(ObjectMetaArgs.builder().build()) .otherFields(Map.of("spec", Map.of( "selfSigned", Map.of()))) .build()); ctx.export("issuer1_name", issuer1.metadata().applyValue(s -> s.name())); var get1 = CustomResource.get("get1", "cert-manager.io/v1", "Issuer", issuer1.id(), null); var issuer2 = new Issuer("issuer2", IssuerArgs.builder() .metadata(ObjectMetaArgs.builder().build()) .spec(Inputs.IssuerSpecArgs.builder() .selfSigned(Inputs.SelfSignedArgs.builder().build()) .build()) .build()); ctx.export("issuer2_name", issuer2.metadata().applyValue(s -> s.name())); ctx.export("issuer2_selfsigned", issuer2.spec().applyValue(s -> s.selfSigned().isPresent())); var get2 = Issuer.get("get2", issuer2.id(), null); ctx.export("get2_selfsigned", get2.spec().applyValue(s -> s.selfSigned().isPresent())); }); } } class Issuer extends CustomResource { /** * The spec of the Issuer. */ @export(name = "spec", refs = { Outputs.IssuerSpec.class }, tree = "[0]") private Output<Outputs.IssuerSpec> spec; public Output<Outputs.IssuerSpec> spec() { return this.spec; } public Issuer(String name, @nullable IssuerArgs args) { super(name, makeArgs(args)); } public Issuer(String name, @nullable IssuerArgs args, @nullable com.pulumi.resources.CustomResourceOptions options) { super(name, makeArgs(args), options); } protected Issuer(String name, Output<String> id, @nullable com.pulumi.resources.CustomResourceOptions options) { super(name, "cert-manager.io/v1", "Issuer", id, options); } private static IssuerArgs makeArgs(@nullable IssuerArgs args) { var builder = args == null ? IssuerArgs.builder() : IssuerArgs.builder(args); return builder .apiVersion("cert-manager.io/v1") .kind("Issuer") .build(); } public static Issuer get(String name, Output<String> id, @nullable com.pulumi.resources.CustomResourceOptions options) { return new Issuer(name, id, options); } } class IssuerArgs extends CustomResourceArgsBase { /** * The spec of the Issuer. */ @import(name = "spec", required = true) @nullable private Output<Inputs.IssuerSpecArgs> spec; public static Builder builder() { return new Builder(); } public static Builder builder(IssuerArgs defaults) { return new Builder(defaults); } static class Builder extends CustomResourceArgsBase.Builder<IssuerArgs, Builder> { public Builder() { super(new IssuerArgs()); } public Builder(IssuerArgs defaults) { super(new IssuerArgs(), defaults); } public Builder spec(@nullable Output<Inputs.IssuerSpecArgs> spec) { $.spec = spec; return this; } public Builder spec(Inputs.IssuerSpecArgs spec) { return spec(Output.of(spec)); } @OverRide protected void copy(IssuerArgs args) { super.copy(args); $.spec = args.spec; } } } class Inputs { public static final class IssuerSpecArgs extends com.pulumi.resources.ResourceArgs { public static final IssuerSpecArgs Empty = new IssuerSpecArgs(); @import(name = "selfSigned", required = true) private @nullable Output<SelfSignedArgs> selfSigned; public Optional<Output<SelfSignedArgs>> selfSigned() { return Optional.ofNullable(this.selfSigned); } private IssuerSpecArgs() { } private IssuerSpecArgs(IssuerSpecArgs $) { this.selfSigned = $.selfSigned; } public static Builder builder() { return new Builder(); } public static Builder builder(IssuerSpecArgs defaults) { return new Builder(defaults); } public static final class Builder { private IssuerSpecArgs $; public Builder() { $ = new IssuerSpecArgs(); } public Builder(IssuerSpecArgs defaults) { $ = new IssuerSpecArgs(Objects.requireNonNull(defaults)); } public Builder selfSigned(@nullable Output<SelfSignedArgs> selfSigned) { $.selfSigned = selfSigned; return this; } public Builder selfSigned(SelfSignedArgs selfSigned) { return selfSigned(Output.of(selfSigned)); } public IssuerSpecArgs build() { return $; } } } public static final class SelfSignedArgs extends com.pulumi.resources.ResourceArgs { public static final SelfSignedArgs Empty = new SelfSignedArgs(); private SelfSignedArgs() { } private SelfSignedArgs(SelfSignedArgs $) { } public static Builder builder() { return new Builder(); } public static Builder builder(SelfSignedArgs defaults) { return new Builder(defaults); } public static final class Builder { private SelfSignedArgs $; public Builder() { $ = new SelfSignedArgs(); } public Builder(SelfSignedArgs defaults) { $ = new SelfSignedArgs(Objects.requireNonNull(defaults)); } public SelfSignedArgs build() { return $; } } } } class Outputs { @CustomType static final class IssuerSpec { private @nullable SelfSigned selfSigned; private IssuerSpec() { } public Optional<SelfSigned> selfSigned() { return Optional.ofNullable(this.selfSigned); } public static Builder builder() { return new Builder(); } public static Builder builder(IssuerSpec defaults) { return new Builder(defaults); } @CustomType.Builder public static final class Builder { private @nullable SelfSigned selfSigned; public Builder() { } public Builder(IssuerSpec defaults) { Objects.requireNonNull(defaults); this.selfSigned = defaults.selfSigned; } @CustomType.Setter public Builder selfSigned(@nullable SelfSigned selfSigned) { this.selfSigned = selfSigned; return this; } public IssuerSpec build() { final var _resultValue = new IssuerSpec(); _resultValue.selfSigned = selfSigned; return _resultValue; } } } @CustomType static final class SelfSigned { private SelfSigned() { } public static Builder builder() { return new Builder(); } public static Builder builder(SelfSigned defaults) { return new Builder(defaults); } @CustomType.Builder public static final class Builder { public Builder() { } public Builder(SelfSigned defaults) { Objects.requireNonNull(defaults); } public SelfSigned build() { final var _resultValue = new SelfSigned(); return _resultValue; } } } } ``` Gives the expected output: ``` ❯ pulumi preview --diff Previewing update (dev) + pulumi:pulumi:Stack: (create) [urn=urn:pulumi:dev::issue-2787-javaa::pulumi:pulumi:Stack::issue-2787-javaa-dev] + kubernetes:cert-manager.io/v1:Issuer: (create) [urn=urn:pulumi:dev::issue-2787-javaa::kubernetes:cert-manager.io/v1:Issuer::issuer1] [provider=urn:pulumi:dev::issue-2787-javaa::pulumi:providers:kubernetes::default_0_0_16_SNAPSHOT::04da6b54-80e4-46f7-96ec-b56ff0331ba9] apiVersion: "cert-manager.io/v1" kind : "Issuer" metadata : { annotations: { pulumi.com/autonamed: "true" } name : "issuer1-dcda28b8" namespace : "default" } spec : { selfSigned: {} } + kubernetes:cert-manager.io/v1:Issuer: (create) [urn=urn:pulumi:dev::issue-2787-javaa::kubernetes:cert-manager.io/v1:Issuer::issuer2] [provider=urn:pulumi:dev::issue-2787-javaa::pulumi:providers:kubernetes::default_0_0_16_SNAPSHOT::04da6b54-80e4-46f7-96ec-b56ff0331ba9] apiVersion: "cert-manager.io/v1" kind : "Issuer" metadata : { annotations: { pulumi.com/autonamed: "true" } name : "issuer2-a0d8c527" namespace : "default" } spec : { selfSigned: {} } --outputs:-- issuer1_name : "issuer1-dcda28b8" issuer2_name : "issuer2-a0d8c527" issuer2_selfsigned: true ```
1 parent 491c036 commit 41b0d90

22 files changed

+2157
-2
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## Unreleased
22

3-
- Update to pulumi-java v0.12.0 #3025 (https://github.com/pulumi/pulumi-kubernetes/pull/3025)
3+
- Update to pulumi-java v0.12.0 (https://github.com/pulumi/pulumi-kubernetes/pull/3025)
44
- Fixed a panic that occurs when diffing Job resources containing `replaceUnready` annotations and an unreachable cluster connection. (https://github.com/pulumi/pulumi-kubernetes/pull/3024)
5+
- CustomResource for Java SDK (https://github.com/pulumi/pulumi-kubernetes/pull/3020)
56

67
## 4.12.0 (May 21, 2024)
78

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ python_sdk::
9797

9898
java_sdk:: PACKAGE_VERSION := $(shell pulumictl convert-version --language generic -v "$(VERSION_GENERIC)")
9999
java_sdk:: bin/pulumi-java-gen
100-
$(WORKING_DIR)/bin/$(JAVA_GEN) generate --schema $(SCHEMA_FILE) --out sdk/java --build gradle-nexus
100+
$(WORKING_DIR)/bin/$(JAVA_GEN) generate --schema $(SCHEMA_FILE) --overlay provider/pkg/gen/java-templates \
101+
--out sdk/java --build gradle-nexus
101102
cd ${PACKDIR}/java/ && \
102103
echo "module fake_java_module // Exclude this directory from Go tools\n\ngo 1.17" > go.mod && \
103104
gradle --console=plain build
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: example
2+
runtime: java
3+
description: An example for com.pulumi.kubernetes.apiextensions.CustomResource
4+
config:
5+
pulumi:tags:
6+
value:
7+
pulumi:template: ""

examples/java/customresource/pom.xml

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.pulumi</groupId>
8+
<artifactId>issue-2787-javaa</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<properties>
12+
<encoding>UTF-8</encoding>
13+
<maven.compiler.source>11</maven.compiler.source>
14+
<maven.compiler.target>11</maven.compiler.target>
15+
<maven.compiler.release>11</maven.compiler.release>
16+
<mainClass>myproject.App</mainClass>
17+
<mainArgs/>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>com.pulumi</groupId>
23+
<artifactId>pulumi</artifactId>
24+
<version>(,1.0]</version>
25+
</dependency>
26+
<dependency>
27+
<groupId>com.pulumi</groupId>
28+
<artifactId>kubernetes</artifactId>
29+
<version>(,5.0]</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>com.google.code.findbugs</groupId>
33+
<artifactId>jsr305</artifactId>
34+
<version>3.0.2</version>
35+
</dependency>
36+
</dependencies>
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.maven.plugins</groupId>
42+
<artifactId>maven-jar-plugin</artifactId>
43+
<version>3.2.2</version>
44+
<configuration>
45+
<archive>
46+
<manifest>
47+
<addClasspath>true</addClasspath>
48+
<mainClass>${mainClass}</mainClass>
49+
</manifest>
50+
</archive>
51+
</configuration>
52+
</plugin>
53+
<plugin>
54+
<groupId>org.apache.maven.plugins</groupId>
55+
<artifactId>maven-assembly-plugin</artifactId>
56+
<version>3.3.0</version>
57+
<configuration>
58+
<archive>
59+
<manifest>
60+
<addClasspath>true</addClasspath>
61+
<mainClass>${mainClass}</mainClass>
62+
</manifest>
63+
</archive>
64+
<descriptorRefs>
65+
<descriptorRef>jar-with-dependencies</descriptorRef>
66+
</descriptorRefs>
67+
</configuration>
68+
<executions>
69+
<execution>
70+
<id>make-my-jar-with-dependencies</id>
71+
<phase>package</phase>
72+
<goals>
73+
<goal>single</goal>
74+
</goals>
75+
</execution>
76+
</executions>
77+
</plugin>
78+
<plugin>
79+
<groupId>org.codehaus.mojo</groupId>
80+
<artifactId>exec-maven-plugin</artifactId>
81+
<version>3.0.0</version>
82+
<configuration>
83+
<mainClass>${mainClass}</mainClass>
84+
<commandlineArgs>${mainArgs}</commandlineArgs>
85+
</configuration>
86+
</plugin>
87+
<plugin>
88+
<groupId>org.apache.maven.plugins</groupId>
89+
<artifactId>maven-wrapper-plugin</artifactId>
90+
<version>3.1.0</version>
91+
<configuration>
92+
<mavenVersion>3.8.5</mavenVersion>
93+
</configuration>
94+
</plugin>
95+
</plugins>
96+
</build>
97+
</project>

0 commit comments

Comments
 (0)