in packages/@aws-cdk/toolkit-lib/lib/api/refactoring/digest.ts [28:118]
export function computeResourceDigests(template: CloudFormationTemplate): Record<string, string> {
const resources = template.Resources || {};
const graph: Record<string, Set<string>> = {};
const reverseGraph: Record<string, Set<string>> = {};
// 1. Build adjacency lists
for (const id of Object.keys(resources)) {
graph[id] = new Set();
reverseGraph[id] = new Set();
}
// 2. Detect dependencies by searching for Ref/Fn::GetAtt
const findDependencies = (value: any): string[] => {
if (!value || typeof value !== 'object') return [];
if (Array.isArray(value)) {
return value.flatMap(findDependencies);
}
if ('Ref' in value) {
return [value.Ref];
}
if ('Fn::GetAtt' in value) {
const refTarget = Array.isArray(value['Fn::GetAtt']) ? value['Fn::GetAtt'][0] : value['Fn::GetAtt'].split('.')[0];
return [refTarget];
}
if ('DependsOn' in value) {
return [value.DependsOn];
}
return Object.values(value).flatMap(findDependencies);
};
for (const [id, res] of Object.entries(resources)) {
const deps = findDependencies(res || {});
for (const dep of deps) {
if (dep in resources && dep !== id) {
graph[id].add(dep);
reverseGraph[dep].add(id);
}
}
}
// 3. Topological sort
const outDegree = Object.keys(graph).reduce((acc, k) => {
acc[k] = graph[k].size;
return acc;
}, {} as Record<string, number>);
const queue = Object.keys(outDegree).filter((k) => outDegree[k] === 0);
const order: string[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
order.push(node);
for (const nxt of reverseGraph[node]) {
outDegree[nxt]--;
if (outDegree[nxt] === 0) {
queue.push(nxt);
}
}
}
// 4. Compute digests in sorted order
const result: Record<string, string> = {};
for (const id of order) {
const resource = resources[id];
const resourceProperties = resource.Properties ?? {};
const model = loadResourceModel(resource.Type);
const identifier = intersection(Object.keys(resourceProperties), model?.primaryIdentifier ?? []);
let toHash: string;
if (identifier.length === model?.primaryIdentifier?.length) {
// The resource has a physical ID defined, so we can
// use the ID and the type as the identity of the resource.
toHash =
resource.Type +
identifier
.sort()
.map((attr) => JSON.stringify(resourceProperties[attr]))
.join('');
} else {
// The resource does not have a physical ID defined, so we need to
// compute the digest based on its properties and dependencies.
const depDigests = Array.from(graph[id]).map((d) => result[d]);
const propertiesHash = hashObject(stripReferences(stripConstructPath(resource)));
toHash = resource.Type + propertiesHash + depDigests.join('');
}
result[id] = crypto.createHash('sha256').update(toHash).digest('hex');
}
return result;
}