Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions packages/@aws-cdk/toolkit-lib/docs/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ Please let us know by [opening an issue](https://github.com/aws/aws-cdk-cli/issu
| `CDK_TOOLKIT_I9500` | Stack diagnosis (no problems found) | `info` | {@link DiagnosedStack} |
| `CDK_TOOLKIT_E9500` | Stack diagnosis (problems found) | `error` | {@link DiagnosedStack} |
| `CDK_TOOLKIT_W9501` | Stack diagnosis (diagnosis could not be performed) | `warn` | {@link DiagnosedStack} |
| `CDK_TOOLKIT_I9600` | Validate passed with no problems | `info` | {@link ValidateResult} |
| `CDK_TOOLKIT_E9600` | Validate found problems | `error` | {@link ValidateResult} |
| `CDK_TOOLKIT_I9601` | No validation plugins configured | `info` | n/a |
| `CDK_TOOLKIT_I0100` | Notices decoration (the header or footer of a list of notices) | `info` | n/a |
| `CDK_TOOLKIT_W0101` | A notice that is marked as a warning | `warn` | n/a |
| `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a |
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './rollback';
export * from './synth';
export * from './watch';
export * from './diagnose';
export * from './validate';
33 changes: 33 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/actions/validate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { PolicyValidationReportJson, PolicyValidationReportConclusion, PluginReportJson } from '@aws-cdk/cloud-assembly-schema';
import type { StackSelector } from '../../api/cloud-assembly';

export interface ValidateOptions {
/**
* Select the stacks to validate
*/
readonly stacks?: StackSelector;
}

/**
* The result of the validate action
*/
export interface ValidateResult {
/**
* Whether validation passed or failed overall.
* 'success' if no plugins reported failures (or no plugins ran).
* 'failure' if any plugin reported a failure.
*/
readonly conclusion: PolicyValidationReportConclusion;

/**
* The title of the validation report
*/
readonly title?: string;

/**
* Reports from each validation plugin
*/
readonly pluginReports: PluginReportJson[];
}

export type { PolicyValidationReportJson, PolicyValidationReportConclusion, PluginReportJson };
19 changes: 19 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type * as cxapi from '@aws-cdk/cloud-assembly-api';
import * as make from './message-maker';
import type { SpanDefinition } from './span';
import type { DiagnosedStack } from '../../../actions/diagnose';
import type { ValidateResult } from '../../../actions/validate';
import type { StackDiff, DiffResult } from '../../../payloads';
import type { BootstrapEnvironmentProgress } from '../../../payloads/bootstrap-environment-progress';
import type { MissingContext, UpdatedContext } from '../../../payloads/context';
Expand Down Expand Up @@ -504,6 +505,24 @@ export const IO = {
interface: 'DiagnosedStack',
}),

// validate (96xx)
CDK_TOOLKIT_I9600: make.info<ValidateResult>({
code: 'CDK_TOOLKIT_I9600',
description: 'Validate passed with no problems',
interface: 'ValidateResult',
}),

CDK_TOOLKIT_E9600: make.error<ValidateResult>({
code: 'CDK_TOOLKIT_E9600',
description: 'Validate found problems',
interface: 'ValidateResult',
}),

CDK_TOOLKIT_I9601: make.info({
code: 'CDK_TOOLKIT_I9601',
description: 'No validation plugins configured',
}),

// Notices
CDK_TOOLKIT_I0100: make.info({
code: 'CDK_TOOLKIT_I0100',
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export type ToolkitAction =
| 'refactor'
| 'diagnose'
| 'orphan'
| 'flags';
| 'flags'
| 'validate';
48 changes: 47 additions & 1 deletion packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '../private/dispose-polyfill';
import * as path from 'node:path';
import * as cxapi from '@aws-cdk/cloud-assembly-api';
import type { FeatureFlagReportProperties } from '@aws-cdk/cloud-assembly-schema';
import { ArtifactType } from '@aws-cdk/cloud-assembly-schema';
import { ArtifactType, Manifest } from '@aws-cdk/cloud-assembly-schema';
import type { TemplateDiff } from '@aws-cdk/cloudformation-diff';
import * as chalk from 'chalk';
import * as chokidar from 'chokidar';
Expand Down Expand Up @@ -57,6 +57,7 @@ import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publi
import type { RefactorOptions } from '../actions/refactor';
import { type RollbackOptions } from '../actions/rollback';
import { type SynthOptions } from '../actions/synth';
import type { ValidateOptions, ValidateResult, PolicyValidationReportConclusion } from '../actions/validate';
import type { IWatcher, WatchOptions } from '../actions/watch';
import { countAssemblyResults } from './private/count-assembly-results';
import { WATCH_EXCLUDE_DEFAULTS } from '../actions/watch/private';
Expand Down Expand Up @@ -113,6 +114,8 @@ import { pLimit } from '../util/concurrency';
import { createIgnoreMatcher } from '../util/glob-matcher';
import { promiseWithResolvers } from '../util/promises';

const POLICY_VALIDATION_REPORT_FILE = 'policy-validation-report.json';

export interface ToolkitOptions {
/**
* The IoHost implementation, handling the inline interactions between the Toolkit and an integration.
Expand Down Expand Up @@ -649,6 +652,49 @@ export class Toolkit extends CloudAssemblySourceBuilder {
return { stacks };
}

/**
* Validate Action
*
* Synthesizes the CDK app and reads the policy validation report
* from the cloud assembly output directory.
*/
public async validate(cx: ICloudAssemblySource, options: ValidateOptions = {}): Promise<ValidateResult> {
const ioHelper = asIoHelper(this.ioHost, 'validate');
const selectStacks = stacksOpt(options);
await using assembly = await synthAndMeasure(ioHelper, cx, selectStacks);

const reportPath = path.join(assembly.directory, POLICY_VALIDATION_REPORT_FILE);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be an artifact in the manifest, no?

Although I can live with this for a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if (!await fs.pathExists(reportPath)) {
const result: ValidateResult = {
conclusion: 'success',
pluginReports: [],
};
await ioHelper.notify(IO.CDK_TOOLKIT_I9601.msg('No validation plugins configured. Add a plugin to your CDK app to enable validation.'));
return result;
}

const report = Manifest.loadValidationReport(reportPath);

const conclusion: PolicyValidationReportConclusion = report.pluginReports.some(
(pr) => pr.conclusion === 'failure',
) ? 'failure' : 'success';

const result: ValidateResult = {
conclusion,
title: report.title,
pluginReports: report.pluginReports,
};

if (conclusion === 'failure') {
await ioHelper.notify(IO.CDK_TOOLKIT_E9600.msg('❌ cdk validate found problems', result));
} else {
await ioHelper.notify(IO.CDK_TOOLKIT_I9600.msg('✅ No problems found', result));
}

return result;
}

/**
* Deploy Action
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "53.0.0",
"artifacts": {
"Stack1": {
"type": "aws:cloudformation:stack",
"environment": "aws://123456789012/us-east-1",
"properties": {
"templateFile": "Stack1.template.json",
"terminationProtection": false,
"validateOnSynth": false
},
"displayName": "Stack1"
},
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "tree.json"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Validation Report",
"someUnexpectedField": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"Stack1": {
"id": "Stack1",
"path": "Stack1",
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "0.0.0"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "53.0.0",
"artifacts": {
"Stack1": {
"type": "aws:cloudformation:stack",
"environment": "aws://123456789012/us-east-1",
"properties": {
"templateFile": "Stack1.template.json",
"terminationProtection": false,
"validateOnSynth": false
},
"displayName": "Stack1"
},
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "tree.json"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "1.0.0",
"pluginReports": [
{
"pluginName": "TestPlugin",
"conclusion": "failure",
"violations": [
{
"ruleName": "no-public-buckets",
"description": "S3 Buckets must not be publicly accessible",
"severity": "error",
"violatingConstructs": []
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"Stack1": {
"id": "Stack1",
"path": "Stack1",
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "0.0.0"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "53.0.0",
"artifacts": {
"Stack1": {
"type": "aws:cloudformation:stack",
"environment": "aws://123456789012/us-east-1",
"properties": {
"templateFile": "Stack1.template.json",
"terminationProtection": false,
"validateOnSynth": false
},
"displayName": "Stack1"
},
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "tree.json"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1.0.0",
"title": "Validation Report",
"pluginReports": [
{
"pluginName": "TestPlugin",
"pluginVersion": "1.0.0",
"conclusion": "success",
"violations": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"Stack1": {
"id": "Stack1",
"path": "Stack1",
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "0.0.0"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "53.0.0",
"artifacts": {
"Stack1": {
"type": "aws:cloudformation:stack",
"environment": "aws://123456789012/us-east-1",
"properties": {
"templateFile": "Stack1.template.json",
"terminationProtection": false,
"validateOnSynth": false
},
"displayName": "Stack1"
},
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "tree.json"
}
}
}
}
Loading
Loading