Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/aws-cdk-lib/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1545,7 +1545,7 @@ validation.
> etc. It's your responsibility as the consumer of a plugin to verify that it is
> secure to use.

By default, the report will be printed in a human readable format. If you want a
By default, the report will be printed in a human-readable format. If you want a
report in JSON format, enable it using the `@aws-cdk/core:validationReportJson`
context passing it directly to the application:

Expand All @@ -1559,6 +1559,19 @@ Alternatively, you can set this context key-value pair using the `cdk.json` or
`cdk.context.json` files in your project directory (see
[Runtime context](https://docs.aws.amazon.com/cdk/v2/guide/context.html)).

This will enable both formats. The human-readable format must be explicitly disabled
to be suppressed, using the
`@aws-cdk/core:validationReportPrettyPrint` context key:

```ts
const app = new App({
context: {
'@aws-cdk/core:validationReportJson': true,
'@aws-cdk/core:validationReportPrettyPrint': false,
},
});
```

If you choose the JSON format, the CDK will print the policy validation report
to a file called `policy-validation-report.json` in the cloud assembly
directory. For the default, human-readable format, the report will be printed to
Expand Down
21 changes: 12 additions & 9 deletions packages/aws-cdk-lib/core/lib/private/synthesis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ConstructTree } from '../validation/private/construct-tree';
import { PolicyValidationReportFormatter, NamedValidationPluginReport } from '../validation/private/report';

const POLICY_VALIDATION_FILE_PATH = 'policy-validation-report.json';
const VALIDATION_REPORT_PRETTY_CONTEXT = '@aws-cdk/core:validationReportPrettyPrint';
const VALIDATION_REPORT_JSON_CONTEXT = '@aws-cdk/core:validationReportJson';

/**
Expand Down Expand Up @@ -147,24 +148,26 @@ function invokeValidationPlugins(root: IConstruct, outdir: string, assembly: Clo
if (reports.length > 0) {
const tree = new ConstructTree(root);
const formatter = new PolicyValidationReportFormatter(tree);
const formatPretty = root.node.tryGetContext(VALIDATION_REPORT_PRETTY_CONTEXT) ?? true;
const formatJson = root.node.tryGetContext(VALIDATION_REPORT_JSON_CONTEXT) ?? false;
const output = formatJson
? formatter.formatJson(reports)
: formatter.formatPrettyPrinted(reports);

const reportFile = path.join(assembly.directory, POLICY_VALIDATION_FILE_PATH);
if (formatJson) {
fs.writeFileSync(reportFile, JSON.stringify(output, undefined, 2));
} else {
if (formatPretty) {
const output = formatter.formatPrettyPrinted(reports);
// eslint-disable-next-line no-console
console.error(output);
}
if (formatJson) {
const output = formatter.formatJson(reports);
fs.writeFileSync(reportFile, JSON.stringify(output, undefined, 2));
}
const failed = reports.some(r => !r.success);
if (failed) {
const message = formatJson
let message = formatJson
? `Validation failed. See the validation report in '${reportFile}' for details`
: 'Validation failed. See the validation report above for details';

if (formatPretty && formatJson) {
message = `Validation failed. See the validation report in '${reportFile}' and above for details`;
}
// eslint-disable-next-line no-console
console.log(message);
process.exitCode = 1;
Expand Down
93 changes: 91 additions & 2 deletions packages/aws-cdk-lib/core/test/validation/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,10 @@ Policy Validation Report Summary
}],
}]),
],
context: { '@aws-cdk/core:validationReportJson': true },
context: {
'@aws-cdk/core:validationReportJson': true,
'@aws-cdk/core:validationReportPrettyPrint': false,
},
});
const stack = new core.Stack(app);
new core.CfnResource(stack, 'Fake', {
Expand All @@ -642,8 +645,90 @@ Policy Validation Report Summary
});
app.synth();
expect(process.exitCode).toEqual(1);
const file = path.join(app.outdir, 'policy-validation-report.json');
const report = fs.readFileSync(file).toString('utf-8');
expect(JSON.parse(report)).toEqual(expect.objectContaining({
title: 'Validation Report',
pluginReports: [
{
summary: {
pluginName: 'test-plugin',
status: 'failure',
},
violations: [
{
ruleName: 'test-rule',
description: 'test recommendation',
ruleMetadata: { id: 'abcdefg' },
violatingResources: [{
'locations': [
'test-location',
],
'resourceLogicalId': 'Fake',
'templatePath': '/path/to/Default.template.json',
}],
violatingConstructs: [
{
constructStack: {
'id': 'Default',
'construct': expect.stringMatching(/(aws-cdk-lib.Stack|Construct)/),
'libraryVersion': expect.any(String),
'location': "Run with '--debug' to include location info",
'path': 'Default',
'child': {
'id': 'Fake',
'construct': expect.stringMatching(/(aws-cdk-lib.CfnResource|Construct)/),
'libraryVersion': expect.any(String),
'location': "Run with '--debug' to include location info",
'path': 'Default/Fake',
},
},
constructPath: 'Default/Fake',
locations: ['test-location'],
resourceLogicalId: 'Fake',
templatePath: '/path/to/Default.template.json',
},
],
},
],
},
],
}));
const consoleOut = consoleLogMock.mock.calls[1][0];
expect(consoleOut).toContain(`Validation failed. See the validation report in \'${file}\' for details`);
});

const report = fs.readFileSync(path.join(app.outdir, 'policy-validation-report.json')).toString('utf-8');
test('Multi format', () => {
const app = new core.App({
policyValidationBeta1: [
new FakePlugin('test-plugin', [{
description: 'test recommendation',
ruleName: 'test-rule',
ruleMetadata: {
id: 'abcdefg',
},
violatingResources: [{
locations: ['test-location'],
resourceLogicalId: 'Fake',
templatePath: '/path/to/Default.template.json',
}],
}]),
],
context: {
'@aws-cdk/core:validationReportJson': true,
},
});
const stack = new core.Stack(app);
new core.CfnResource(stack, 'Fake', {
type: 'Test::Resource::Fake',
properties: {
result: 'failure',
},
});
app.synth();
expect(process.exitCode).toEqual(1);
const file = path.join(app.outdir, 'policy-validation-report.json');
const report = fs.readFileSync(file).toString('utf-8');
expect(JSON.parse(report)).toEqual(expect.objectContaining({
title: 'Validation Report',
pluginReports: [
Expand Down Expand Up @@ -691,6 +776,10 @@ Policy Validation Report Summary
},
],
}));
const consoleOut = consoleLogMock.mock.calls[1][0];
expect(consoleOut).toContain(`Validation failed. See the validation report in \'${file}\' and above for details`);
const consoleReport = consoleErrorMock.mock.calls[0][0];
expect(consoleReport).toContain('Validation Report');
});
});

Expand Down
Loading