Skip to content

Commit

Permalink
fix secret + unknown output case
Browse files Browse the repository at this point in the history
Signed-off-by: Julia Plewa <jplewa@virtuslab.com>
  • Loading branch information
jplewa committed Sep 23, 2023
1 parent f774aea commit 2d4c7e4
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 48 deletions.
37 changes: 15 additions & 22 deletions sdk/src/main/kotlin/com/pulumi/kotlin/Output.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> myOutput = Output.of("value");
Expand All @@ -28,20 +28,9 @@ object Output {
*/
suspend fun interpolation(format: suspend OutputInterpolationContext.() -> String): Output<String> {
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<String>().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
}
}

Expand All @@ -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 <T> Output<T>.unaryPlus(): String {
suspend operator fun <T> Output<T>.unaryPlus(): String? {
return interpolate()
}

// TODO: decide if we prefer this or the unary plus
suspend fun <T> Output<T>.interpolate(): String {
suspend fun <T> Output<T>.interpolate(): String? {
val outputData = (this as OutputInternal<T>)
.dataAsync
.await()
Expand All @@ -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 <T> unknownOutput(): Output<T> {
return OutputInternal(CompletableFuture.completedFuture(OutputData.unknown()))
}
27 changes: 1 addition & 26 deletions sdk/src/test/kotlin/com/pulumi/kotlin/OutputTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>()))
val output2 = Output.ofSecret("value2")
val output3 = OutputInternal(CompletableFuture.completedFuture(OutputData.unknown<String>()))

Expand All @@ -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<String>()))
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
Expand Down

0 comments on commit 2d4c7e4

Please sign in to comment.