Skip to content

Commit 7e93220

Browse files
authoredNov 9, 2023
fix: clean the elb access bucket at env deploy when it is disabled in the env mft (#5437)
Fixes #5428 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
1 parent 9d9e8d6 commit 7e93220

16 files changed

+760
-9
lines changed
 
+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
"use strict";
4+
5+
const aws = require("aws-sdk");
6+
7+
// These are used for test purposes only
8+
let defaultResponseURL;
9+
let defaultLogGroup;
10+
let defaultLogStream;
11+
12+
/**
13+
* Upload a CloudFormation response object to S3.
14+
*
15+
* @param {object} event the Lambda event payload received by the handler function
16+
* @param {object} context the Lambda context received by the handler function
17+
* @param {string} responseStatus the response status, either 'SUCCESS' or 'FAILED'
18+
* @param {string} physicalResourceId CloudFormation physical resource ID
19+
* @param {object} [responseData] arbitrary response data object
20+
* @param {string} [reason] reason for failure, if any, to convey to the user
21+
* @returns {Promise} Promise that is resolved on success, or rejected on connection error or HTTP error response
22+
*/
23+
let report = function (
24+
event,
25+
context,
26+
responseStatus,
27+
physicalResourceId,
28+
responseData,
29+
reason
30+
) {
31+
return new Promise((resolve, reject) => {
32+
const https = require("https");
33+
const { URL } = require("url");
34+
35+
var responseBody = JSON.stringify({
36+
Status: responseStatus,
37+
Reason: reason,
38+
PhysicalResourceId: physicalResourceId || context.logStreamName,
39+
StackId: event.StackId,
40+
RequestId: event.RequestId,
41+
LogicalResourceId: event.LogicalResourceId,
42+
Data: responseData,
43+
});
44+
45+
const parsedUrl = new URL(event.ResponseURL || defaultResponseURL);
46+
const options = {
47+
hostname: parsedUrl.hostname,
48+
port: 443,
49+
path: parsedUrl.pathname + parsedUrl.search,
50+
method: "PUT",
51+
headers: {
52+
"Content-Type": "",
53+
"Content-Length": responseBody.length,
54+
},
55+
};
56+
57+
https
58+
.request(options)
59+
.on("error", reject)
60+
.on("response", (res) => {
61+
res.resume();
62+
if (res.statusCode >= 400) {
63+
reject(new Error(`Error ${res.statusCode}: ${res.statusMessage}`));
64+
} else {
65+
resolve();
66+
}
67+
})
68+
.end(responseBody, "utf8");
69+
});
70+
};
71+
72+
/**
73+
* Delete all objects in a bucket.
74+
*
75+
* @param {string} bucketName Name of the bucket to be cleaned.
76+
*/
77+
const cleanBucket = async function (bucketName) {
78+
const s3 = new aws.S3();
79+
// Make sure the bucket exists.
80+
try {
81+
await s3.headBucket({ Bucket: bucketName }).promise();
82+
} catch (err) {
83+
if (err.name === "ResourceNotFoundException") {
84+
return;
85+
}
86+
throw err;
87+
}
88+
const listObjectVersionsParam = {
89+
Bucket: bucketName
90+
}
91+
while (true) {
92+
const listResp = await s3.listObjectVersions(listObjectVersionsParam).promise();
93+
// After deleting other versions, remove delete markers version.
94+
// For info on "delete marker": https://docs.aws.amazon.com/AmazonS3/latest/dev/DeleteMarker.html
95+
let objectsToDelete = [
96+
...listResp.Versions.map(version => ({ Key: version.Key, VersionId: version.VersionId })),
97+
...listResp.DeleteMarkers.map(marker => ({ Key: marker.Key, VersionId: marker.VersionId }))
98+
];
99+
if (objectsToDelete.length === 0) {
100+
return
101+
}
102+
const delResp = await s3.deleteObjects({
103+
Bucket: bucketName,
104+
Delete: {
105+
Objects: objectsToDelete,
106+
Quiet: true
107+
}
108+
}).promise()
109+
if (delResp.Errors.length > 0) {
110+
throw new AggregateError([new Error(`${delResp.Errors.length}/${objectsToDelete.length} objects failed to delete`),
111+
new Error(`first failed on key "${delResp.Errors[0].Key}": ${delResp.Errors[0].Message}`)]);
112+
}
113+
if (!listResp.IsTruncated) {
114+
return
115+
}
116+
listObjectVersionsParam.KeyMarker = listResp.NextKeyMarker
117+
listObjectVersionsParam.VersionIdMarker = listResp.NextVersionIdMarker
118+
}
119+
};
120+
121+
/**
122+
* Correct desired count handler, invoked by Lambda.
123+
*/
124+
exports.handler = async function (event, context) {
125+
var responseData = {};
126+
const props = event.ResourceProperties;
127+
const physicalResourceId = event.PhysicalResourceId || `bucket-cleaner-${event.LogicalResourceId}`;
128+
129+
try {
130+
switch (event.RequestType) {
131+
case "Create":
132+
case "Update":
133+
break;
134+
case "Delete":
135+
await cleanBucket(props.BucketName);
136+
break;
137+
default:
138+
throw new Error(`Unsupported request type ${event.RequestType}`);
139+
}
140+
await report(event, context, "SUCCESS", physicalResourceId, responseData);
141+
} catch (err) {
142+
console.log(`Caught error ${err}.`);
143+
await report(
144+
event,
145+
context,
146+
"FAILED",
147+
physicalResourceId,
148+
null,
149+
`${err.message} (Log: ${defaultLogGroup || context.logGroupName}/${defaultLogStream || context.logStreamName
150+
})`
151+
);
152+
}
153+
};
154+
155+
/**
156+
* @private
157+
*/
158+
exports.withDefaultResponseURL = function (url) {
159+
defaultResponseURL = url;
160+
};
161+
162+
/**
163+
* @private
164+
*/
165+
exports.withDefaultLogStream = function (logStream) {
166+
defaultLogStream = logStream;
167+
};
168+
169+
/**
170+
* @private
171+
*/
172+
exports.withDefaultLogGroup = function (logGroup) {
173+
defaultLogGroup = logGroup;
174+
};
175+
176+
class AggregateError extends Error {
177+
#errors;
178+
name = "AggregateError";
179+
constructor(errors) {
180+
let message = errors
181+
.map(error =>
182+
String(error),
183+
)
184+
.join("\n");
185+
super(message);
186+
this.#errors = errors;
187+
}
188+
get errors() {
189+
return [...this.#errors];
190+
}
191+
}

‎cf-custom-resources/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@
4848
"ws": ">=7.4.6",
4949
"yargs-parser": ">=13.1.2"
5050
}
51-
}
51+
}

0 commit comments

Comments
 (0)
Failed to load comments.