I figured out a way to automatically apply a CfnCondition to every resource in the stack using the Construct#onPrepare hook in the Stack
From the above page, Construct#onPrepare()
is:
Perform final modifications before synthesis
This method can be implemented by derived constructs in order to perform final changes before synthesis. prepare() will be called after child constructs have been prepared.
This is an advanced framework feature. Only use this if you understand the implications.
The following CDK code (example code - I only put in the relevant bits here) is in Kotlin but
would apply to any
supported CDK
language:
class MyStack internal constructor(app: App, name: String) : Stack(app, name) {
private lateinit var myCondition: CfnCondition
init {
myCondition = CfnCondition.Builder.create(this, "myCondition")
.expression(Fn.condition...) // Fill in your condition
.build()
}
/**
* Use the `Stack#onPrepare` hook to find all CF resources and apply our stack standard CF condition on them.
* See:
* * [Stack.onPrepare]: https://docs.aws.amazon.com/cdk/api/latest/typescript/api/core/construct.html#core_Construct_onPrepare
*/
override fun onPrepare() {
super.onPrepare()
findAllCfnResources(this.node).forEach { it.cfnOptions.condition = this.myCondition }
}
/**
* Recurse through all children nodes and accumulate [CfnResource] nodes.
*/
private fun findAllCfnResources(node: ConstructNode): List<CfnResource> {
val nodes = node.children.map { it.node } + node
val cfnResources: List<CfnResource> = nodes.flatMap { it.findAll() }.mapNotNull { it as? CfnResource }
if (node.children.isEmpty()) {
return cfnResources
}
return node.children.fold(cfnResources) {
accumulatedCfnResources, it -> accumulatedCfnResources + findAllCfnResources(it.node)
}
}
}
A unit-test (Junit and AssertJ, also in Kotlin) I added verifies that this covers all resources:
private val mapper = ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT)
private lateinit var stack: Stack
private lateinit var synthesizedStackCfTemplate: JsonNode
@BeforeEach
fun setUp() {
val app = App()
stack = MyStack(app, "MyUnitTestStack")
synthesizedStackCfTemplate = mapper.valueToTree(app.synth().getStackArtifact(stack.artifactId).template)
}
@Test
fun `All CfnResources have the expected CF Condition`() {
val expectedCondition = "myCondition"
val softly = SoftAssertions()
synthesizedStackCfTemplate.get("Resources").forEach { resource ->
softly.assertThat(resource.get("Condition")?.asText())
.describedAs("Resource is missing expected CF condition [$expectedCondition]: $resource")
.isEqualTo(expectedCondition)
}
softly.assertAll()
}