Search code examples
amazon-web-servicesaws-cloudformationaws-cdkinfrastructure-as-code

How refactorable are AWS CDK applications?


I'm exploring how refactorable CDK applications are. Suppose I defined a custom construct (a stack) to create an EKS cluster. Let's call it EksStack. Ideally, I'd create the role to be associated with the cluster and the EKS cluster itself, as described by the following snippet (I'm using Scala instead of Java, so the snippets are going to be in Scala syntax):

class EksStack (scope: Construct, id: String, props: StackProps) extends Stack(scope, id, props) {
    private val role = new Role(this, "eks-role", RoleProps.builder()
        .description(...)
        .managedPolicies(...)
        .assumedBy(...)
        .build()
    )

    private val cluster = new Cluster(this, "eks-cluster", ClusterProps.builder()
        .version(...)
        .role(role)
        .defaultCapacityType(DefaultCapacityType.EC2)
        .build()
    )
}

When I synthetize the application, I can see that the generated template contains the definition of the VPC, together with the Elastic IPs, NATs, Internet Gateways, and so on.

Now suppose that I want to refactor EksStack and have a different stack, say VpcStack, explicitly create the VPC:

class VpcStack (scope: Construct, id: String, props: StackProps) extends Stack(scope, id, props) {
    val vpc = new Vpc(this, VpcId, VpcProps.builder()
      .cidr(...)
      .enableDnsSupport(true)
      .enableDnsHostnames(true)
      .maxAzs(...)
      .build()
    )
}

Ideally, the cluster in EksStack would just be using the reference to the VPC created by VpcStack, something like (note the new call to vpc() in the builder of cluster):

class EksStack (scope: Construct, id: String, props: StackProps, vpc: IVpc) extends Stack(scope, id, props) {
    private val role = new Role(this, "eks-role", RoleProps.builder()
        .description(...)
        .managedPolicies(...)
        .assumedBy(...)
        .build()
    )

    private val cluster = new Cluster(this, "eks-cluster", ClusterProps.builder()
        .version(...)
        .role(role)
        .vpc(vpc)
        .defaultCapacityType(DefaultCapacityType.EC2)
        .build()
    )
}

This obviously doesn't work, as CloudFormation would delete the VPC created by EksStack in favor of the one created by VpcStack. I read here and there and tried to add a retain policy in EksStack and to override the logical ID of the VPC in VpcStack, using the ID I originally saw in the CloudFormation template for EksStack:

val cfnVpc = cluster.getVpc.getNode.getDefaultChild.asInstanceOf[CfnVPC]
cfnVpc.applyRemovalPolicy(RemovalPolicy.RETAIN)

and

val cfnVpc = vpc.getNode.getDefaultChild.asInstanceOf[CfnVPC]
cfnVpc.overrideLogicalId("LogicalID")

and then retried the diff. Again, it seems that the VPC is deleted and re-created.

Now, I saw that it is possible to migrate CloudFormation resources (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/refactor-stacks.html) using the "Import resources into stack" action. My question is: can I move the creation of a resource from a stack to another in CDK without re-creating it?

EDIT: To elaborate a bit on my problem, when I define the VPC in VpcStack, I'd like CDK to think that the resource was created by VpcStack instead ok EksStack. Something like moving the definition of it from one stack to another without having CloudFormation delete the original one to re-create it. In my use case, I'd have a stack define a create initially (either explicitly or implicitly, such as my VPC), but then, after I while, I might want to refactor my application, moving the creation of that resource in a dedicated stack. I'm trying to understand if this moving always leads to the resource being re-created of if there's any way to avoid it.


Solution

  • I'm not sure if I understand the problem, but if you are trying to reference an existing resource you can use a context query. (e.g. Vpc.fromLookup).

    https://docs.aws.amazon.com/cdk/latest/guide/context.html

    Additionally, if you would like to use the Vpc created from VpcStack inside of EksStack you can output the vpc id from the VpcStack and use the context query in the eks stack that way.

    this is C# code but the principal is the same.

    var myVpc = new Vpc(...);
    
    
    new CfnOutput(this, "MyVpcIdOutput", new CfnOutputProps()
    {
        ExportName = "VpcIdOutput",
        Value = myVpc.VpcId
    }
               
    

    and then when you create the EksStack you can import the vpc id that you previously exported.

    new EksStack(this, "MyCoolStack", new EksStackProps()
    {
        MyVpcId = Fn.ImportValue("VpcIdOutput")
    }
    

    where EksStackProps is

    public class EksStackProps 
    {
        public string MyVpcId { get; set; }
    }