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(cli): support for matching notices with arbitrary module names #19088

Merged
merged 32 commits into from Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
abb4a05
feat(cli): support for notices
Feb 11, 2022
7c873c3
Added cache
Feb 11, 2022
e280527
Refactoring
Feb 11, 2022
236f3a9
refactor to make unit testing feasible
kaizencc Feb 15, 2022
f5c2962
support cdk acknowledge and cdk notices
kaizencc Feb 15, 2022
5175a13
refactor acknowledgements
kaizencc Feb 16, 2022
4c32d0f
refactor acknowledgements and filter for them
kaizencc Feb 16, 2022
e402447
fix cdk notices
kaizencc Feb 16, 2022
4274283
better cdk notices
kaizencc Feb 16, 2022
9b68e6f
specify v2 framework
kaizencc Feb 17, 2022
8a245d2
Added tests for WebsiteNoticeDataSource
Feb 17, 2022
8546c00
Refactored tests
Feb 17, 2022
2642b3a
Getting the framework version for v1
Feb 17, 2022
bcf9c13
Added tests for CachedDataSource
Feb 17, 2022
527eb2b
extract generateMessage function
Feb 17, 2022
0657b9f
add no-notices option
kaizencc Feb 17, 2022
e026e55
Merge branch 'master' into otaviom/notices
kaizencc Feb 17, 2022
977c061
finish merge
kaizencc Feb 17, 2022
303b953
add readme section
kaizencc Feb 17, 2022
f307cc0
Added more tests
Feb 18, 2022
d9ba71f
Ignoring the cache when showing all notices
Feb 21, 2022
17cbce3
Removed paragraph about zero and non-zero return codes.
Feb 21, 2022
a74eadc
Simplified handling of notices/no-notices flag and some other minor i…
Feb 21, 2022
10979a9
Refresh notices and better formatting
Feb 21, 2022
6b89ea1
Merge branch 'master' into otaviom/notices
otaviomacedo Feb 22, 2022
fc4a56e
feat(cli): support for matching notices with arbitrary module names
Feb 22, 2022
1020b99
Simple prefix matching and unified framework version with the arbitra…
Feb 22, 2022
e7afe1d
Added catch to refreshNotices call;
Feb 22, 2022
0bb05ae
Merge branch 'master' into otaviom/arbitrary-components
Feb 22, 2022
f76d5c8
Improved name matching and some refactoring
Feb 22, 2022
4b6ded6
Improved documentation of the match() function.
Feb 22, 2022
6325423
Merge branch 'master' into otaviom/arbitrary-components
mergify[bot] Feb 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/aws-cdk/README.md
Expand Up @@ -507,8 +507,6 @@ $ cdk doctor

## Notices

> This feature exists on CDK CLI version 2.14.0 and up.

CDK Notices are important messages regarding security vulnerabilities, regressions, and usage of unsupported
versions. Relevant notices appear on every command by default. For example,

Expand All @@ -530,6 +528,7 @@ NOTICES

More information at: https://github.com/aws/aws-cdk/issues/16603


17061 Error when building EKS cluster with monocdk import

Overview: When using monocdk/aws-eks to build a stack containing
Expand All @@ -540,6 +539,7 @@ NOTICES

More information at: https://github.com/aws/aws-cdk/issues/17061


If you don’t want to see an notice anymore, use "cdk acknowledge ID". For example, "cdk acknowledge 16603".
```

Expand All @@ -553,8 +553,9 @@ You can suppress warnings in a variety of ways:

```json
{
"notices": false,
"context": {
"notices": false
...
}
}
```
Expand Down Expand Up @@ -587,12 +588,16 @@ NOTICES

16603 Toggling off auto_delete_objects for Bucket empties the bucket

Overview: if a stack is deployed with an S3 bucket with auto_delete_objects=True, and then re-deployed with auto_delete_objects=False, all the objects in the bucket will be deleted.
Overview: if a stack is deployed with an S3 bucket with
auto_delete_objects=True, and then re-deployed with
auto_delete_objects=False, all the objects in the bucket
will be deleted.

Affected versions: framework: <=2.15.0 >=2.10.0

More information at: https://github.com/aws/aws-cdk/issues/16603


If you don’t want to see a notice anymore, use "cdk acknowledge <id>". For example, "cdk acknowledge 16603".
```

Expand Down
116 changes: 100 additions & 16 deletions packages/aws-cdk/lib/notices.ts
Expand Up @@ -3,6 +3,7 @@ import * as path from 'path';
import * as fs from 'fs-extra';
import * as semver from 'semver';
import { debug, print } from './logging';
import { flatMap } from './util';
import { cdkCacheDir } from './util/directories';
import { versionNumber } from './version';

