From cd7a05c1ebe8043ea51768d53b446cab5915d669 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 9 Jan 2025 14:12:26 +0100 Subject: [PATCH 1/2] Avoid calling invokes with dependencies on unknown resources (#1577) Java implementation of https://github.com/pulumi/pulumi/pull/18133 DependsOn for resources is an ordering constraint for register resource calls. If a resource R1 depends on a resource R2, the register resource call for R2 will happen after R1. This is ensured by awaiting the URN for each resource dependency before calling register resource. For invokes, this causes a problem when running under preview. During preview, register resource immediately returns with the URN, however this does not tell us if the resource "exists". Instead of waiting for the dependency's URN, we wait for the ID. This tells us that whether a physical resource exists (if the state is in sync), and we can avoid calling the invoke when it is unknown. --- CHANGELOG_PENDING.md | 3 +- .../deployment/internal/DeploymentImpl.java | 66 ++++++++++++------- .../DeploymentInvokeDependsOnTest.java | 38 ++++++++++- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 47aeeaa254e..851d0fcc2a8 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,5 +1,6 @@ ### Improvements - Fix emitted functions for `assetArchive` and `remoteAsset` in generated programs + - Avoid calling invokes with dependencies on unknown resources -### Bug Fixes \ No newline at end of file +### Bug Fixes diff --git a/sdk/java/pulumi/src/main/java/com/pulumi/deployment/internal/DeploymentImpl.java b/sdk/java/pulumi/src/main/java/com/pulumi/deployment/internal/DeploymentImpl.java index fde9c0ed690..95f31aa09f5 100644 --- a/sdk/java/pulumi/src/main/java/com/pulumi/deployment/internal/DeploymentImpl.java +++ b/sdk/java/pulumi/src/main/java/com/pulumi/deployment/internal/DeploymentImpl.java @@ -91,6 +91,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -541,23 +542,36 @@ public Output invoke(String token, TypeShape targetType, InvokeArgs ar ? CompletableFuture.completedFuture(null) : packageRef; - // Find all the resource dependencies from dependsOn. We need to wait for these futures to complete - // before calling the invoke. - var depsFuture = this.prepare.getAllTransitivelyReferencedResourceUrnsAsync(ImmutableSet.copyOf(options.getDependsOn())); + // The expanded set of dependencies, including children of components. + var transitiveDeps = this.prepare.getAllTransitivelyReferencedResources(ImmutableSet.copyOf(options.getDependsOn())); + // If we depend on any CustomResources, we need to ensure that their + // ID is known before proceeding. If it is not known, we will return + // an unknown result. + var hasUnknownIDs = CompletableFutures.allOf(transitiveDeps + .filter(r -> r instanceof CustomResource) + .map(r -> Internal.of(((CustomResource) r).id()).isKnown()) + .collect(ImmutableSet.toImmutableSet()) + ).thenApply(s -> s.stream().anyMatch(known -> !known)); // Wait for all values from args to be available, and then perform the RPC. - return new OutputInternal<>(this.featureSupport.monitorSupportsResourceReferences() - .thenCompose(keepResources -> this.serializeInvokeArgs(token, args, keepResources)) - .thenCompose(serializedArgs -> { - if (!serializedArgs.containsUnknowns) { - return CompletableFuture.allOf(depsFuture, packageRefFuture) - .thenCompose(v -> this.invokeRawAsync(token, serializedArgs, options, packageRefFuture.join())) - .thenApply(result -> parseInvokeResponse(token, targetType, result)) - .thenApply(output -> output.withDependencies(options.getDependsOn())); - } else { - return CompletableFuture.completedFuture(OutputData.unknown()); - } - })); + return new OutputInternal<>(CompletableFuture.allOf( + this.featureSupport.monitorSupportsResourceReferences(), + hasUnknownIDs) + .thenCompose(ignored -> { + boolean keepResources = this.featureSupport.monitorSupportsResourceReferences().join(); + boolean hasUnknown = hasUnknownIDs.join(); + + return this.serializeInvokeArgs(token, args, keepResources) + .thenCompose(serializedArgs -> { + if (serializedArgs.containsUnknowns || hasUnknown) { + return CompletableFuture.completedFuture(OutputData.unknown()); + } else { + return packageRefFuture + .thenCompose(packageRefString -> this.invokeRawAsync(token, serializedArgs, options, packageRefString)) + .thenApply(result -> parseInvokeResponse(token, targetType, result)); + } + }); + })); } private OutputData parseInvokeResponse( @@ -1073,7 +1087,7 @@ private CompletableFuture> gatherExplicitDependenciesAsync(Output return Internal.of(resources).getValueOrDefault(List.of()); } - private CompletableFuture> getAllTransitivelyReferencedResourceUrnsAsync( + private Stream getAllTransitivelyReferencedResources( ImmutableSet resources ) { // Go through 'resources', but transitively walk through **Component** resources, collecting any @@ -1112,14 +1126,18 @@ private CompletableFuture> getAllTransitivelyReferencedReso return Internal.from(resource).getRemote(); } return false; // Unreachable - }) - .map(resource -> Internal.of(resource.urn()).getValueOrDefault("")) - .collect(toImmutableSet()); - return CompletableFutures.allOf(transitivelyReachableCustomResources) - .thenApply(ts -> ts.stream() - .filter(Strings::isNonEmptyOrNull) - .collect(toImmutableSet()) - ); + }); + return transitivelyReachableCustomResources; + } + + private CompletableFuture> getAllTransitivelyReferencedResourceUrnsAsync( + ImmutableSet resources) { + var urns = getAllTransitivelyReferencedResources(resources) + .map(resource -> Internal.of(resource.urn()).getValueOrDefault("")); + return CompletableFutures.allOf(urns.collect(ImmutableSet.toImmutableSet())) + .thenApply(strings -> strings.stream().filter(Strings::isNonEmptyOrNull)) + .thenApply(urn -> urn.collect(ImmutableSet.toImmutableSet()) + ); } /** diff --git a/sdk/java/pulumi/src/test/java/com/pulumi/deployment/DeploymentInvokeDependsOnTest.java b/sdk/java/pulumi/src/test/java/com/pulumi/deployment/DeploymentInvokeDependsOnTest.java index 673cbc781ef..dccbffda781 100644 --- a/sdk/java/pulumi/src/test/java/com/pulumi/deployment/DeploymentInvokeDependsOnTest.java +++ b/sdk/java/pulumi/src/test/java/com/pulumi/deployment/DeploymentInvokeDependsOnTest.java @@ -8,7 +8,6 @@ import com.pulumi.core.annotations.CustomType.Setter; import com.pulumi.core.annotations.Import; import com.pulumi.core.annotations.ResourceType; -import com.pulumi.core.internal.Internal; import com.pulumi.deployment.internal.Runner; import com.pulumi.resources.Resource; import com.pulumi.resources.InvokeArgs; @@ -30,6 +29,7 @@ import java.lang.Thread; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; public class DeploymentInvokeDependsOnTest { @@ -43,7 +43,7 @@ void testInvokesDependsOn() { var marker = new ResolveMarker(); var test = PulumiTestInternal.builder() - .options(TestOptions.builder().preview(true).build()) + .options(TestOptions.builder().build()) .mocks(new Mocks() { @Override public CompletableFuture newResourceAsync(ResourceArgs args) { @@ -92,6 +92,40 @@ public CompletableFuture> callAsync(CallArgs args) { assertThat(result.exitCode()).isEqualTo(Runner.ProcessExitedSuccessfully); } + @Test + void testInvokesDependsOnUnknown() { + var test = PulumiTestInternal.builder() + .options(TestOptions.builder().preview(true).build()) + .mocks(new Mocks() { + @Override + public CompletableFuture newResourceAsync(ResourceArgs args) { + return CompletableFuture + .supplyAsync(() -> ResourceResult.of(Optional.empty(), ImmutableMap.of())); + } + + @Override + public CompletableFuture> callAsync(CallArgs args) { + return CompletableFuture.completedFuture(ImmutableMap.of()); + } + }) + .build(); + + var result = test.runTest(ctx -> { + var res = new MyCustomResource("r1", null, CustomResourceOptions.builder().build()); + var deps = new ArrayList(); + deps.add(res); + + var opts = new InvokeOutputOptions(null, null, null, deps); + CustomInvokes.doStuff(CustomArgs.Empty, opts).applyValue(r -> { + assertFalse(true, "invoke should not be called!"); + return r; + }); + }); + + assertThat(result.exceptions()).hasSize(0); + assertThat(result.exitCode()).isEqualTo(Runner.ProcessExitedSuccessfully); + } + public static final class MyArgs extends ResourceArgs { } From 70c572ae0cf798b2bcfd6b32f71ee246f4d18717 Mon Sep 17 00:00:00 2001 From: Zaid Ajaj Date: Thu, 9 Jan 2025 15:32:21 +0100 Subject: [PATCH 2/2] [program-gen] Fix generation of `double` literals in generated programs (#1580) ### Description This PR implements an initial pass for handling `__convert` calls in program-gen starting with `double` literals. Later on we will extend this to handle enums but we don't have a conformance test for these yet. The return type of `__convert` calls is annotated with the target schema type so we detect when are going to emit an expression of type `model.NumberType` (in PCL space) in a location that expects `schema.NumberType`. When that pattern is detected we make sure to emit double literals that end with `.0` when the value of the number is exact. - Fixes #1565 - Unskips `l2-resource-primitive` now that the test is passing - ~~Removes `l2-map-keys` as there is no such test~~ --- CHANGELOG_PENDING.md | 1 + pkg/cmd/pulumi-language-java/language_test.go | 3 +- .../l2-resource-primitives/Pulumi.yaml | 2 + .../projects/l2-resource-primitives/pom.xml | 99 ++++++++++ .../src/main/java/generated_program/App.java | 37 ++++ .../testdata/sdks/primitive-7.0.0/README.md | 1 + .../sdks/primitive-7.0.0/build.gradle | 157 ++++++++++++++++ .../sdks/primitive-7.0.0/settings.gradle | 14 ++ .../java/com/pulumi/primitive/Provider.java | 54 ++++++ .../com/pulumi/primitive/ProviderArgs.java | 28 +++ .../java/com/pulumi/primitive/Resource.java | 113 ++++++++++++ .../com/pulumi/primitive/ResourceArgs.java | 174 ++++++++++++++++++ .../java/com/pulumi/primitive/Utilities.java | 102 ++++++++++ pkg/codegen/java/gen_program.go | 4 +- pkg/codegen/java/gen_program_expressions.go | 38 +++- .../azure-native-pp/java/azure-native.java | 8 +- .../java/kubernetes-pod.java | 2 +- 17 files changed, 826 insertions(+), 11 deletions(-) create mode 100644 pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/Pulumi.yaml create mode 100644 pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/pom.xml create mode 100644 pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/src/main/java/generated_program/App.java create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/README.md create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/build.gradle create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/settings.gradle create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Provider.java create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ProviderArgs.java create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Resource.java create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ResourceArgs.java create mode 100644 pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Utilities.java diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 851d0fcc2a8..60411c8aa23 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,7 @@ ### Improvements - Fix emitted functions for `assetArchive` and `remoteAsset` in generated programs + - Fix generation of `double` literals in generated programs - Avoid calling invokes with dependencies on unknown resources ### Bug Fixes diff --git a/pkg/cmd/pulumi-language-java/language_test.go b/pkg/cmd/pulumi-language-java/language_test.go index 72327e42f86..8bb6075db19 100644 --- a/pkg/cmd/pulumi-language-java/language_test.go +++ b/pkg/cmd/pulumi-language-java/language_test.go @@ -166,12 +166,11 @@ var expectedFailures = map[string]string{ "l2-invoke-options": "#1563 Invoke argument and result handling", "l2-invoke-options-depends-on": "#1563 Invoke argument and result handling", "l2-invoke-secrets": "#1563 Invoke argument and result handling", - "l2-map-keys": "#1569 Spot and qualify duplicate identifiers", + "l2-map-keys": "#1561 Map literals are not generated correctly", "l2-provider-grpc-config-schema-secret": "#1568 Don't generate duplicate files", "l2-provider-grpc-config-secret": "#1568 Don't generate duplicate files", "l2-provider-grpc-config": "#1568 Don't generate duplicate files", "l2-resource-config": "#1566 Fix l2-resource-config / plugin download URL code generation", - "l2-resource-primitives": "#1565 Better generation of numeric literals", "l2-resource-secret": "#1564 Fix l2-resource-secret", } diff --git a/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/Pulumi.yaml b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/Pulumi.yaml new file mode 100644 index 00000000000..6959f9aaf07 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/Pulumi.yaml @@ -0,0 +1,2 @@ +name: l2-resource-primitives +runtime: java diff --git a/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/pom.xml b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/pom.xml new file mode 100644 index 00000000000..8954c60fb57 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + com.pulumi + l2-resource-primitives + 1.0-SNAPSHOT + + + UTF-8 + 11 + 11 + 11 + generated_program.App + + + + + + repository-0 + REPOSITORY + + + + + + com.pulumi + pulumi + CORE.VERSION + + + com.pulumi + primitive + 7.0.0 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + true + ${mainClass} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.4.2 + + + + true + ${mainClass} + + + + jar-with-dependencies + + + + + make-my-jar-with-dependencies + package + + single + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + ${mainClass} + ${mainArgs} + + + + org.apache.maven.plugins + maven-wrapper-plugin + 3.1.1 + + 3.8.5 + + + + + \ No newline at end of file diff --git a/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/src/main/java/generated_program/App.java b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/src/main/java/generated_program/App.java new file mode 100644 index 00000000000..52a3fb05f33 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/projects/l2-resource-primitives/src/main/java/generated_program/App.java @@ -0,0 +1,37 @@ +package generated_program; + +import com.pulumi.Context; +import com.pulumi.Pulumi; +import com.pulumi.core.Output; +import com.pulumi.primitive.Resource; +import com.pulumi.primitive.ResourceArgs; +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) { + var res = new Resource("res", ResourceArgs.builder() + .boolean_(true) + .float_(3.14) + .integer(42) + .string("hello") + .numberArray( + -1.0, + 0.0, + 1.0) + .booleanMap(Map.ofEntries( + Map.entry("t", true), + Map.entry("f", false) + )) + .build()); + + } +} diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/README.md b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/README.md new file mode 100644 index 00000000000..8d1c8b69c3f --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/README.md @@ -0,0 +1 @@ + diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/build.gradle b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/build.gradle new file mode 100644 index 00000000000..4b1f33e7a1b --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/build.gradle @@ -0,0 +1,157 @@ +// *** WARNING: this file was generated by pulumi-java-gen *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +plugins { + id("signing") + id("java-library") + id("maven-publish") +} + +group = "com.pulumi" + +def resolvedVersion = System.getenv("PACKAGE_VERSION") ?: + (project.version == "unspecified" + ? "7.0.0" + : project.version) + +def signingKey = System.getenv("SIGNING_KEY") +def signingPassword = System.getenv("SIGNING_PASSWORD") +def publishRepoURL = System.getenv("PUBLISH_REPO_URL") +def publishRepoUsername = System.getenv("PUBLISH_REPO_USERNAME") +def publishRepoPassword = System.getenv("PUBLISH_REPO_PASSWORD") + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +compileJava { + options.fork = true + options.forkOptions.jvmArgs.addAll(["-Xmx16g"]) + options.encoding = "UTF-8" +} + +repositories { + maven { + url("REPOSITORY") + } + mavenLocal() + maven { // The google mirror is less flaky than mavenCentral() + url("https://maven-central.storage-download.googleapis.com/maven2/") + } + mavenCentral() +} + +dependencies { + implementation("com.google.code.findbugs:jsr305:3.0.2") + implementation("com.google.code.gson:gson:2.8.9") + implementation("com.pulumi:pulumi:CORE.VERSION") +} + +task sourcesJar(type: Jar) { + from sourceSets.main.allJava + archiveClassifier.set('sources') +} + +task javadocJar(type: Jar) { + from javadoc + archiveClassifier.set('javadoc') + zip64 = true +} + +def genPulumiResources = tasks.register('genPulumiResources') { + doLast { + def resourcesDir = sourceSets.main.output.resourcesDir + def subDir = project.name.replace(".", "/") + def outDir = file("$resourcesDir/$subDir") + outDir.mkdirs() + new File(outDir, "version.txt").text = resolvedVersion + def builder = new groovy.json.JsonBuilder() + builder { + resource true + name "primitive" + version resolvedVersion + } + def infoJson = builder.toPrettyString() + new File(outDir, "plugin.json").text = infoJson + } +} + +jar.configure { + dependsOn genPulumiResources +} + +publishing { + publications { + mainPublication(MavenPublication) { + groupId = "com.pulumi" + artifactId = "primitive" + version = resolvedVersion + from components.java + artifact sourcesJar + artifact javadocJar + + pom { + inceptionYear = "" + name = "" + packaging = "jar" + description = " " + + url = "https://example.com" + + scm { + connection = "https://example.com" + developerConnection = "https://example.com" + url = "https://example.com" + } + + licenses { + license { + name = "" + url = "" + } + } + + developers { + developer { + id = "" + name = "" + email = "" + } + } + } + } + } + + if (publishRepoURL) { + repositories { + maven { + name = "PublishRepo" + url = publishRepoURL + credentials { + username = publishRepoUsername + password = publishRepoPassword + } + } + } + } +} + +javadoc { + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption('html5', true) + } + options.jFlags("-Xmx8g", "-Xms512m") +} + +jar { + zip64 = true +} + +if (signingKey) { + signing { + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications.mainPublication + } +} \ No newline at end of file diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/settings.gradle b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/settings.gradle new file mode 100644 index 00000000000..397ed828987 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/settings.gradle @@ -0,0 +1,14 @@ +// *** WARNING: this file was generated by pulumi-java-gen. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +pluginManagement { + repositories { + maven { // The google mirror is less flaky than mavenCentral() + url("https://maven-central.storage-download.googleapis.com/maven2/") + } + gradlePluginPortal() + } +} + +rootProject.name = "com.pulumi.primitive" +include("lib") diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Provider.java b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Provider.java new file mode 100644 index 00000000000..615bf00d8b4 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Provider.java @@ -0,0 +1,54 @@ +// *** WARNING: this file was generated by pulumi-language-java. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.primitive; + +import com.pulumi.core.Output; +import com.pulumi.core.annotations.ResourceType; +import com.pulumi.core.internal.Codegen; +import com.pulumi.primitive.ProviderArgs; +import com.pulumi.primitive.Utilities; +import javax.annotation.Nullable; + +@ResourceType(type="pulumi:providers:primitive") +public class Provider extends com.pulumi.resources.ProviderResource { + /** + * + * @param name The _unique_ name of the resulting resource. + */ + public Provider(java.lang.String name) { + this(name, ProviderArgs.Empty); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + */ + public Provider(java.lang.String name, @Nullable ProviderArgs args) { + this(name, args, null); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + * @param options A bag of options that control this resource's behavior. + */ + public Provider(java.lang.String name, @Nullable ProviderArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { + super("primitive", name, makeArgs(args, options), makeResourceOptions(options, Codegen.empty()), false); + } + + private static ProviderArgs makeArgs(@Nullable ProviderArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { + if (options != null && options.getUrn().isPresent()) { + return null; + } + return args == null ? ProviderArgs.Empty : args; + } + + private static com.pulumi.resources.CustomResourceOptions makeResourceOptions(@Nullable com.pulumi.resources.CustomResourceOptions options, @Nullable Output id) { + var defaultOptions = com.pulumi.resources.CustomResourceOptions.builder() + .version(Utilities.getVersion()) + .build(); + return com.pulumi.resources.CustomResourceOptions.merge(defaultOptions, options, id); + } + +} diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ProviderArgs.java b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ProviderArgs.java new file mode 100644 index 00000000000..6060ca90506 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ProviderArgs.java @@ -0,0 +1,28 @@ +// *** WARNING: this file was generated by pulumi-language-java. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.primitive; + + + + +public final class ProviderArgs extends com.pulumi.resources.ResourceArgs { + + public static final ProviderArgs Empty = new ProviderArgs(); + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private ProviderArgs $; + + public Builder() { + $ = new ProviderArgs(); + } + public ProviderArgs build() { + return $; + } + } + +} diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Resource.java b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Resource.java new file mode 100644 index 00000000000..23f62fe893a --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Resource.java @@ -0,0 +1,113 @@ +// *** WARNING: this file was generated by pulumi-language-java. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.primitive; + +import com.pulumi.core.Output; +import com.pulumi.core.annotations.Export; +import com.pulumi.core.annotations.ResourceType; +import com.pulumi.core.internal.Codegen; +import com.pulumi.primitive.ResourceArgs; +import com.pulumi.primitive.Utilities; +import java.lang.Boolean; +import java.lang.Double; +import java.lang.Integer; +import java.lang.String; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +@ResourceType(type="primitive:index:Resource") +public class Resource extends com.pulumi.resources.CustomResource { + @Export(name="boolean", refs={Boolean.class}, tree="[0]") + private Output boolean_; + + public Output boolean_() { + return this.boolean_; + } + @Export(name="booleanMap", refs={Map.class,String.class,Boolean.class}, tree="[0,1,2]") + private Output> booleanMap; + + public Output> booleanMap() { + return this.booleanMap; + } + @Export(name="float", refs={Double.class}, tree="[0]") + private Output float_; + + public Output float_() { + return this.float_; + } + @Export(name="integer", refs={Integer.class}, tree="[0]") + private Output integer; + + public Output integer() { + return this.integer; + } + @Export(name="numberArray", refs={List.class,Double.class}, tree="[0,1]") + private Output> numberArray; + + public Output> numberArray() { + return this.numberArray; + } + @Export(name="string", refs={String.class}, tree="[0]") + private Output string; + + public Output string() { + return this.string; + } + + /** + * + * @param name The _unique_ name of the resulting resource. + */ + public Resource(java.lang.String name) { + this(name, ResourceArgs.Empty); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + */ + public Resource(java.lang.String name, ResourceArgs args) { + this(name, args, null); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + * @param options A bag of options that control this resource's behavior. + */ + public Resource(java.lang.String name, ResourceArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { + super("primitive:index:Resource", name, makeArgs(args, options), makeResourceOptions(options, Codegen.empty()), false); + } + + private Resource(java.lang.String name, Output id, @Nullable com.pulumi.resources.CustomResourceOptions options) { + super("primitive:index:Resource", name, null, makeResourceOptions(options, id), false); + } + + private static ResourceArgs makeArgs(ResourceArgs args, @Nullable com.pulumi.resources.CustomResourceOptions options) { + if (options != null && options.getUrn().isPresent()) { + return null; + } + return args == null ? ResourceArgs.Empty : args; + } + + private static com.pulumi.resources.CustomResourceOptions makeResourceOptions(@Nullable com.pulumi.resources.CustomResourceOptions options, @Nullable Output id) { + var defaultOptions = com.pulumi.resources.CustomResourceOptions.builder() + .version(Utilities.getVersion()) + .build(); + return com.pulumi.resources.CustomResourceOptions.merge(defaultOptions, options, id); + } + + /** + * Get an existing Host resource's state with the given name, ID, and optional extra + * properties used to qualify the lookup. + * + * @param name The _unique_ name of the resulting resource. + * @param id The _unique_ provider ID of the resource to lookup. + * @param options Optional settings to control the behavior of the CustomResource. + */ + public static Resource get(java.lang.String name, Output id, @Nullable com.pulumi.resources.CustomResourceOptions options) { + return new Resource(name, id, options); + } +} diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ResourceArgs.java b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ResourceArgs.java new file mode 100644 index 00000000000..02982a2772d --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/ResourceArgs.java @@ -0,0 +1,174 @@ +// *** WARNING: this file was generated by pulumi-language-java. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.primitive; + +import com.pulumi.core.Output; +import com.pulumi.core.annotations.Import; +import com.pulumi.exceptions.MissingRequiredPropertyException; +import java.lang.Boolean; +import java.lang.Double; +import java.lang.Integer; +import java.lang.String; +import java.util.List; +import java.util.Map; +import java.util.Objects; + + +public final class ResourceArgs extends com.pulumi.resources.ResourceArgs { + + public static final ResourceArgs Empty = new ResourceArgs(); + + @Import(name="boolean", required=true) + private Output boolean_; + + public Output boolean_() { + return this.boolean_; + } + + @Import(name="booleanMap", required=true) + private Output> booleanMap; + + public Output> booleanMap() { + return this.booleanMap; + } + + @Import(name="float", required=true) + private Output float_; + + public Output float_() { + return this.float_; + } + + @Import(name="integer", required=true) + private Output integer; + + public Output integer() { + return this.integer; + } + + @Import(name="numberArray", required=true) + private Output> numberArray; + + public Output> numberArray() { + return this.numberArray; + } + + @Import(name="string", required=true) + private Output string; + + public Output string() { + return this.string; + } + + private ResourceArgs() {} + + private ResourceArgs(ResourceArgs $) { + this.boolean_ = $.boolean_; + this.booleanMap = $.booleanMap; + this.float_ = $.float_; + this.integer = $.integer; + this.numberArray = $.numberArray; + this.string = $.string; + } + + public static Builder builder() { + return new Builder(); + } + public static Builder builder(ResourceArgs defaults) { + return new Builder(defaults); + } + + public static final class Builder { + private ResourceArgs $; + + public Builder() { + $ = new ResourceArgs(); + } + + public Builder(ResourceArgs defaults) { + $ = new ResourceArgs(Objects.requireNonNull(defaults)); + } + + public Builder boolean_(Output boolean_) { + $.boolean_ = boolean_; + return this; + } + + public Builder boolean_(Boolean boolean_) { + return boolean_(Output.of(boolean_)); + } + + public Builder booleanMap(Output> booleanMap) { + $.booleanMap = booleanMap; + return this; + } + + public Builder booleanMap(Map booleanMap) { + return booleanMap(Output.of(booleanMap)); + } + + public Builder float_(Output float_) { + $.float_ = float_; + return this; + } + + public Builder float_(Double float_) { + return float_(Output.of(float_)); + } + + public Builder integer(Output integer) { + $.integer = integer; + return this; + } + + public Builder integer(Integer integer) { + return integer(Output.of(integer)); + } + + public Builder numberArray(Output> numberArray) { + $.numberArray = numberArray; + return this; + } + + public Builder numberArray(List numberArray) { + return numberArray(Output.of(numberArray)); + } + + public Builder numberArray(Double... numberArray) { + return numberArray(List.of(numberArray)); + } + + public Builder string(Output string) { + $.string = string; + return this; + } + + public Builder string(String string) { + return string(Output.of(string)); + } + + public ResourceArgs build() { + if ($.boolean_ == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "boolean_"); + } + if ($.booleanMap == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "booleanMap"); + } + if ($.float_ == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "float_"); + } + if ($.integer == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "integer"); + } + if ($.numberArray == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "numberArray"); + } + if ($.string == null) { + throw new MissingRequiredPropertyException("ResourceArgs", "string"); + } + return $; + } + } + +} diff --git a/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Utilities.java b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Utilities.java new file mode 100644 index 00000000000..8d16814ccf9 --- /dev/null +++ b/pkg/cmd/pulumi-language-java/testdata/sdks/primitive-7.0.0/src/main/java/com/pulumi/primitive/Utilities.java @@ -0,0 +1,102 @@ +// *** WARNING: this file was generated by pulumi-language-java. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.primitive; + + + + + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import com.pulumi.core.internal.Environment; +import com.pulumi.deployment.InvokeOptions; +import com.pulumi.deployment.InvokeOutputOptions; + +public class Utilities { + + public static Optional getEnv(java.lang.String... names) { + for (var n : names) { + var value = Environment.getEnvironmentVariable(n); + if (value.isValue()) { + return Optional.of(value.value()); + } + } + return Optional.empty(); + } + + public static Optional getEnvBoolean(java.lang.String... names) { + for (var n : names) { + var value = Environment.getBooleanEnvironmentVariable(n); + if (value.isValue()) { + return Optional.of(value.value()); + } + } + return Optional.empty(); + } + + public static Optional getEnvInteger(java.lang.String... names) { + for (var n : names) { + var value = Environment.getIntegerEnvironmentVariable(n); + if (value.isValue()) { + return Optional.of(value.value()); + } + } + return Optional.empty(); + } + + public static Optional getEnvDouble(java.lang.String... names) { + for (var n : names) { + var value = Environment.getDoubleEnvironmentVariable(n); + if (value.isValue()) { + return Optional.of(value.value()); + } + } + return Optional.empty(); + } + + public static InvokeOptions withVersion(@Nullable InvokeOptions options) { + if (options != null && options.getVersion().isPresent()) { + return options; + } + return new InvokeOptions( + options == null ? null : options.getParent().orElse(null), + options == null ? null : options.getProvider().orElse(null), + getVersion() + ); + } + + public static InvokeOutputOptions withVersion(@Nullable InvokeOutputOptions options) { + if (options != null && options.getVersion().isPresent()) { + return options; + } + return new InvokeOutputOptions( + options == null ? null : options.getParent().orElse(null), + options == null ? null : options.getProvider().orElse(null), + getVersion(), + options == null ? null : options.getDependsOn() + ); + } + + private static final java.lang.String version; + public static java.lang.String getVersion() { + return version; + } + + static { + var resourceName = "com/pulumi/primitive/version.txt"; + var versionFile = Utilities.class.getClassLoader().getResourceAsStream(resourceName); + if (versionFile == null) { + throw new IllegalStateException( + java.lang.String.format("expected resource '%s' on Classpath, not found", resourceName) + ); + } + version = new BufferedReader(new InputStreamReader(versionFile)) + .lines() + .collect(Collectors.joining("\n")) + .trim(); + } +} diff --git a/pkg/codegen/java/gen_program.go b/pkg/codegen/java/gen_program.go index 77a4399d27f..88e9ec30116 100644 --- a/pkg/codegen/java/gen_program.go +++ b/pkg/codegen/java/gen_program.go @@ -1069,9 +1069,9 @@ func (g *generator) genResource(w io.Writer, resource *pcl.Resource) { // latter will typically be a union of the type we've computed and one or more output types. This may result // in inaccurate code generation later on. Arguably this is a bug in the generator, but this will have to do // for now. - _, diagnostics := resource.InputType.Traverse(hcl.TraverseAttr{Name: input.Name}) + targetType, diagnostics := resource.InputType.Traverse(hcl.TraverseAttr{Name: input.Name}) g.diagnostics = append(g.diagnostics, diagnostics...) - value := g.lowerExpression(input.Value, input.Type()) + value := g.lowerExpression(input.Value, targetType.(model.Type)) input.Value = value } } diff --git a/pkg/codegen/java/gen_program_expressions.go b/pkg/codegen/java/gen_program_expressions.go index 95d2286dd92..e6b6733613d 100644 --- a/pkg/codegen/java/gen_program_expressions.go +++ b/pkg/codegen/java/gen_program_expressions.go @@ -192,14 +192,48 @@ func isTemplatePathString(expr model.Expression) (bool, []model.Expression) { } } +func (g *generator) genIntrinsic(w io.Writer, from model.Expression, to model.Type) { + targetType := pcl.LowerConversion(from, to) + output, isOutput := targetType.(*model.OutputType) + if isOutput { + targetType = output.ElementType + } + + if targetType.Equals(model.NumberType) { + if schemaType, ok := pcl.GetSchemaForType(to); ok { + if inputType, ok := schemaType.(*schema.InputType); ok { + schemaType = inputType.ElementType + } + + if expr, ok := from.(*model.LiteralValueExpression); ok && schemaType == schema.NumberType { + bf := expr.Value.AsBigFloat() + if i, acc := bf.Int64(); acc == big.Exact { + g.Fgenf(w, "%d.0", i) + return + } + + f, _ := bf.Float64() + g.Fgenf(w, "%g", f) + return + } + } + } + + g.Fgenf(w, "%.v", from) +} + func (g *generator) GenFunctionCallExpression(w io.Writer, expr *model.FunctionCallExpression) { switch expr.Name { case pcl.IntrinsicConvert: switch arg := expr.Args[0].(type) { case *model.ObjectConsExpression: - g.genObjectConsExpression(w, arg, &schema.MapType{ElementType: schema.StringType}) + if schemaType, ok := pcl.GetSchemaForType(expr.Signature.ReturnType); ok { + g.genObjectConsExpression(w, arg, schemaType) + } else { + g.genObjectConsExpression(w, arg, &schema.MapType{ElementType: schema.StringType}) + } default: - g.Fgenf(w, "%.v", expr.Args[0]) // <- probably wrong w.r.t. precedence + g.genIntrinsic(w, expr.Args[0], expr.Signature.ReturnType) } case pcl.IntrinsicApply: g.genApply(w, expr) diff --git a/pkg/codegen/testing/test/testdata/azure-native-pp/java/azure-native.java b/pkg/codegen/testing/test/testdata/azure-native-pp/java/azure-native.java index d963cf37032..cf7c3e3b65d 100644 --- a/pkg/codegen/testing/test/testdata/azure-native-pp/java/azure-native.java +++ b/pkg/codegen/testing/test/testdata/azure-native-pp/java/azure-native.java @@ -46,18 +46,18 @@ public static void stack(Context ctx) { .odataType("#Microsoft.Azure.Cdn.Models.DeliveryRuleCacheExpirationActionParameters") .build()) .build(), - DeliveryRuleCacheExpirationActionArgs.builder() + DeliveryRuleResponseHeaderActionArgs.builder() .name("ModifyResponseHeader") - .parameters(CacheExpirationActionParametersArgs.builder() + .parameters(HeaderActionParametersArgs.builder() .headerAction("Overwrite") .headerName("Access-Control-Allow-Origin") .odataType("#Microsoft.Azure.Cdn.Models.DeliveryRuleHeaderActionParameters") .value("*") .build()) .build(), - DeliveryRuleCacheExpirationActionArgs.builder() + DeliveryRuleRequestHeaderActionArgs.builder() .name("ModifyRequestHeader") - .parameters(CacheExpirationActionParametersArgs.builder() + .parameters(HeaderActionParametersArgs.builder() .headerAction("Overwrite") .headerName("Accept-Encoding") .odataType("#Microsoft.Azure.Cdn.Models.DeliveryRuleHeaderActionParameters") diff --git a/pkg/codegen/testing/test/testdata/kubernetes-pod-pp/java/kubernetes-pod.java b/pkg/codegen/testing/test/testdata/kubernetes-pod-pp/java/kubernetes-pod.java index fa7e3875548..1f0b2706c01 100644 --- a/pkg/codegen/testing/test/testdata/kubernetes-pod-pp/java/kubernetes-pod.java +++ b/pkg/codegen/testing/test/testdata/kubernetes-pod-pp/java/kubernetes-pod.java @@ -34,7 +34,7 @@ public static void stack(Context ctx) { .resources(ResourceRequirementsArgs.builder() .limits(Map.ofEntries( Map.entry("memory", "20Mi"), - Map.entry("cpu", 0.2) + Map.entry("cpu", "0.2") )) .build()) .build())