diff --git a/API.md b/API.md index 328bc304d2..6462bb9d2d 100644 --- a/API.md +++ b/API.md @@ -317,9 +317,22 @@ protected ignoreRule(ignores: Array, ruleId: string, finding __Returns__: * string +#### protected initializeStackReport(params) + +Initialize the report for the rule pack's compliance report for the resource's Stack if it doesn't exist. + +```ts +protected initializeStackReport(params: IApplyRule): void +``` + +* **params** ([IApplyRule](#cdk-nag-iapplyrule)) *No description* + + + + #### protected writeToStackComplianceReport(params, ruleId, compliance, explanation?) -Write a line to the rule packs compliance report for the resource's Stack. +Write a line to the rule pack's compliance report for the resource's Stack. ```ts protected writeToStackComplianceReport(params: IApplyRule, ruleId: string, compliance: NagRuleCompliance | string, explanation?: string): void diff --git a/src/nag-pack.ts b/src/nag-pack.ts index 8e5e0ea981..b3f8758fb1 100644 --- a/src/nag-pack.ts +++ b/src/nag-pack.ts @@ -121,12 +121,13 @@ export abstract class NagPack implements IAspect { const ruleId = `${this.packName}-${ruleSuffix}`; try { const ruleCompliance = params.rule(params.node); - if ( - this.reports === true && - ruleCompliance === NagRuleCompliance.COMPLIANT - ) { - this.writeToStackComplianceReport(params, ruleId, ruleCompliance); - } else if (this.isNonCompliant(ruleCompliance)) { + if (this.reports === true) { + this.initializeStackReport(params); + if (ruleCompliance === NagRuleCompliance.COMPLIANT) { + this.writeToStackComplianceReport(params, ruleId, ruleCompliance); + } + } + if (this.isNonCompliant(ruleCompliance)) { const findings = this.asFindings(ruleCompliance); for (const findingId of findings) { const suppressionReason = this.ignoreRule( @@ -243,7 +244,7 @@ export abstract class NagPack implements IAspect { } /** - * Write a line to the rule packs compliance report for the resource's Stack + * Write a line to the rule pack's compliance report for the resource's Stack * @param params The @IApplyRule interface with rule details. * @param ruleId The id of the rule. * @param compliance The compliance status of the rule. @@ -264,20 +265,33 @@ export abstract class NagPack implements IAspect { compliance, explanation ); - let outDir = App.of(params.node)?.outdir; + const outDir = App.of(params.node)?.outdir; const stackName = params.node.stack.nested ? Names.uniqueId(params.node.stack) : params.node.stack.stackName; const fileName = `${this.packName}-${stackName}-NagReport.csv`; const filePath = join(outDir ? outDir : '', fileName); + appendFileSync(filePath, line); + } + + /** + * Initialize the report for the rule pack's compliance report for the resource's Stack if it doesn't exist + * @param params + */ + protected initializeStackReport(params: IApplyRule): void { + const stackName = params.node.stack.nested + ? Names.uniqueId(params.node.stack) + : params.node.stack.stackName; + const fileName = `${this.packName}-${stackName}-NagReport.csv`; if (!this.reportStacks.includes(fileName)) { + const outDir = App.of(params.node)?.outdir; + const filePath = join(outDir ? outDir : '', fileName); this.reportStacks.push(fileName); writeFileSync( filePath, 'Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info\n' ); } - appendFileSync(filePath, line); } /** diff --git a/test/Engine.test.ts b/test/Engine.test.ts index a4dce42a39..4be2da6652 100644 --- a/test/Engine.test.ts +++ b/test/Engine.test.ts @@ -915,7 +915,9 @@ describe('Report system', () => { explanation: 'bar.', level: NagMessageLevel.ERROR, rule: function (node2: CfnResource): NagRuleCompliance { - if (node2.cfnResourceType !== 'Error') { + if (node2.cfnResourceType === 'N/A') { + return NagRuleCompliance.NOT_APPLICABLE; + } else if (node2.cfnResourceType !== 'Error') { return compliance; } throw Error('foobar'); @@ -958,6 +960,18 @@ describe('Report system', () => { app.synth(); expect(pack.readReportStacks.length).toEqual(2); }); + test('Reports are initialized for stacks with no relevant resources', () => { + const app = new App(); + const stack = new Stack(app, 'Stack1'); + const pack = new TestPack(); + Aspects.of(app).add(pack); + new CfnResource(stack, 'rNAResource', { + type: 'N/A', + }); + app.synth(); + expect(pack.readReportStacks.length).toEqual(1); + expect(pack.lines.length).toBe(0); + }); test('Nested Stack reports do not contain tokens in names', () => { const app = new App(); const parent = new Stack(app, 'Parent');