-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdelete-fns.ts
168 lines (134 loc) · 4.79 KB
/
delete-fns.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import { LambdaClient, ListFunctionsCommand } from "@aws-sdk/client-lambda";
import {
ListStacksCommand,
type StackSummary,
DeleteStackCommand,
CloudFormationClient,
} from "@aws-sdk/client-cloudformation";
import {
CloudFrontClient,
ListDistributionsCommand,
} from "@aws-sdk/client-cloudfront";
const lambda = new LambdaClient({
region: "us-east-1",
});
const cloudformation = new CloudFormationClient({ region: "us-east-1" });
const cloudfront = new CloudFrontClient();
/**
* Delete orphaned Lambda@Edge functions.
*/
export async function deleteFns() {
// Fetch Lambda@Edge functions from us-east-1 region.
const edgeFns = await listEdgeFunctions();
console.log(`Found ${edgeFns.length} edge functions.`);
// Fetch a list of attached Lambda@Edge function ARNs by inspecting CloudFront deployments.
const attachedFunctions = await listAttachedFunctions();
console.log(`Found ${attachedFunctions.length} attached functions.`);
// Calculate which functions are unattached by comparing the ARN lists.
const unattachedFunctions = edgeFns.filter(
(arn) => !attachedFunctions.includes(arn),
);
console.log(`Calculated ${unattachedFunctions.length} unattached functions.`);
// Fetch CloudFormation stacks from us-east-1 region.
const stacks = await listStacks();
// For each unattached function ARN, try to find the corresponding CloudFormation stack
// and delete it.
let deletedStackCount = 0;
for (const unattachedFunction of unattachedFunctions) {
const stackSearch = unattachedFunction.match(/edge-lambda-stack-\w+/);
if (!stackSearch) {
console.warn(`Could not resolve stack ID from ${unattachedFunction}.`);
continue;
}
const stackIdSearch = stackSearch[0];
const stack = stacks.find(
(stack) => stack.StackId?.includes(stackIdSearch),
);
if (!stack) {
console.warn(
`Could not resolve stack for ${unattachedFunction} (stack ID search ${stackIdSearch}).`,
);
continue;
}
await cloudformation.send(
new DeleteStackCommand({ StackName: stack.StackName }),
);
deletedStackCount++;
}
console.log(
`Deleted ${deletedStackCount} stacks with unattached edge functions.`,
);
}
/**
* Recursively fetch a list of attached Lambda@Edge function ARNs by inspecting the cache behaviors
* of CloudFront distributions.
*/
async function listAttachedFunctions(
attachedFunctions: string[] = [],
Marker?: string,
): Promise<string[]> {
const command = new ListDistributionsCommand({ Marker });
const response = await cloudfront.send(command);
if (!response.DistributionList?.Items) return attachedFunctions;
for (const distribution of response.DistributionList.Items) {
// Combine cache behaviors
const cacheBehaviors = [
...(distribution.DefaultCacheBehavior
? [distribution.DefaultCacheBehavior]
: []),
...(distribution.CacheBehaviors?.Items ?? []),
];
// Loop cache behaviors and append attached functions.
for (const cacheBehavior of cacheBehaviors) {
if (!cacheBehavior.LambdaFunctionAssociations?.Items) continue;
for (const functionAssociation of cacheBehavior.LambdaFunctionAssociations
.Items) {
if (functionAssociation.LambdaFunctionARN) {
attachedFunctions.push(
// Remove the version number from the end.
functionAssociation.LambdaFunctionARN.replace(/:\d+$/, ""),
);
}
}
}
}
if (!response.DistributionList.NextMarker) return attachedFunctions;
return await listAttachedFunctions(
attachedFunctions,
response.DistributionList.NextMarker,
);
}
/**
* Recursively fetch CloudFormation stacks.
*/
async function listStacks(
stacks: StackSummary[] = [],
NextToken?: string,
): Promise<StackSummary[]> {
const command = new ListStacksCommand({ NextToken });
const response = await cloudformation.send(command);
stacks = [...stacks, ...(response.StackSummaries ?? [])];
if (!response.NextToken) return stacks;
return await listStacks(stacks, response.NextToken);
}
/**
* Recursively fetch Lambda@Edge function ARNs.
*
* A Lambda@Edge function is identified by the presence of "edge-lambda" in the ARN, which is
* the naming convention that the CDK EdgeFunction construct uses.
*/
async function listEdgeFunctions(
edgeFns: string[] = [],
Marker?: string,
): Promise<string[]> {
const command = new ListFunctionsCommand({ Marker });
const response = await lambda.send(command);
if (!response.Functions) return edgeFns;
for (const lambdaFn of response.Functions) {
if (!lambdaFn.FunctionArn) continue;
if (!lambdaFn.FunctionArn.includes("edge-lambda")) continue;
edgeFns.push(lambdaFn.FunctionArn);
}
if (!response.NextMarker) return edgeFns;
return await listEdgeFunctions(edgeFns, response.NextMarker);
}