Skip to content

Commit babf536

Browse files
Added PR state memoization
1 parent db4d049 commit babf536

2 files changed

Lines changed: 37 additions & 17 deletions

File tree

packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export async function deleteUnusedPrStacks(
8383
const cloudFormationClient = new CloudFormationClient({})
8484
const route53Client = new Route53Client({})
8585
const {hostedZoneId, cnameRecords} = await getHostedZoneInfo(route53Client, hostedZoneName)
86+
const pullRequestStateCache = new Map<string, string>()
8687

8788
console.log("checking cloudformation stacks")
8889

@@ -94,7 +95,7 @@ export async function deleteUnusedPrStacks(
9495
}
9596

9697
const stackName = stack.StackName
97-
if (!(await isClosedPullRequest(stackName, baseStackName, repoName))) {
98+
if (!(await isClosedPullRequest(stackName, baseStackName, repoName, pullRequestStateCache))) {
9899
continue
99100
}
100101

@@ -188,34 +189,50 @@ async function getHostedZoneInfo(
188189
return {hostedZoneId, cnameRecords}
189190
}
190191

191-
async function isClosedPullRequest(stackName: string, baseStackName: string, repoName: string): Promise<boolean> {
192+
async function isClosedPullRequest(
193+
stackName: string,
194+
baseStackName: string,
195+
repoName: string,
196+
pullRequestStateCache: Map<string, string>
197+
): Promise<boolean> {
192198
const match = new RegExp(String.raw`^${baseStackName}-pr-(?<pullRequestId>\d+)(-[\w-]+)?$`).exec(stackName)
193199
if (!match?.groups?.pullRequestId) {
194200
return false
195201
}
196202

197203
const pullRequestId = match.groups.pullRequestId
198-
console.log(`Checking pull request id ${pullRequestId}`)
199-
const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}`
204+
let pullRequestState = pullRequestStateCache.get(pullRequestId)
205+
if (pullRequestState === undefined) {
200206

201-
const headers: Record<string, string> = {
202-
Accept: "application/vnd.github+json",
203-
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
204-
}
207+
console.log(`Checking pull request id ${pullRequestId}`)
208+
const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}`
205209

206-
const response = await fetch(url, {headers})
207-
if (!response.ok) {
208-
console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`)
209-
return false
210+
const headers: Record<string, string> = {
211+
Accept: "application/vnd.github+json",
212+
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
213+
}
214+
215+
const response = await fetch(url, {headers})
216+
if (!response.ok) {
217+
console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`)
218+
// To avoid accidentally deleting stacks due to transient API failures, we treat errors as non-closed state
219+
// but do not cache the result to allow for retries on subsequent runs
220+
return false
221+
}
222+
223+
const data = (await response.json()) as {state?: string}
224+
pullRequestState = data.state
225+
if (pullRequestState) {
226+
pullRequestStateCache.set(pullRequestId, pullRequestState)
227+
}
210228
}
211229

212-
const data = (await response.json()) as {state?: string}
213-
if (data.state !== "closed") {
214-
console.log(`not going to delete stack ${stackName} as PR state is ${data.state}`)
230+
if (pullRequestState !== "closed") {
231+
console.log(`not going to delete stack ${stackName} as PR state is ${pullRequestState}`)
215232
return false
216233
}
217234

218-
console.log(`** going to delete stack ${stackName} as PR state is ${data.state} **`)
235+
console.log(`** going to delete stack ${stackName} as PR state is ${pullRequestState} **`)
219236
return true
220237
}
221238

packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,11 +893,14 @@ describe("stack deletion", () => {
893893
await vi.runAllTimersAsync()
894894
await promise
895895

896-
// One delete stack call for the PR stack
896+
// One delete stack call per PR stack
897897
expect(mockDeleteStackSend).toHaveBeenCalledTimes(3)
898898
expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-123-sandbox`})
899899
expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-123-front-door`})
900900
expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-123-stateful`})
901+
902+
// PR state should only be fetched once per PR, not once per stack
903+
expect(mockGetPRState).toHaveBeenCalledTimes(1)
901904
})
902905

903906
test("does not delete open PR stacks", async () => {

0 commit comments

Comments
 (0)