Skip to content

Commit

Permalink
chore(integ-tests): refactor assertions provider (#22238)
Browse files Browse the repository at this point in the history
The assertions framework provides utilities for querying data (e.g. `assertions.awsApiCall`) and separate utilities for making assertions on that data. Currently these two actions are separated into separate custom resources so the `awsApiCall` queries data which it returns and then passes to a _separate_ custom resource which makes assertions on that data.

Separating these out is unnecessary since we can make the assertion in the same custom resource. A future PR will introduce the ability to wait for a certain condition which actually requires making the assertion in
  the same resource.


----

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
corymhall committed Oct 2, 2022
1 parent cf43b03 commit ae7b150
Show file tree
Hide file tree
Showing 20 changed files with 421 additions and 436 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"version": "21.0.0",
"files": {
"84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7": {
"d47f7e6772bfdf47ecbc070ffe204baf53bacbfbf7814eb407bd8ea108c1c1bb": {
"source": {
"path": "asset.84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.bundle",
"path": "asset.d47f7e6772bfdf47ecbc070ffe204baf53bacbfbf7814eb407bd8ea108c1c1bb.bundle",
"packaging": "zip"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip",
"objectKey": "d47f7e6772bfdf47ecbc070ffe204baf53bacbfbf7814eb407bd8ea108c1c1bb.zip",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
},
"e0e91fa96756b94719efc2b0e2ea19fcd9c842b56f185f07700616554fcd0eb7": {
"68c9fd01db97c536686e52c360f3ffd305a7e923768b7a0cac2f9e3ba23198e3": {
"source": {
"path": "LogGroupDefaultTestDeployAssert353EE07A.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "e0e91fa96756b94719efc2b0e2ea19fcd9c842b56f185f07700616554fcd0eb7.json",
"objectKey": "68c9fd01db97c536686e52c360f3ffd305a7e923768b7a0cac2f9e3ba23198e3.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"parameters": {
"Entries": [
{
"Detail": "{\"date\":\"abc1661947671805\"}",
"Detail": "{\"date\":\"abc1663960602347\"}",
"DetailType": "cdk-integ-custom-rule",
"Source": "cdk-integ"
}
]
},
"flattenResponse": "false",
"salt": "1661947671807"
"salt": "1663960602348"
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
Expand Down Expand Up @@ -93,7 +93,7 @@
"S3Bucket": {
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
},
"S3Key": "84802aa01d2d2c9e7d8d69705ee832c97f1ebad2d73c72be5c32d53f16cf90a7.zip"
"S3Key": "d47f7e6772bfdf47ecbc070ffe204baf53bacbfbf7814eb407bd8ea108c1c1bb.zip"
},
"Timeout": 120,
"Handler": "index.handler",
Expand All @@ -116,39 +116,17 @@
},
"service": "CloudWatchLogs",
"api": "filterLogEvents",
"expected": "{\"$StringLike\":\"abc1663960602347\"}",
"actualPath": "events.0.message",
"parameters": {
"logGroupName": {
"Fn::ImportValue": "log-group-events:ExportsOutputRefloggroup2F19C5C9B4F4C6918"
},
"startTime": "1661947671805",
"startTime": "1663960602347",
"limit": 1
},
"flattenResponse": "true",
"salt": "1661947671807"
},
"DependsOn": [
"AwsApiCallEventBridgeputEvents"
],
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
"AwsApiCallCloudWatchLogsfilterLogEventsAssertEqualsCloudWatchLogsfilterLogEvents148E959E": {
"Type": "Custom::DeployAssert@AssertEquals",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F",
"Arn"
]
},
"actual": {
"Fn::GetAtt": [
"AwsApiCallCloudWatchLogsfilterLogEvents",
"apiCallResponse.events.0.message"
]
},
"expected": "{\"$StringLike\":\"abc1661947671805\"}",
"salt": "1661947671808"
"salt": "1663960602348"
},
"DependsOn": [
"AwsApiCallEventBridgeputEvents"
Expand All @@ -158,11 +136,11 @@
}
},
"Outputs": {
"AssertionResultsAssertEqualsCloudWatchLogsfilterLogEvents": {
"AssertionResultsAwsApiCallCloudWatchLogsfilterLogEvents": {
"Value": {
"Fn::GetAtt": [
"AwsApiCallCloudWatchLogsfilterLogEventsAssertEqualsCloudWatchLogsfilterLogEvents148E959E",
"data"
"AwsApiCallCloudWatchLogsfilterLogEvents",
"assertion"
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,20 +404,11 @@ var CustomResourceHandler = class {
}
async handle() {
try {
console.log(`Event: ${JSON.stringify({ ...this.event, ResponseURL: "..." })}`);
const response = await this.processEvent(this.event.ResourceProperties);
console.log(`Event output : ${JSON.stringify(response)}`);
await this.respond({
status: "SUCCESS",
reason: "OK",
data: response
});
return response;
} catch (e) {
console.log(e);
await this.respond({
status: "FAILED",
reason: e.message ?? "Internal Error"
});
throw e;
} finally {
clearTimeout(this.timeout);
}
Expand Down Expand Up @@ -479,7 +470,8 @@ var AssertionHandler = class extends CustomResourceHandler {
matchResult.finished();
if (matchResult.hasFailed()) {
result = {
data: JSON.stringify({
failed: true,
assertion: JSON.stringify({
status: "fail",
message: [
...matchResult.toHumanStrings(),
Expand All @@ -488,11 +480,11 @@ var AssertionHandler = class extends CustomResourceHandler {
})
};
if (request2.failDeployment) {
throw new Error(result.data);
throw new Error(result.assertion);
}
} else {
result = {
data: JSON.stringify({
assertion: JSON.stringify({
status: "success"
})
};
Expand Down Expand Up @@ -562,7 +554,10 @@ function flatten(object) {
{},
...function _flatten(child, path = []) {
return [].concat(...Object.keys(child).map((key) => {
const childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key];
let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key];
if (typeof childKey === "string") {
childKey = isJsonString(childKey);
}
return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey };
}));
}(object)
Expand All @@ -572,6 +567,9 @@ var AwsApiCallHandler = class extends CustomResourceHandler {
async processEvent(request2) {
const AWS = require("aws-sdk");
console.log(`AWS SDK VERSION: ${AWS.VERSION}`);
if (!Object.prototype.hasOwnProperty.call(AWS, request2.service)) {
throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS.VERSION}.`);
}
const service = new AWS[request2.service]();
const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise();
console.log(`SDK response received ${JSON.stringify(response)}`);
Expand All @@ -582,28 +580,87 @@ var AwsApiCallHandler = class extends CustomResourceHandler {
const flatData = {
...flatten(respond)
};
return request2.flattenResponse === "true" ? flatData : respond;
const resp = request2.flattenResponse === "true" ? flatData : respond;
console.log(`Returning result ${JSON.stringify(resp)}`);
return resp;
}
};
function isJsonString(value) {
try {
return JSON.parse(value);
} catch {
return value;
}
}

// lib/assertions/providers/lambda-handler/types.ts
var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals";
var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall";

// lib/assertions/providers/lambda-handler/index.ts
async function handler(event, context) {
console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`);
const provider = createResourceHandler(event, context);
await provider.handle();
try {
if (event.RequestType === "Delete") {
await provider.respond({
status: "SUCCESS",
reason: "OK"
});
return;
}
const result = await provider.handle();
const actualPath = event.ResourceProperties.actualPath;
const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse;
if ("expected" in event.ResourceProperties) {
const assertion = new AssertionHandler({
...event,
ResourceProperties: {
ServiceToken: event.ServiceToken,
actual,
expected: event.ResourceProperties.expected
}
}, context);
try {
const assertionResult = await assertion.handle();
await provider.respond({
status: "SUCCESS",
reason: "OK",
data: {
...assertionResult,
...result
}
});
return;
} catch (e) {
await provider.respond({
status: "FAILED",
reason: e.message ?? "Internal Error"
});
return;
}
}
await provider.respond({
status: "SUCCESS",
reason: "OK",
data: result
});
} catch (e) {
await provider.respond({
status: "FAILED",
reason: e.message ?? "Internal Error"
});
return;
}
return;
}
function createResourceHandler(event, context) {
if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) {
return new AwsApiCallHandler(event, context);
}
switch (event.ResourceType) {
case ASSERT_RESOURCE_TYPE:
return new AssertionHandler(event, context);
default:
throw new Error(`Unsupported resource type "${event.ResourceType}`);
} else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) {
return new AssertionHandler(event, context);
} else {
throw new Error(`Unsupported resource type "${event.ResourceType}`);
}
}
// Annotate the CommonJS export names for ESM import in node:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/e0e91fa96756b94719efc2b0e2ea19fcd9c842b56f185f07700616554fcd0eb7.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/68c9fd01db97c536686e52c360f3ffd305a7e923768b7a0cac2f9e3ba23198e3.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down Expand Up @@ -207,16 +207,10 @@
"data": "AwsApiCallCloudWatchLogsfilterLogEvents"
}
],
"/LogGroup/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/AssertEqualsCloudWatchLogsfilterLogEvents/Default/Default": [
"/LogGroup/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/AssertionResults": [
{
"type": "aws:cdk:logicalId",
"data": "AwsApiCallCloudWatchLogsfilterLogEventsAssertEqualsCloudWatchLogsfilterLogEvents148E959E"
}
],
"/LogGroup/DefaultTest/DeployAssert/AwsApiCallCloudWatchLogsfilterLogEvents/AssertEqualsCloudWatchLogsfilterLogEvents/AssertionResults": [
{
"type": "aws:cdk:logicalId",
"data": "AssertionResultsAssertEqualsCloudWatchLogsfilterLogEvents"
"data": "AssertionResultsAwsApiCallCloudWatchLogsfilterLogEvents"
}
],
"/LogGroup/DefaultTest/DeployAssert/BootstrapVersion": [
Expand Down

0 comments on commit ae7b150

Please sign in to comment.