Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): template validation after synthesis #23951

Merged
merged 112 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
420efda
First draft
otaviomacedo Feb 1, 2023
d5e1d8b
Integrated with `App`
otaviomacedo Feb 3, 2023
c140db7
Checking whether checkov is installed
otaviomacedo Feb 7, 2023
aa421f4
Added kics validation
otaviomacedo Feb 7, 2023
0ef0c9f
Applying the cfn-guard validation
SankyRed Feb 16, 2023
69fe823
Moved validation to a separate phase
otaviomacedo Feb 17, 2023
aa0f21b
Added creation stack (class and id only)
otaviomacedo Feb 17, 2023
24bfd47
Removed unused interface
otaviomacedo Feb 20, 2023
d39ca9a
Taking a stab at the README
otaviomacedo Feb 20, 2023
46839e0
List of resources and constructs in ValidationViolationResourceAware …
otaviomacedo Feb 21, 2023
577b7bb
Fixed compilation error
otaviomacedo Feb 21, 2023
81c41c0
Added stack trace to the report
otaviomacedo Feb 21, 2023
f47bd62
Revert tree-metadata.ts to the original state
otaviomacedo Feb 21, 2023
c85d01c
creating an integration test package (private) and move the test plugins
corymhall Feb 21, 2023
9750076
some minor changes to validation
corymhall Feb 21, 2023
7b3205e
adding some initial unit tests
corymhall Feb 22, 2023
06ce483
adding some docs
corymhall Feb 22, 2023
07ed3c2
removing stdout option since it's not used
corymhall Feb 22, 2023
6963cd3
some work on allowing multiple plugins
corymhall Feb 22, 2023
c721590
Added more docs
otaviomacedo Feb 23, 2023
55ad8d9
New unit test
otaviomacedo Feb 23, 2023
506bac2
Removed logger
otaviomacedo Feb 23, 2023
dea5196
adding better trace info
corymhall Feb 23, 2023
66a23b1
making the validation report work per stack rather than per plugin
corymhall Feb 23, 2023
a1c79e6
Added more information to the README
otaviomacedo Feb 24, 2023
a370653
Fixed calls in the README
otaviomacedo Feb 24, 2023
1219c80
refactoring some things.
corymhall Feb 24, 2023
7764190
execute plugins at the stage level
corymhall Feb 24, 2023
858fbe9
fixing build and test
corymhall Feb 24, 2023
9072829
Comparing checksum after validation to prevent tampering
otaviomacedo Feb 27, 2023
098234e
Added note about modifying the cloud assembly
otaviomacedo Feb 27, 2023
bd64c77
removing parent from tree.json
corymhall Feb 27, 2023
b3b729d
adding validation plugins to analytics metadata
corymhall Feb 27, 2023
0791557
adding comments
corymhall Feb 27, 2023
eec9942
removing integ tests
corymhall Feb 28, 2023
5e0cc21
More unit tests
otaviomacedo Feb 28, 2023
a5be452
Hash and tree computation made lazy
otaviomacedo Feb 28, 2023
e10f76c
Refactoring: validation report logic split into a data structure and …
otaviomacedo Mar 3, 2023
b35a92e
Added test for multiple plugins
otaviomacedo Mar 3, 2023
893cdf5
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 3, 2023
2abdbc9
Merge from main
otaviomacedo Mar 3, 2023
ebc0b48
Add an entry to the report when a plugin is not ready and keep going
otaviomacedo Mar 7, 2023
7573d93
Added `severity` to `ValidationViolation`
otaviomacedo Mar 7, 2023
bb4db9b
Remove spurious files
otaviomacedo Mar 7, 2023
dc8f59d
Removed spurious files
otaviomacedo Mar 7, 2023
ef820b5
Added doc to `validationPlugins` property
otaviomacedo Mar 7, 2023
c2b859d
User can choose report format
otaviomacedo Mar 7, 2023
659f7c3
ValidationContext -> IValidationContext
otaviomacedo Mar 7, 2023
df6c345
toJson -> formatJson; toString -> formatPrettyPrinted
otaviomacedo Mar 7, 2023
51b52e1
recommendation -> description
otaviomacedo Mar 7, 2023
bb6f6a1
resourceName -> resourceLogicalId
otaviomacedo Mar 7, 2023
e8cfc47
ValidationReport -> ValidationPluginReport
otaviomacedo Mar 7, 2023
1159fa4
Interfaces used only by the framework made private
otaviomacedo Mar 7, 2023
8311e7a
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 7, 2023
4413d4f
Updated aws-cdk-lib dependencies
otaviomacedo Mar 7, 2023
cc3f8f0
Removed `isReady()` from `IValidationPlugin`
otaviomacedo Mar 7, 2023
1e9e47d
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 7, 2023
cf34d40
Error message thrown by plugins added to the report
otaviomacedo Mar 8, 2023
dbdbea8
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 8, 2023
7ac53dc
refactoring the construct trace logic into a separate class
corymhall Mar 8, 2023
db416ef
Updated yarn.lock
otaviomacedo Mar 8, 2023
34637cf
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 8, 2023
f31d298
Removed pluginName from the fields that plugins need to return from v…
otaviomacedo Mar 8, 2023
60ae2a9
Updated aws-cdk-lib README
otaviomacedo Mar 8, 2023
d1bffce
Updated NOTICE file
otaviomacedo Mar 9, 2023
25cf6c2
Removed isReady mentions from the README and added a warning about th…
otaviomacedo Mar 9, 2023
120b393
Empty constructPath replaced with 'N/A' only in the pretty-printed fo…
otaviomacedo Mar 9, 2023
ee615d4
Updated README
otaviomacedo Mar 9, 2023
f192fde
fixing pretty print report
corymhall Mar 9, 2023
71944ab
get the template paths from the cloud assembly
corymhall Mar 10, 2023
973f000
fixing test
corymhall Mar 10, 2023
5fc3018
Controlling the format via the application context
otaviomacedo Mar 10, 2023
3b833e0
Added note about the feature being experimental
otaviomacedo Mar 10, 2023
5ced641
fixing trace
corymhall Mar 13, 2023
fa84b14
removing trailing space
corymhall Mar 13, 2023
ba5d710
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 13, 2023
534d3fa
fixing trace test
corymhall Mar 14, 2023
bb8a089
fixing build
corymhall Mar 14, 2023
bd4e36a
Merge branch 'main' into otaviom/checkov-poc
corymhall Mar 15, 2023
7946cbb
adding ruleMetadata
corymhall Mar 15, 2023
3e1bcf1
adding rule metadata field
corymhall Mar 16, 2023
60da765
renaming everything to `PolicyValidation`
corymhall Mar 16, 2023
93d1255
Apply suggestions from code review
corymhall Mar 21, 2023
661b53a
addressing review comments
corymhall Mar 22, 2023
2e81c15
cleaning up logic
corymhall Mar 22, 2023
5568496
fixing yarn.lock
corymhall Mar 22, 2023
a7c0271
updates based on review
corymhall Mar 22, 2023
4e75df8
Clarified the use of context
otaviomacedo Mar 22, 2023
f2e0eba
adding tests for stages and fixing related issues
corymhall Mar 22, 2023
c7a1b34
update docstring
corymhall Mar 22, 2023
8ae9a0f
fixing tests for multiple stages
corymhall Mar 22, 2023
450b25c
more changes based on review
corymhall Mar 22, 2023
99aba79
refactoring trace cache methods
corymhall Mar 22, 2023
9b50d09
Updating report format
corymhall Mar 22, 2023
25e098a
fixing report
corymhall Mar 22, 2023
4fd03a1
renaming public api to beta1
corymhall Mar 22, 2023
8c021cc
Update packages/@aws-cdk/core/lib/private/synthesis.ts
otaviomacedo Mar 27, 2023
f5bf8fc
Mentioning the policy-validation-report.json file in the README
otaviomacedo Mar 27, 2023
6495177
Fixed test
otaviomacedo Mar 27, 2023
e5c4775
Added beta1 prefixes
otaviomacedo Mar 27, 2023
ccaf159
Added version field
otaviomacedo Mar 27, 2023
9e60ea0
Sync aws-cdk-lib readme with core readme
otaviomacedo Mar 27, 2023
b0dabdd
Merge branch 'main' into otaviom/checkov-poc
otaviomacedo Mar 27, 2023
cc4144c
Update packages/@aws-cdk/core/README.md
otaviomacedo Mar 27, 2023
85e2e0d
Update packages/@aws-cdk/core/README.md
otaviomacedo Mar 27, 2023
5c5a87c
Update packages/@aws-cdk/core/README.md
otaviomacedo Mar 27, 2023
dbe5cc7
Update packages/@aws-cdk/core/README.md
otaviomacedo Mar 27, 2023
3e73679
Apply suggestions from code review
otaviomacedo Mar 27, 2023
f15a688
fixing build
corymhall Mar 27, 2023
0a2e6b9
removing from feature flags
corymhall Mar 27, 2023
3ef2a76
fixing build
corymhall Mar 27, 2023
4746872
Merge branch 'main' into otaviom/checkov-poc
corymhall Mar 27, 2023
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@
"@aws-cdk/assertions-alpha/fs-extra/**",
"@aws-cdk/assertions/fs-extra",
"@aws-cdk/assertions/fs-extra/**",
"@aws-cdk/aws-iot-actions-alpha/case",
"@aws-cdk/aws-iot-actions-alpha/case/**",
"@aws-cdk/aws-codebuild/yaml",
"@aws-cdk/aws-codebuild/yaml/**",
"@aws-cdk/aws-codepipeline-actions/case",
Expand All @@ -99,6 +97,8 @@
"@aws-cdk/aws-eks/yaml/**",
"@aws-cdk/aws-events-targets/aws-sdk",
"@aws-cdk/aws-events-targets/aws-sdk/**",
"@aws-cdk/aws-iot-actions-alpha/case",
"@aws-cdk/aws-iot-actions-alpha/case/**",
"@aws-cdk/aws-iot-actions/case",
"@aws-cdk/aws-iot-actions/case/**",
"@aws-cdk/aws-s3-deployment/case",
Expand All @@ -117,6 +117,8 @@
"@aws-cdk/core/ignore/**",
"@aws-cdk/core/minimatch",
"@aws-cdk/core/minimatch/**",
"@aws-cdk/core/table",
"@aws-cdk/core/table/**",
"@aws-cdk/cx-api/semver",
"@aws-cdk/cx-api/semver/**",
"@aws-cdk/pipelines/aws-sdk",
Expand Down
114 changes: 114 additions & 0 deletions packages/@aws-cdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1302,4 +1302,118 @@ permissions boundary attached.

