packages/@aws-cdk/toolkit-lib/lib/api/refactoring/exclude.ts (90 lines of code) (raw):

import type { AssemblyManifest } from '@aws-cdk/cloud-assembly-schema'; import { ArtifactMetadataEntryType, ArtifactType } from '@aws-cdk/cloud-assembly-schema'; import type { ResourceLocation as CfnResourceLocation } from '@aws-sdk/client-cloudformation'; import type { ResourceLocation } from './cloudformation'; export interface ExcludeList { isExcluded(location: ResourceLocation): boolean; } export class ManifestExcludeList implements ExcludeList { private readonly excludedLocations: CfnResourceLocation[]; constructor(manifest: AssemblyManifest) { this.excludedLocations = this.getExcludedLocations(manifest); } private getExcludedLocations(asmManifest: AssemblyManifest): CfnResourceLocation[] { // First, we need to filter the artifacts to only include CloudFormation stacks const stackManifests = Object.entries(asmManifest.artifacts ?? {}).filter( ([_, manifest]) => manifest.type === ArtifactType.AWS_CLOUDFORMATION_STACK, ); const result: CfnResourceLocation[] = []; for (let [stackName, manifest] of stackManifests) { const locations = Object.values(manifest.metadata ?? {}) // Then pick only the resources in each stack marked with DO_NOT_REFACTOR .filter((entries) => entries.some((entry) => entry.type === ArtifactMetadataEntryType.DO_NOT_REFACTOR && entry.data === true), ) // Finally, get the logical ID of each resource .map((entries) => { const logicalIdEntry = entries.find((entry) => entry.type === ArtifactMetadataEntryType.LOGICAL_ID); const location: CfnResourceLocation = { StackName: stackName, LogicalResourceId: logicalIdEntry!.data! as string, }; return location; }); result.push(...locations); } return result; } isExcluded(location: ResourceLocation): boolean { return this.excludedLocations.some( (loc) => loc.StackName === location.stack.stackName && loc.LogicalResourceId === location.logicalResourceId, ); } } export class InMemoryExcludeList implements ExcludeList { private readonly excludedLocations: CfnResourceLocation[]; private readonly excludedPaths: string[]; constructor(items: string[]) { this.excludedLocations = []; this.excludedPaths = []; if (items.length === 0) { return; } const locationRegex = /^[A-Za-z0-9]+\.[A-Za-z0-9]+$/; items.forEach((item: string) => { if (locationRegex.test(item)) { const [stackName, logicalId] = item.split('.'); this.excludedLocations.push({ StackName: stackName, LogicalResourceId: logicalId, }); } else { this.excludedPaths.push(item); } }); } isExcluded(location: ResourceLocation): boolean { const containsLocation = this.excludedLocations.some((loc) => { return loc.StackName === location.stack.stackName && loc.LogicalResourceId === location.logicalResourceId; }); const containsPath = this.excludedPaths.some((path) => location.toPath() === path); return containsLocation || containsPath; } } export class UnionExcludeList implements ExcludeList { constructor(private readonly excludeLists: ExcludeList[]) { } isExcluded(location: ResourceLocation): boolean { return this.excludeLists.some((excludeList) => excludeList.isExcluded(location)); } } export class NeverExclude implements ExcludeList { isExcluded(_location: ResourceLocation): boolean { return false; } } export class AlwaysExclude implements ExcludeList { isExcluded(_location: ResourceLocation): boolean { return true; } } export function fromManifestAndExclusionList(manifest: AssemblyManifest, exclude?: string[]): ExcludeList { return new UnionExcludeList([new ManifestExcludeList(manifest), new InMemoryExcludeList(exclude ?? [])]); }