Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!--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 ```
- Loading branch information