For more details see the [Permissions Boundary](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam-readme.html#permissions-boundaries) section in the IAM guide.

## Policy Validation

If you or your organization use (or would like to use) any policy validation tool, such as
[CloudFormation
Guard](https://docs.aws.amazon.com/cfn-guard/latest/ug/what-is-guard.html) or
[OPA](https://www.openpolicyagent.org/), to define constraints on your
CloudFormation template, you can incorporate them into the CDK application.
By using the appropriate plugin, you can make the CDK application check the
generated CloudFormation templates against your policies immediately after
synthesis. If there are any violations, the synthesis will fail and a report
will be printed to the console or to a file (see below).

> **Note**
> This feature is considered experimental, and both the plugin API and the
iliapolo marked this conversation as resolved.
Show resolved Hide resolved
> format of the validation report are subject to change in the future.

### For application developers

To use one or more validation plugins in your application, use the
`policyValidationBeta1` property of `Stage`:

```ts
// globally for the entire app (an app is a stage)
const app = new App({
policyValidationBeta1: [
// These hypothetical classes implement IValidationPlugin:
new ThirdPartyPluginX(),
new ThirdPartyPluginY(),
],
});

// only apply to a particular stage
const prodStage = new Stage(app, 'ProdStage', {
policyValidationBeta1: [...],
});
```

Immediately after synthesis, all plugins registered this way will be invoked to
validate all the templates generated in the scope you defined. In particular, if
you register the templates in the `App` object, all templates will be subject to
validation.

> **Warning**
> Other than modifying the cloud assembly, plugins can do anything that your CDK
> application can. They can read data from the filesystem, access the network
> 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
report in JSON format, enable it using the `@aws-cdk/core:validationReportJson`
context passing it directly to the application:

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

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)).

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
the standard output.

### For plugin authors

The communication protocol between the CDK core module and your policy tool is
defined by the `IValidationPluginBeta1` interface. To create a new plugin you must
write a class that implements this interface. There are two things you need to
implement: the plugin name (by overriding the `name` property), and the
`validate()` method.

The framework will call `validate()`, passing an `IValidationContextBeta1` object.
The location of the templates to be validated is given by `templatePaths`. The
plugin should return an instance of `ValidationPluginReportBeta1`. This object
represents the report that the user wil receive at the end of the synthesis.

```ts
validate(context: ValidationContextBeta1): ValidationReportBeta1 {
// First read the templates using context.templatePaths...

// ...then perform the validation, and then compose and return the report.
// Using hard-coded values here for better clarity:
return {
success: false,
violations: [{
ruleName: 'CKV_AWS_117',
recommendation: 'Ensure that AWS Lambda function is configured inside a VPC',
fix: 'https://docs.bridgecrew.io/docs/ensure-that-aws-lambda-function-is-configured-inside-a-vpc-1',
violatingResources: [{
resourceName: 'MyFunction3BAA72D1',
templatePath: '/home/johndoe/myapp/cdk.out/MyService.template.json',
locations: 'Properties/VpcConfig',
}],
}],
};
}
```

Note that plugins are not allowed to modify anything in the cloud assembly. Any
attempt to do so will result in synthesis failure.

If your plugin depends on an external tool, keep in mind that some developers may
not have that tool installed in their workstations yet. To minimize friction, we
highly recommend that you provide some installation script along with your
plugin package, to automate the whole process. Better yet, run that script as
part of the installation of your package. With `npm`, for example, you can run
add it to the `postinstall`
otaviomacedo marked this conversation as resolved.
Show resolved Hide resolved
[script](https://docs.npmjs.com/cli/v9/using-npm/scripts) in the `package.json`
file.

<!--END CORE DOCUMENTATION-->
9 changes: 9 additions & 0 deletions packages/@aws-cdk/core/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PRIVATE_CONTEXT_DEFAULT_STACK_SYNTHESIZER } from './private/private-con
import { addCustomSynthesis, ICustomSynthesis } from './private/synthesis';
import { IReusableStackSynthesizer } from './stack-synthesizers';
import { Stage } from './stage';
import { IPolicyValidationPluginBeta1 } from './validation/validation';

const APP_SYMBOL = Symbol.for('@aws-cdk/core.App');

Expand Down Expand Up @@ -118,6 +119,13 @@ export interface AppProps {
* @default - A `DefaultStackSynthesizer` with default settings
*/
readonly defaultStackSynthesizer?: IReusableStackSynthesizer;

/**
* Validation plugins to run after synthesis
*
* @default - no validation plugins
*/
readonly policyValidationBeta1?: IPolicyValidationPluginBeta1[];
}

/**
Expand Down Expand Up @@ -159,6 +167,7 @@ export class App extends Stage {
constructor(props: AppProps = {}) {
super(undefined as any, '', {
outdir: props.outdir ?? process.env[cxapi.OUTDIR_ENV],
policyValidationBeta1: props.policyValidationBeta1,
});

Object.defineProperty(this, APP_SYMBOL, { value: true });
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export * from './cloudformation.generated';
export * from './feature-flags';
export * from './permissions-boundary';

export * from './validation';

// WARNING: Should not be exported, but currently is because of a bug. See the
// class description for more information.
export * from './private/intrinsic';
Expand Down
31 changes: 31 additions & 0 deletions packages/@aws-cdk/core/lib/private/runtime-info.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IConstruct } from 'constructs';
import { App } from '../app';
import { Stack } from '../stack';
import { Stage } from '../stage';

Expand Down Expand Up @@ -38,6 +39,34 @@ export function constructInfoFromConstruct(construct: IConstruct): ConstructInfo
return undefined;
}

/**
* Add analytics data for any validation plugins that are used.
* Since validation plugins are not constructs we have to handle them
* as a special case
*/
function addValidationPluginInfo(stack: Stack, allConstructInfos: ConstructInfo[]): void {
let stage = Stage.of(stack);
let done = false;
do {
iliapolo marked this conversation as resolved.
Show resolved Hide resolved
if (App.isApp(stage)) {
done = true;
}
if (stage) {
allConstructInfos.push(...stage.policyValidationBeta1.map(
plugin => {
return {
// the fqn can be in the format of `package.module.construct`
// those get pulled out into separate fields
fqn: `policyValidation.${plugin.name}`,
version: plugin.version ?? '0.0.0',
};
},
));
stage = Stage.of(stage);
}
} while (!done && stage);
}

/**
* For a given stack, walks the tree and finds the runtime info for all constructs within the tree.
* Returns the unique list of construct info present in the stack,
Expand All @@ -57,6 +86,8 @@ export function constructInfoFromStack(stack: Stack): ConstructInfo[] {
version: getJsiiAgentVersion(),
});

addValidationPluginInfo(stack, allConstructInfos);

// Filter out duplicate values
const uniqKeys = new Set();
return allConstructInfos.filter(construct => {
Expand Down
138 changes: 137 additions & 1 deletion packages/@aws-cdk/core/lib/private/synthesis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { createHash } from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as cxapi from '@aws-cdk/cx-api';
import { CloudAssembly } from '@aws-cdk/cx-api';
import { IConstruct } from 'constructs';
import { MetadataResource } from './metadata-resource';
import { prepareApp } from './prepare-app';
Expand All @@ -9,6 +13,12 @@ import { Aspects, IAspect } from '../aspect';
import { Stack } from '../stack';
import { ISynthesisSession } from '../stack-synthesizers/types';
import { Stage, StageSynthesisOptions } from '../stage';
import { IPolicyValidationPluginBeta1 } from '../validation';
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_JSON_CONTEXT = '@aws-cdk/core:validationReportJson';

/**
* Options for `synthesize()`
Expand Down Expand Up @@ -49,7 +59,115 @@ export function synthesize(root: IConstruct, options: SynthesisOptions = { }): c
// stacks to add themselves to the synthesized cloud assembly.
synthesizeTree(root, builder, options.validateOnSynthesis);

return builder.buildAssembly();
const assembly = builder.buildAssembly();

invokeValidationPlugins(root, builder.outdir, assembly);

return assembly;
}

/**
* Find all the assemblies in the app, including all levels of nested assemblies
* and return a map where the assemblyId is the key
*/
function getAssemblies(root: App, rootAssembly: CloudAssembly): Map<string, CloudAssembly> {
const assemblies = new Map<string, CloudAssembly>();
assemblies.set(root.artifactId, rootAssembly);
visitAssemblies(root, 'pre', construct => {
const stage = construct as Stage;
if (stage.parentStage && assemblies.has(stage.parentStage.artifactId)) {
assemblies.set(
stage.artifactId,
assemblies.get(stage.parentStage.artifactId)!.getNestedAssembly(stage.artifactId),
);
}
});
return assemblies;
}

/**
* Invoke validation plugins for all stages in an App.
*/
function invokeValidationPlugins(root: IConstruct, outdir: string, assembly: CloudAssembly) {
if (!App.isApp(root)) return;
const hash = computeChecksumOfFolder(outdir);
const assemblies = getAssemblies(root, assembly);
const templatePathsByPlugin: Map<IPolicyValidationPluginBeta1, string[]> = new Map();
visitAssemblies(root, 'post', construct => {
if (Stage.isStage(construct)) {
for (const plugin of construct.policyValidationBeta1) {
if (!templatePathsByPlugin.has(plugin)) {
iliapolo marked this conversation as resolved.
Show resolved Hide resolved
templatePathsByPlugin.set(plugin, []);
}
let assemblyToUse = assemblies.get(construct.artifactId);
if (!assemblyToUse) throw new Error(`Validation failed, cannot find cloud assembly for stage ${construct.stageName}`);
templatePathsByPlugin.get(plugin)!.push(...assemblyToUse.stacksRecursively.map(stack => stack.templateFullPath));
}
}
});

const reports: NamedValidationPluginReport[] = [];
if (templatePathsByPlugin.size > 0) {
// eslint-disable-next-line no-console
console.log('Performing Policy Validations\n');
}
for (const [plugin, paths] of templatePathsByPlugin.entries()) {
try {
const report = plugin.validate({ templatePaths: paths });
reports.push({ ...report, pluginName: plugin.name });
} catch (e: any) {
reports.push({
success: false,
pluginName: plugin.name,
pluginVersion: plugin.version,
violations: [],
metadata: {
error: `Validation plugin '${plugin.name}' failed: ${e.message}`,
},
});
}
if (computeChecksumOfFolder(outdir) !== hash) {
throw new Error(`Illegal operation: validation plugin '${plugin.name}' modified the cloud assembly`);
}
}

if (reports.length > 0) {
const tree = new ConstructTree(root);
const formatter = new PolicyValidationReportFormatter(tree);
const formatJson = root.node.tryGetContext(VALIDATION_REPORT_JSON_CONTEXT) ?? false;
const output = formatJson
? formatter.formatJson(reports)
: formatter.formatPrettyPrinted(reports);

if (formatJson) {
fs.writeFileSync(path.join(assembly.directory, POLICY_VALIDATION_FILE_PATH), JSON.stringify(output, undefined, 2));
otaviomacedo marked this conversation as resolved.
Show resolved Hide resolved
} else {
// eslint-disable-next-line no-console
console.error(output);
}
const failed = reports.some(r => !r.success);
if (failed) {
throw new Error('Validation failed. See the validation report above for details');
} else {
// eslint-disable-next-line no-console
console.log('Policy Validation Successful!');
}
}
iliapolo marked this conversation as resolved.
Show resolved Hide resolved
}

function computeChecksumOfFolder(folder: string): string {
const hash = createHash('sha256');
const files = fs.readdirSync(folder, { withFileTypes: true });

for (const file of files) {
const fullPath = path.join(folder, file.name);
if (file.isDirectory()) {
hash.update(computeChecksumOfFolder(fullPath));
} else if (file.isFile()) {
hash.update(fs.readFileSync(fullPath));
}
}
return hash.digest().toString('hex');
}

const CUSTOM_SYNTHESIS_SYM = Symbol.for('@aws-cdk/core:customSynthesis');
Expand Down Expand Up @@ -232,6 +350,24 @@ function validateTree(root: IConstruct) {
}
}

/**
* Visit the given construct tree in either pre or post order, only looking at Assemblies
*/
function visitAssemblies(root: IConstruct, order: 'pre' | 'post', cb: (x: IConstruct) => void) {
if (order === 'pre') {
cb(root);
}

for (const child of root.node.children) {
if (!Stage.isStage(child)) { continue; }
visitAssemblies(child, order, cb);
}

if (order === 'post') {
cb(root);
}
}

/**
* Visit the given construct tree in either pre or post order, stopping at Assemblies
*/
Expand Down
Loading