packages/@aws-cdk/toolkit-lib/lib/api/cloudformation/nested-stack-helpers.ts (131 lines of code) (raw):

import * as path from 'path'; import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import { LazyListStackResources, type ListStackResources } from './evaluate-cloudformation-template'; import { CloudFormationStack, type Template } from './stack-helpers'; import { formatErrorMessage } from '../../util'; import type { SDK } from '../aws-auth/private'; export interface RootTemplateWithNestedStacks { readonly deployedRootTemplate: Template; readonly nestedStacks: { [nestedStackLogicalId: string]: NestedStackTemplates; }; } /** * Reads the currently deployed template and all of its nested stack templates from CloudFormation. */ export async function loadCurrentTemplateWithNestedStacks( rootStackArtifact: CloudFormationStackArtifact, sdk: SDK, retrieveProcessedTemplate: boolean = false, ): Promise<RootTemplateWithNestedStacks> { const deployedRootTemplate = await loadCurrentTemplate(rootStackArtifact, sdk, retrieveProcessedTemplate); const nestedStacks = await loadNestedStacks(rootStackArtifact, sdk, { generatedTemplate: rootStackArtifact.template, deployedTemplate: deployedRootTemplate, deployedStackName: rootStackArtifact.stackName, }); return { deployedRootTemplate, nestedStacks, }; } /** * Returns the currently deployed template from CloudFormation that corresponds to `stackArtifact`. */ export async function loadCurrentTemplate( stackArtifact: CloudFormationStackArtifact, sdk: SDK, retrieveProcessedTemplate: boolean = false, ): Promise<Template> { return loadCurrentStackTemplate(stackArtifact.stackName, sdk, retrieveProcessedTemplate); } async function loadCurrentStackTemplate( stackName: string, sdk: SDK, retrieveProcessedTemplate: boolean = false, ): Promise<Template> { const cfn = sdk.cloudFormation(); const stack = await CloudFormationStack.lookup(cfn, stackName, retrieveProcessedTemplate); return stack.template(); } async function loadNestedStacks( rootStackArtifact: CloudFormationStackArtifact, sdk: SDK, parentTemplates: StackTemplates, ): Promise<{ [nestedStackLogicalId: string]: NestedStackTemplates }> { const listStackResources = parentTemplates.deployedStackName ? new LazyListStackResources(sdk, parentTemplates.deployedStackName) : undefined; const nestedStacks: { [nestedStackLogicalId: string]: NestedStackTemplates } = {}; for (const [nestedStackLogicalId, generatedNestedStackResource] of Object.entries( parentTemplates.generatedTemplate.Resources ?? {}, )) { if (!isCdkManagedNestedStack(generatedNestedStackResource)) { continue; } const assetPath = generatedNestedStackResource.Metadata['aws:asset:path']; const nestedStackTemplates = await getNestedStackTemplates( rootStackArtifact, assetPath, nestedStackLogicalId, listStackResources, sdk, ); nestedStacks[nestedStackLogicalId] = { deployedTemplate: nestedStackTemplates.deployedTemplate, generatedTemplate: nestedStackTemplates.generatedTemplate, physicalName: nestedStackTemplates.deployedStackName, nestedStackTemplates: await loadNestedStacks(rootStackArtifact, sdk, nestedStackTemplates), }; } return nestedStacks; } async function getNestedStackTemplates( rootStackArtifact: CloudFormationStackArtifact, nestedTemplateAssetPath: string, nestedStackLogicalId: string, listStackResources: ListStackResources | undefined, sdk: SDK, ): Promise<StackTemplates> { const nestedTemplatePath = path.join(rootStackArtifact.assembly.directory, nestedTemplateAssetPath); // CFN generates the nested stack name in the form `ParentStackName-NestedStackLogicalID-SomeHashWeCan'tCompute, // the arn is of the form: arn:aws:cloudformation:region:123456789012:stack/NestedStackName/AnotherHashWeDon'tNeed // so we get the ARN and manually extract the name. const nestedStackArn = await getNestedStackArn(nestedStackLogicalId, listStackResources); const deployedStackName = nestedStackArn?.slice(nestedStackArn.indexOf('/') + 1, nestedStackArn.lastIndexOf('/')); return { generatedTemplate: JSON.parse(fs.readFileSync(nestedTemplatePath, 'utf-8')), deployedTemplate: deployedStackName ? await loadCurrentStackTemplate(deployedStackName, sdk) : {}, deployedStackName, }; } async function getNestedStackArn( nestedStackLogicalId: string, listStackResources?: ListStackResources, ): Promise<string | undefined> { try { const stackResources = await listStackResources?.listStackResources(); return stackResources?.find((sr) => sr.LogicalResourceId === nestedStackLogicalId)?.PhysicalResourceId; } catch (e: any) { if (formatErrorMessage(e).startsWith('Stack with id ') && formatErrorMessage(e).endsWith(' does not exist')) { return; } throw e; } } function isCdkManagedNestedStack(stackResource: any): stackResource is NestedStackResource { return ( stackResource.Type === 'AWS::CloudFormation::Stack' && stackResource.Metadata && stackResource.Metadata['aws:asset:path'] ); } export interface NestedStackTemplates { readonly physicalName: string | undefined; readonly deployedTemplate: Template; readonly generatedTemplate: Template; readonly nestedStackTemplates: { [nestedStackLogicalId: string]: NestedStackTemplates; }; } interface StackTemplates { readonly generatedTemplate: any; readonly deployedTemplate: any; readonly deployedStackName: string | undefined; } interface NestedStackResource { readonly Metadata: { 'aws:asset:path': string }; readonly Properties: any; }