From 2d4c7e43a424660bcd5e79c79fe44ae3c852b3f8 Mon Sep 17 00:00:00 2001 From: Julia Plewa Date: Sat, 23 Sep 2023 16:24:11 +0200 Subject: [PATCH] fix secret + unknown output case Signed-off-by: Julia Plewa --- .../main/kotlin/com/pulumi/kotlin/Output.kt | 37 ++++++++----------- .../kotlin/com/pulumi/kotlin/OutputTest.kt | 27 +------------- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/sdk/src/main/kotlin/com/pulumi/kotlin/Output.kt b/sdk/src/main/kotlin/com/pulumi/kotlin/Output.kt index 4df4255d..81b14249 100644 --- a/sdk/src/main/kotlin/com/pulumi/kotlin/Output.kt +++ b/sdk/src/main/kotlin/com/pulumi/kotlin/Output.kt @@ -11,13 +11,13 @@ import java.util.concurrent.CompletableFuture object Output { /** - * Use this method to open an [OutputInterpolationContext] in which the unary plus operator can be used on outputs + * Use this method to open an [OutputInterpolationContext] in which the unary plus operator can be used on outputs * to interpolate their asynchronous values into an asynchronous string wrapped in an output. * ```kotlin * val myOutput = Output.of("value") * val interpolatedString = interpolate { "The value of this output is: ${+myOutput}" } * ``` - * + * * This method is an alternative to the [Output.format] method from Java. * ```java * Output myOutput = Output.of("value"); @@ -28,20 +28,9 @@ object Output { */ suspend fun interpolation(format: suspend OutputInterpolationContext.() -> String): Output { val context = OutputInterpolationContext() - return try { - val output = Output.of(context.format()) - if (context.isSecret) { - output.asSecret() - } else { - output - } - } catch (e: UnknownOutputError) { - if (context.isSecret) { - unknownOutput().asSecret() - } else { - unknownOutput() - } - } + val value = context.format() + val output = if (context.isKnown) Output.of(value) else unknownOutput() + return if (context.isSecret) output.asSecret() else output } } @@ -50,18 +39,21 @@ object Output { * * @property isSecret denotes whether any of the interpolated values contain a secret */ -class OutputInterpolationContext internal constructor(var isSecret: Boolean = false) { +class OutputInterpolationContext internal constructor( + var isSecret: Boolean = false, + var isKnown: Boolean = true, +) { /** * The unary plus operator that can be used on [Output] instances within [OutputInterpolationContext]. * * @return an asynchronous value of the [Output] in question converted to a string with [toString] */ - suspend operator fun Output.unaryPlus(): String { + suspend operator fun Output.unaryPlus(): String? { return interpolate() } // TODO: decide if we prefer this or the unary plus - suspend fun Output.interpolate(): String { + suspend fun Output.interpolate(): String? { val outputData = (this as OutputInternal) .dataAsync .await() @@ -71,13 +63,14 @@ class OutputInterpolationContext internal constructor(var isSecret: Boolean = fa if (outputData.isSecret) { this@OutputInterpolationContext.isSecret = true } + if (!outputData.isKnown) { + this@OutputInterpolationContext.isKnown = false + } - return value ?: throw UnknownOutputError() + return value } } -private class UnknownOutputError : RuntimeException("Cannot interpolate an output whose value is unknown") - private fun unknownOutput(): Output { return OutputInternal(CompletableFuture.completedFuture(OutputData.unknown())) } diff --git a/sdk/src/test/kotlin/com/pulumi/kotlin/OutputTest.kt b/sdk/src/test/kotlin/com/pulumi/kotlin/OutputTest.kt index 93490c78..7f6211c5 100644 --- a/sdk/src/test/kotlin/com/pulumi/kotlin/OutputTest.kt +++ b/sdk/src/test/kotlin/com/pulumi/kotlin/OutputTest.kt @@ -92,7 +92,7 @@ class OutputTest { @Test fun `interpolates unknown and secret outputs`() { // given - val output1 = Output.of("value1") + val output1 = OutputInternal(CompletableFuture.completedFuture(OutputData.unknown())) val output2 = Output.ofSecret("value2") val output3 = OutputInternal(CompletableFuture.completedFuture(OutputData.unknown())) @@ -114,31 +114,6 @@ class OutputTest { assertEquals(javaResult.isSecret(), result.isSecret()) } - @Test - fun `interpolates unknown and secret outputs as not secret if an unknown output appears before a secret output`() { - // given - val output1 = OutputInternal(CompletableFuture.completedFuture(OutputData.unknown())) - val output2 = Output.ofSecret("value2") - val output3 = Output.of("value1") - - // when - val result = runBlocking { - interpolation { - "output1: ${+output1}, output2: ${+output2}, output3: ${+output3}" - } - } - - // then - assertEquals(null, result.getValue()) - assertFalse(result.isKnown()) - assertFalse(result.isSecret()) // note: this isn't the desired behavior - - val javaResult = Output.format("output1: %s, output2: %s, output3: %s", output1, output2, output3) - assertEquals(javaResult.getValue(), result.getValue()) - assertEquals(javaResult.isKnown(), result.isKnown()) - assertNotEquals(javaResult.isSecret(), result.isSecret()) // note: this isn't the desired behavior - } - @Test fun `interpolates outputs that are both unknown and secret`() { // given