Search code examples
pythonaws-cdk

How do I create a `CfnOutput` that is a list of values?


All of the examples use the CfnOutput class. But the CfnOutput class only accepts a str for the value parameter.

This doesn't work for that reason:

from aws_cdk import CfnOutput, Stack, aws_ec2
from constructs import Construct

class MyVpc(Stack):
    def __init__(self, scope: Construct, construct_id: str) -> None:
        super().__init__(scope, construct_id)

        vpc = aws_ec2.Vpc.from_lookup(scope, construct_id, is_default=True)
        
        CfnOutput(self, "private_subnets", values=vpc.private_subnets)
        CfnOutput(self, "public_subnets", values=vpc.public_subnets)

The error being

Argument of type "List[ISubnet]" cannot be assigned to parameter "value" of type "str" in function

As the code snippet shows, I'd like to export a list of VPC subnets from the AWS-created default VPC in order to pass into to an another stack that needs a list of subnet IDs. There is no example or documentation for something like this that I can find after a long search in vain: there are hundreds of Python classes in this library whose documentation is extremely difficult to navigate for someone who's new to AWS CDK. I can concatenate the subnet IDs into a string and then split them in the other stack, but that seems ridiculous.

What is the correct way to export a value that is more general than a string from one stack to be used in another stack?


Solution

  • If you need to reference VPC resources across independent stacks (not nested or in the same CDK app), you can use CfnOutput and Vpc.from_lookup() Outputs only accept strings, not lists. So we can create the CfnOutput by iterating over subnets like this:

    for idx, subnet in vpc.private_subnets:
        CfnOutput(self, f"private_subnet{idx}", value=subnet.subnet_id)
    

    For nested stacks or Stacks that are part of the same App, it's simpler to pass the VPC object directly:

    1. Create a VPC provider stack:
    class VpcStack(Stack):
        def __init__(self, scope: Construct, construct_id: str, **kwargs):
            super().__init__(scope, construct_id, **kwargs)
            self.vpc = aws_ec2.Vpc(self, "VPC", max_azs=2)
            self.private_subnets = self.vpc.private_subnets
            self.public_subnets = self.vpc.public_subnets
    
    1. Create a consumer stack that takes the provider stack as a parameter:
    class VpcConsumerStack(Stack):
        def __init__(self, scope: Construct, construct_id: str, vpc_provider: VpcProviderStack, **kwargs):
            super().__init__(scope, construct_id, **kwargs)
            private_subnets = vpc_provider.private_subnets
            public_subnets = vpc_provider.public_subnets
            # Use these subnets as needed
    
    1. Instantiate these stacks in your main CDK app:
    app = App()
    vpc_provider = VpcProviderStack(app, "VpcProviderStack")
    vpc_consumer = VpcConsumerStack(app, "VpcConsumerStack", vpc_provider=vpc_provider)
    app.synth()