Expand Down Expand Up @@ -75,8 +76,8 @@ export interface FilterNoticeOptions {
export function filterNotices(data: Notice[], options: FilterNoticeOptions): Notice[] {
const filter = new NoticeFilter({
cliVersion: options.cliVersion ?? versionNumber(),
frameworkVersion: options.frameworkVersion ?? frameworkVersion(options.outdir ?? 'cdk.out'),
acknowledgedIssueNumbers: options.acknowledgedIssueNumbers ?? new Set(),
tree: loadTree(options.outdir ?? 'cdk.out').tree,
});
return data.filter(notice => filter.apply(notice));
}
Expand Down Expand Up @@ -188,8 +189,8 @@ export class CachedDataSource implements NoticeDataSource {

export interface NoticeFilterProps {
cliVersion: string,
frameworkVersion: string | undefined,
acknowledgedIssueNumbers: Set<number>,
tree: ConstructTreeNode,
}

export class NoticeFilter {
Expand All @@ -206,8 +207,9 @@ export class NoticeFilter {
if (this.acknowledgedIssueNumbers.has(notice.issueNumber)) {
return false;
}

return this.applyVersion(notice, 'cli', this.props.cliVersion) ||
this.applyVersion(notice, 'framework', this.props.frameworkVersion);
match(resolveAliases(notice.components), this.props.tree);
}

/**
Expand All @@ -222,6 +224,32 @@ export class NoticeFilter {
}
}

/**
* Some component names are aliases to actual component names. For example "framework"
* is an alias for either the core library (v1) or the whole CDK library (v2).
*
* This function converts all aliases to their actual counterpart names, to be used to
* match against the construct tree.
*
* @param components a list of components. Components whose name is an alias will be
* transformed and all others will be left intact.
*/
function resolveAliases(components: Component[]): Component[] {
return flatMap(components, component => {
if (component.name === 'framework') {
return [{
name: '@aws-cdk/core.',
version: component.version,
}, {
name: 'aws-cdk-lib.',
version: component.version,
}];
} else {
return [component];
}
});
}

function formatNotice(notice: Notice): string {
const componentsValue = notice.components.map(c => `${c.name}: ${c.version}`).join(', ');
return [
Expand All @@ -244,21 +272,77 @@ function formatOverview(text: string) {
return '\t' + heading + content;
}

function frameworkVersion(outdir: string): string | undefined {
const tree = loadTree().tree;
/**
* Whether any component in the tree matches any component in the query.
* A match happens when:
*
* 1. The version of the node matches the version in the query, interpreted
* as a semver range.
*
* 2. The name in the query is a prefix of the node name when the query ends in '.',
* or the two names are exactly the same, otherwise.
*/
function match(query: Component[], tree: ConstructTreeNode): boolean {
return some(tree, node => {
return query.some(component =>
compareNames(component.name, node.constructInfo?.fqn) &&
compareVersions(component.version, node.constructInfo?.version));
});

if (tree?.constructInfo?.fqn.startsWith('aws-cdk-lib')
|| tree?.constructInfo?.fqn.startsWith('@aws-cdk/core')) {
return tree.constructInfo.version;
function compareNames(pattern: string, target: string | undefined): boolean {
if (target == null) { return false; }
return pattern.endsWith('.') ? target.startsWith(pattern) : pattern === target;
}
return undefined;

function loadTree() {
try {
return fs.readJSONSync(path.join(outdir, 'tree.json'));
} catch (e) {
debug(`Failed to get tree.json file: ${e}`);
return {};
function compareVersions(pattern: string, target: string | undefined): boolean {
return semver.satisfies(target ?? '', pattern);
}
}

function loadTree(outdir: string) {
try {
return fs.readJSONSync(path.join(outdir, 'tree.json'));
} catch (e) {
debug(`Failed to get tree.json file: ${e}`);
return {};
}
}

/**
* Source information on a construct (class fqn and version)
*/
interface ConstructInfo {
readonly fqn: string;
readonly version: string;
}

/**
* A node in the construct tree.
* @internal
*/
interface ConstructTreeNode {
readonly id: string;
readonly path: string;
readonly children?: { [key: string]: ConstructTreeNode };
readonly attributes?: { [key: string]: any };

/**
* Information on the construct class that led to this node, if available
*/
readonly constructInfo?: ConstructInfo;
}

function some(node: ConstructTreeNode, predicate: (n: ConstructTreeNode) => boolean): boolean {
return node != null && (predicate(node) || findInChildren());

function findInChildren(): boolean {
if (node.children == null) { return false; }

for (const name in node.children) {
if (some(node.children[name], predicate)) {
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,110 @@
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"Tree": {
"id": "Tree",
"path": "Tree",
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.0.66"
}
},
"SimulationStack": {
"id": "SimulationStack",
"path": "SimulationStack",
"children": {
"HttpApi": {
"id": "HttpApi",
"path": "SimulationStack/HttpApi",
"children": {
"Resource": {
"id": "Resource",
"path": "SimulationStack/HttpApi/Resource",
"attributes": {
"aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Api",
"aws:cdk:cloudformation:props": {
"name": "HttpApi",
"protocolType": "HTTP"
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.aws_apigatewayv2.CfnApi",
"version": "2.8.0"
}
},
"DefaultStage": {
"id": "DefaultStage",
"path": "SimulationStack/HttpApi/DefaultStage",
"children": {
"Resource": {
"id": "Resource",
"path": "SimulationStack/HttpApi/DefaultStage/Resource",
"attributes": {
"aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Stage",
"aws:cdk:cloudformation:props": {
"apiId": {
"Ref": "HttpApiF5A9A8A7"
},
"stageName": "$default",
"autoDeploy": true
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.aws_apigatewayv2.CfnStage",
"version": "2.8.0"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/aws-apigatewayv2-alpha.HttpStage",
"version": "2.13.0-alpha.0"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/aws-apigatewayv2-alpha.HttpApi",
"version": "2.13.0-alpha.0"
}
},
"CDKMetadata": {
"id": "CDKMetadata",
"path": "SimulationStack/CDKMetadata",
"children": {
"Default": {
"id": "Default",
"path": "SimulationStack/CDKMetadata/Default",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnResource",
"version": "2.8.0"
}
},
"Condition": {
"id": "Condition",
"path": "SimulationStack/CDKMetadata/Condition",
"constructInfo": {
"fqn": "aws-cdk-lib.CfnCondition",
"version": "2.8.0"
}
}
},
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.0.66"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.Stack",
"version": "2.8.0"
}
}
},
"constructInfo": {
"fqn": "aws-cdk-lib.App",
"version": "2.8.0"
}
}
}