AWS CDK:将 Cfn 条件添加到 all/multiple 资源

AWS CDK: Add Cfn Condition to all/multiple resources

我需要向 CDK 中的 all/multiple 资源添加条件(例如 isProdisSpecificRegion)。我看到我可以使用此技术将 CfnCondition 添加到特定的 CDK 资源:。例如

// Create the user using the L2 construct
const user = new iam.User(this, 'User');

// Add the condition on the underlying AWS::IAM::User
(user.node.defaultChild as iam.CfnUser).cfnOptions.condition = regionCondition

有没有办法遍历所有资源并应用条件?

通常,除非有非常特殊的需要,否则您不想将 CfnConditional 与 CDK 一起使用。正如 Amit 在他们的评论中提到的,惯用的 CDK 方法是使用该语言的 if/else 逻辑来实例化或不实例化一个构造。

如果您绝对需要在每个资源上使用 CFNConditional,我认为方面功能可以使用逃生舱口将条件应用于底层 cloudformation 资源,尽管我会 强烈反对。

我找到了一种自动应用 CfnCondition to every resource in the stack using the Construct#onPrepare hook in the Stack

的方法

从上面的页面,Construct#onPrepare()是:

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.

以下 CDK 代码(示例代码 - 我只在此处放入相关位)在 Kotlin 但 将适用于任何 支持的CDK 语言:

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)
        }
    }
}

我添加的单元测试(Junit 和 AssertJ,也在 Kotlin 中)验证这涵盖了所有资源:

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()
}

使用CDK Aspects () I was able to simply my solution even further (building upon my previous solution: ):

class ApplyCfnConditionAspect(val cfnCondition: CfnCondition): IAspect {
    override fun visit(construct: IConstruct) {
        (construct as? CfnResource)?.cfnOptions?.condition = this.cfnCondition
    }
}

... 然后在 Stack 内,指定所有资源后:

this.node.applyAspect(ApplyCfnConditionAspect(shouldEnableAnalyticsCondition))