Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance CDK support with nested stack handling #295

Merged
merged 4 commits into from
Dec 18, 2024

Conversation

flostadler
Copy link
Contributor

@flostadler flostadler commented Dec 16, 2024

This change adds support for Nested Stacks to pulumi-cdk.
In CDK, a nested stack is a CDK stack that you create inside another stack, known as the parent stack. Users create nested stacks using the NestedStack construct.
By using nested stacks, users can organize resources across multiple stacks. Nested stacks also offer a way around the AWS CloudFormation 500-resource limit for stacks. A nested stack counts as only one resource in the stack that contains it. However, it can contain up to 500 resources, including additional nested stacks.
While Pulumi doesn't have those limitations, many existing CDK constructs use nested stacks to get around the CloudFormation limitations. The EKS cluster construct is one of the most prominent ones that require nested stacks.

In detail, this change:

  • enhances the AssemblyManifestReader to support reading nested stacks.
  • introduces a new unique StackAddress for elements in the CloudFormation templates. It combines the stack path (tree path of the stack) and the stack-local logical ID in order to form an assembly wide unique address. This is necessary in order to represent a hierarchy of stacks in the same pulumi program
  • adds a new NestedStackConstruct component. It is used to namespace the resources of a NestedStack in order to not run into any issues with duplicate logical IDs. It achieves this by including the stack path in the pulumi resource type. The types of parent resources are included in URNs, meaning all resources parented under the nested stack have their own unique namespace.
  • updates the artifact converter and intrinsics to handle cross stack references (Parameters & Outputs) for nested stacks

Integration tests and an example are part of #298 which is part of this PR stack.

Review Advice

I'd recommend first reviewing the changes to the code reading the cloud assembly (src/assembly/manifest.ts), then the GraphBuilder (src/graph.ts) and lastly the app converter and intrinsics (src/converters/app-converter.ts & src/converters/intrinsics.ts). The other changes are in support for those

Fixes #80

@flostadler flostadler self-assigned this Dec 16, 2024
@flostadler flostadler force-pushed the flostadler/nested-stacks branch from d48ac5e to 2caf7b8 Compare December 17, 2024 14:09
- Introduced `StackAddress` type to uniquely identify resources across nested stacks.
- Updated `CloudFormationParameterWithId` to use `stackAddress` instead of `id`.
- Added `NestedStackTemplate` interface and `isNestedStackTemplate` function for better nested stack management.
- Modified various functions and interfaces to accommodate stack paths and addresses, improving resource mapping and retrieval.
- Created `StackMap` class for efficient management of resources across stacks.
- Updated tests to reflect changes in resource identification and nested stack handling.

This update improves the overall structure and usability of arbitrary stacks in the cloud assembly.
@flostadler flostadler force-pushed the flostadler/nested-stacks branch from 2caf7b8 to 674ff01 Compare December 17, 2024 14:11
@flostadler
Copy link
Contributor Author

flostadler commented Dec 17, 2024

@flostadler flostadler changed the title Nested Stack Support Enhance CDK support with nested stack handling Dec 17, 2024
@flostadler flostadler requested review from corymhall, t0yv0 and a team December 17, 2024 14:26
@flostadler flostadler marked this pull request as ready for review December 17, 2024 14:26
@@ -367,10 +448,39 @@ export class StackConverter extends ArtifactConverter implements intrinsics.Intr
/** @internal */
asOutputValue<T>(v: T): T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cutting a follow up ticket for handling cases where multiple nested stacks have a resource with the same logical ID. The users could inform the converter about the right stack by passing it in as an optional argument.

Copy link
Contributor

@corymhall corymhall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome! My only minor comment is around adding some unit tests to the intrinsics and graph tests.


// resources of the nested stack should be mapped
// this tests that properties are correctly passed to the nested stack
const nestedBucket = converter.resources.get({ stackPath: `${manifest.id}/nesty`, id: 'bucket43879C71'})?.resource as native.s3.Bucket;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like NestedStack resources still have the cdk random suffix (no way around it). More reason not to use nested stacks!

tests/graph.test.ts Outdated Show resolved Hide resolved
tests/converters/intrinsics.test.ts Outdated Show resolved Hide resolved
@flostadler
Copy link
Contributor Author

@corymhall I think this is ready for another look

Copy link
Contributor

@corymhall corymhall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@flostadler flostadler merged commit fa10f4a into main Dec 18, 2024
13 checks passed
@flostadler flostadler deleted the flostadler/nested-stacks branch December 18, 2024 14:13
flostadler added a commit that referenced this pull request Dec 18, 2024
…umi (#298)

This PR adds an integration test and example for CDK Nested stacks.

Stacked on top of #295
Related to #80
* @param logicalId - The logicalId of the nested stack
* @returns The path to the nested stack wrapper node in the construct tree
*/
public static getNestedStackPath(nestedStackResourcePath: string, logicalId: string): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: type aliaseses instead of string for these things can be helpful to clarify at type system level what's what.

readonly Metadata?: { [key: string]: any };
}

export interface CloudFormationOutput {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this represent and what can we expect under Value: any?

@@ -95,9 +103,11 @@ export class AppConverter {
* StackConverter converts all of the resources in a CDK stack to Pulumi resources
*/
export class StackConverter extends ArtifactConverter implements intrinsics.IntrinsicContext {
readonly parameters = new Map<CloudFormationParameterLogicalId, any>();
readonly resources = new Map<string, Mapping<pulumi.Resource>>();
readonly parameters = new StackMap<any>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat.

@@ -367,10 +448,44 @@ export class StackConverter extends ArtifactConverter implements intrinsics.Intr
/** @internal */
asOutputValue<T>(v: T): T {
const value = this.cdkStack.resolve(v);
return this.processIntrinsics(value) as T;
try {
return this.processIntrinsics(value, this.stack.constructTree.path) as T;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this eventually become a single-pass processIntrinsics?

That is can processIntrinsics absorb this.graph.nestedStackNodes parameter and just not fail but to the right thing.

@@ -85,22 +96,22 @@ export interface IntrinsicContext {
*
* See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html
*/
findCondition(conditionName: string): Expression | undefined;
findCondition(stackAddress: StackAddress): Expression | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

* StackAddress uniquely identifies a resource in a cloud assembly
* across several (nested) stacks.
*/
export type StackAddress = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps ResourceAddress? StackAddress makes it sound like it identifies a Stack. Doc comments on props are also nice, with examples.

@t0yv0
Copy link
Member

t0yv0 commented Dec 18, 2024

Very nice. Sorry for late review with some questions. Nothing blocking.

@pulumi-bot
Copy link
Contributor

This PR has been shipped in release v1.4.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support NestedStacks (cannot create eks.Cluster)
4 participants