@aligent/cdk-aspects 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/index.d.ts +3 -0
- package/index.js +4 -1
- package/lib/defaults/dynamodb.d.ts +53 -0
- package/lib/defaults/dynamodb.js +114 -0
- package/lib/defaults/s3-bucket.d.ts +51 -0
- package/lib/defaults/s3-bucket.js +85 -0
- package/lib/resource-prefix.d.ts +68 -0
- package/lib/resource-prefix.js +187 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @aligent/cdk-aspects
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1630](https://github.com/aligent/cdk-constructs/pull/1630) [`91347d6`](https://github.com/aligent/cdk-constructs/commit/91347d62e048c23ae85f657e97c1dd357c1b2a70) Thanks [@kai-nguyen-aligent](https://github.com/kai-nguyen-aligent)! - Add default aspects for S3 and DynamoDB resources to enforce secure configuration defaults
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#1627](https://github.com/aligent/cdk-constructs/pull/1627) [`a53bad0`](https://github.com/aligent/cdk-constructs/commit/a53bad0c3b8156ec3b57476b4007cdb854785ba6) Thanks [@kai-nguyen-aligent](https://github.com/kai-nguyen-aligent)! - Add new ResourcePrefixAspect
|
|
14
|
+
|
|
3
15
|
## 0.3.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
export * from "./lib/defaults/dynamodb";
|
|
1
2
|
export * from "./lib/defaults/log-group";
|
|
2
3
|
export * from "./lib/defaults/nodejs-function";
|
|
4
|
+
export * from "./lib/defaults/s3-bucket";
|
|
3
5
|
export * from "./lib/defaults/step-functions";
|
|
4
6
|
export * from "./lib/lambda-sfn-versioning";
|
|
5
7
|
export * from "./lib/microservice-checks";
|
|
8
|
+
export * from "./lib/resource-prefix";
|
package/index.js
CHANGED
|
@@ -14,9 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./lib/defaults/dynamodb"), exports);
|
|
17
18
|
__exportStar(require("./lib/defaults/log-group"), exports);
|
|
18
19
|
__exportStar(require("./lib/defaults/nodejs-function"), exports);
|
|
20
|
+
__exportStar(require("./lib/defaults/s3-bucket"), exports);
|
|
19
21
|
__exportStar(require("./lib/defaults/step-functions"), exports);
|
|
20
22
|
__exportStar(require("./lib/lambda-sfn-versioning"), exports);
|
|
21
23
|
__exportStar(require("./lib/microservice-checks"), exports);
|
|
22
|
-
|
|
24
|
+
__exportStar(require("./lib/resource-prefix"), exports);
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsMERBQXdDO0FBQ3hDLDJEQUF5QztBQUN6QyxpRUFBK0M7QUFDL0MsMkRBQXlDO0FBQ3pDLGdFQUE4QztBQUM5Qyw4REFBNEM7QUFDNUMsNERBQTBDO0FBQzFDLHdEQUFzQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCAqIGZyb20gXCIuL2xpYi9kZWZhdWx0cy9keW5hbW9kYlwiO1xuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RlZmF1bHRzL2xvZy1ncm91cFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RlZmF1bHRzL25vZGVqcy1mdW5jdGlvblwiO1xuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RlZmF1bHRzL3MzLWJ1Y2tldFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vbGliL2RlZmF1bHRzL3N0ZXAtZnVuY3Rpb25zXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9saWIvbGFtYmRhLXNmbi12ZXJzaW9uaW5nXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9saWIvbWljcm9zZXJ2aWNlLWNoZWNrc1wiO1xuZXhwb3J0ICogZnJvbSBcIi4vbGliL3Jlc291cmNlLXByZWZpeFwiO1xuIl19
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type IAspect } from "aws-cdk-lib";
|
|
2
|
+
import { IConstruct } from "constructs";
|
|
3
|
+
interface Config {
|
|
4
|
+
duration: "SHORT" | "MEDIUM" | "LONG";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Aspect that automatically applies configuration-aware defaults to DynamoDB Tables
|
|
8
|
+
*
|
|
9
|
+
* Visits all constructs in the scope and automatically applies configuration-specific
|
|
10
|
+
* removal policies and point-in-time recovery settings to DynamoDB tables.
|
|
11
|
+
* Different configurations balance between cost optimization and data retention needs.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Apply configuration-specific defaults to all tables
|
|
16
|
+
* Aspects.of(app).add(new DynamoDbDefaultsAspect({ duration: 'SHORT' }));
|
|
17
|
+
*
|
|
18
|
+
* // Tables automatically inherit configuration defaults
|
|
19
|
+
* new Table(stack, 'MyTable', {
|
|
20
|
+
* // point-in-time recovery and removal policy applied automatically
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html
|
|
25
|
+
*/
|
|
26
|
+
export declare class DynamoDbDefaultsAspect implements IAspect {
|
|
27
|
+
private readonly defaultProps;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new DynamoDbDefaultsAspect
|
|
30
|
+
*
|
|
31
|
+
* @param config - Configuration identifier used to select appropriate defaults.
|
|
32
|
+
*/
|
|
33
|
+
constructor(config: Config);
|
|
34
|
+
/**
|
|
35
|
+
* Get duration-specific DynamoDB table properties
|
|
36
|
+
*
|
|
37
|
+
* @param duration - The duration to get the table properties for
|
|
38
|
+
* @returns The table properties for the duration
|
|
39
|
+
*/
|
|
40
|
+
private retentionProperties;
|
|
41
|
+
private isProvisionedThroughputConfigured;
|
|
42
|
+
private isOnDemandThroughputConfigured;
|
|
43
|
+
/**
|
|
44
|
+
* Visits a construct and applies configuration-appropriate defaults
|
|
45
|
+
*
|
|
46
|
+
* Applies configuration-specific billing mode, throughput, point-in-time recovery,
|
|
47
|
+
* and removal policies to tables that don't already have these properties explicitly set.
|
|
48
|
+
*
|
|
49
|
+
* @param node - The construct to potentially modify
|
|
50
|
+
*/
|
|
51
|
+
visit(node: IConstruct): void;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DynamoDbDefaultsAspect = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
const aws_dynamodb_1 = require("aws-cdk-lib/aws-dynamodb");
|
|
6
|
+
/**
|
|
7
|
+
* Aspect that automatically applies configuration-aware defaults to DynamoDB Tables
|
|
8
|
+
*
|
|
9
|
+
* Visits all constructs in the scope and automatically applies configuration-specific
|
|
10
|
+
* removal policies and point-in-time recovery settings to DynamoDB tables.
|
|
11
|
+
* Different configurations balance between cost optimization and data retention needs.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Apply configuration-specific defaults to all tables
|
|
16
|
+
* Aspects.of(app).add(new DynamoDbDefaultsAspect({ duration: 'SHORT' }));
|
|
17
|
+
*
|
|
18
|
+
* // Tables automatically inherit configuration defaults
|
|
19
|
+
* new Table(stack, 'MyTable', {
|
|
20
|
+
* // point-in-time recovery and removal policy applied automatically
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html
|
|
25
|
+
*/
|
|
26
|
+
class DynamoDbDefaultsAspect {
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new DynamoDbDefaultsAspect
|
|
29
|
+
*
|
|
30
|
+
* @param config - Configuration identifier used to select appropriate defaults.
|
|
31
|
+
*/
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.defaultProps = this.retentionProperties(config.duration);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get duration-specific DynamoDB table properties
|
|
37
|
+
*
|
|
38
|
+
* @param duration - The duration to get the table properties for
|
|
39
|
+
* @returns The table properties for the duration
|
|
40
|
+
*/
|
|
41
|
+
retentionProperties(duration) {
|
|
42
|
+
switch (duration) {
|
|
43
|
+
case "SHORT":
|
|
44
|
+
return {
|
|
45
|
+
billingMode: aws_dynamodb_1.BillingMode.PROVISIONED,
|
|
46
|
+
readCapacity: 1,
|
|
47
|
+
writeCapacity: 1,
|
|
48
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
49
|
+
};
|
|
50
|
+
case "MEDIUM":
|
|
51
|
+
return {
|
|
52
|
+
billingMode: aws_dynamodb_1.BillingMode.PAY_PER_REQUEST,
|
|
53
|
+
maxReadRequestUnits: 100,
|
|
54
|
+
maxWriteRequestUnits: 100,
|
|
55
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
56
|
+
};
|
|
57
|
+
default:
|
|
58
|
+
return {
|
|
59
|
+
billingMode: aws_dynamodb_1.BillingMode.PAY_PER_REQUEST,
|
|
60
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.RETAIN,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
isProvisionedThroughputConfigured() {
|
|
65
|
+
const { billingMode, readCapacity, writeCapacity } = this.defaultProps;
|
|
66
|
+
return (billingMode === aws_dynamodb_1.BillingMode.PROVISIONED &&
|
|
67
|
+
readCapacity !== undefined &&
|
|
68
|
+
writeCapacity !== undefined);
|
|
69
|
+
}
|
|
70
|
+
isOnDemandThroughputConfigured() {
|
|
71
|
+
const { billingMode, maxReadRequestUnits, maxWriteRequestUnits } = this.defaultProps;
|
|
72
|
+
return (billingMode === aws_dynamodb_1.BillingMode.PAY_PER_REQUEST &&
|
|
73
|
+
maxReadRequestUnits !== undefined &&
|
|
74
|
+
maxWriteRequestUnits !== undefined);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Visits a construct and applies configuration-appropriate defaults
|
|
78
|
+
*
|
|
79
|
+
* Applies configuration-specific billing mode, throughput, point-in-time recovery,
|
|
80
|
+
* and removal policies to tables that don't already have these properties explicitly set.
|
|
81
|
+
*
|
|
82
|
+
* @param node - The construct to potentially modify
|
|
83
|
+
*/
|
|
84
|
+
visit(node) {
|
|
85
|
+
if (node instanceof aws_dynamodb_1.Table) {
|
|
86
|
+
const { billingMode, readCapacity, writeCapacity, maxReadRequestUnits, maxWriteRequestUnits, removalPolicy, } = this.defaultProps;
|
|
87
|
+
if (removalPolicy) {
|
|
88
|
+
node.applyRemovalPolicy(removalPolicy);
|
|
89
|
+
}
|
|
90
|
+
const cfnTable = node.node.defaultChild;
|
|
91
|
+
if (!cfnTable)
|
|
92
|
+
return;
|
|
93
|
+
if (cfnTable.billingMode === undefined) {
|
|
94
|
+
cfnTable.billingMode = billingMode;
|
|
95
|
+
}
|
|
96
|
+
if (cfnTable.provisionedThroughput === undefined &&
|
|
97
|
+
this.isProvisionedThroughputConfigured()) {
|
|
98
|
+
cfnTable.provisionedThroughput = {
|
|
99
|
+
readCapacityUnits: readCapacity,
|
|
100
|
+
writeCapacityUnits: writeCapacity,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (cfnTable.onDemandThroughput === undefined &&
|
|
104
|
+
this.isOnDemandThroughputConfigured()) {
|
|
105
|
+
cfnTable.onDemandThroughput = {
|
|
106
|
+
maxReadRequestUnits,
|
|
107
|
+
maxWriteRequestUnits,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.DynamoDbDefaultsAspect = DynamoDbDefaultsAspect;
|
|
114
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHluYW1vZGIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJkeW5hbW9kYi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2Q0FBMEQ7QUFDMUQsMkRBS2tDO0FBT2xDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0gsTUFBYSxzQkFBc0I7SUFHakM7Ozs7T0FJRztJQUNILFlBQVksTUFBYztRQUN4QixJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ssbUJBQW1CLENBQ3pCLFFBQXFDO1FBRXJDLFFBQVEsUUFBUSxFQUFFLENBQUM7WUFDakIsS0FBSyxPQUFPO2dCQUNWLE9BQU87b0JBQ0wsV0FBVyxFQUFFLDBCQUFXLENBQUMsV0FBVztvQkFDcEMsWUFBWSxFQUFFLENBQUM7b0JBQ2YsYUFBYSxFQUFFLENBQUM7b0JBQ2hCLGFBQWEsRUFBRSwyQkFBYSxDQUFDLE9BQU87aUJBQ3JDLENBQUM7WUFDSixLQUFLLFFBQVE7Z0JBQ1gsT0FBTztvQkFDTCxXQUFXLEVBQUUsMEJBQVcsQ0FBQyxlQUFlO29CQUN4QyxtQkFBbUIsRUFBRSxHQUFHO29CQUN4QixvQkFBb0IsRUFBRSxHQUFHO29CQUN6QixhQUFhLEVBQUUsMkJBQWEsQ0FBQyxPQUFPO2lCQUNyQyxDQUFDO1lBQ0o7Z0JBQ0UsT0FBTztvQkFDTCxXQUFXLEVBQUUsMEJBQVcsQ0FBQyxlQUFlO29CQUN4QyxhQUFhLEVBQUUsMkJBQWEsQ0FBQyxNQUFNO2lCQUNwQyxDQUFDO1FBQ04sQ0FBQztJQUNILENBQUM7SUFFTyxpQ0FBaUM7UUFDdkMsTUFBTSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztRQUV2RSxPQUFPLENBQ0wsV0FBVyxLQUFLLDBCQUFXLENBQUMsV0FBVztZQUN2QyxZQUFZLEtBQUssU0FBUztZQUMxQixhQUFhLEtBQUssU0FBUyxDQUM1QixDQUFDO0lBQ0osQ0FBQztJQUVPLDhCQUE4QjtRQUNwQyxNQUFNLEVBQUUsV0FBVyxFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLEdBQzlELElBQUksQ0FBQyxZQUFZLENBQUM7UUFFcEIsT0FBTyxDQUNMLFdBQVcsS0FBSywwQkFBVyxDQUFDLGVBQWU7WUFDM0MsbUJBQW1CLEtBQUssU0FBUztZQUNqQyxvQkFBb0IsS0FBSyxTQUFTLENBQ25DLENBQUM7SUFDSixDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxJQUFnQjtRQUNwQixJQUFJLElBQUksWUFBWSxvQkFBSyxFQUFFLENBQUM7WUFDMUIsTUFBTSxFQUNKLFdBQVcsRUFDWCxZQUFZLEVBQ1osYUFBYSxFQUNiLG1CQUFtQixFQUNuQixvQkFBb0IsRUFDcEIsYUFBYSxHQUNkLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztZQUV0QixJQUFJLGFBQWEsRUFBRSxDQUFDO2dCQUNsQixJQUFJLENBQUMsa0JBQWtCLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDekMsQ0FBQztZQUVELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBd0IsQ0FBQztZQUNwRCxJQUFJLENBQUMsUUFBUTtnQkFBRSxPQUFPO1lBRXRCLElBQUksUUFBUSxDQUFDLFdBQVcsS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDdkMsUUFBUSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7WUFDckMsQ0FBQztZQUVELElBQ0UsUUFBUSxDQUFDLHFCQUFxQixLQUFLLFNBQVM7Z0JBQzVDLElBQUksQ0FBQyxpQ0FBaUMsRUFBRSxFQUN4QyxDQUFDO2dCQUNELFFBQVEsQ0FBQyxxQkFBcUIsR0FBRztvQkFDL0IsaUJBQWlCLEVBQUUsWUFBYTtvQkFDaEMsa0JBQWtCLEVBQUUsYUFBYztpQkFDbkMsQ0FBQztZQUNKLENBQUM7WUFFRCxJQUNFLFFBQVEsQ0FBQyxrQkFBa0IsS0FBSyxTQUFTO2dCQUN6QyxJQUFJLENBQUMsOEJBQThCLEVBQUUsRUFDckMsQ0FBQztnQkFDRCxRQUFRLENBQUMsa0JBQWtCLEdBQUc7b0JBQzVCLG1CQUFtQjtvQkFDbkIsb0JBQW9CO2lCQUNyQixDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0NBQ0Y7QUFwSEQsd0RBb0hDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUmVtb3ZhbFBvbGljeSwgdHlwZSBJQXNwZWN0IH0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQge1xuICBCaWxsaW5nTW9kZSxcbiAgQ2ZuVGFibGUsXG4gIFRhYmxlLFxuICB0eXBlIFRhYmxlUHJvcHMsXG59IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtZHluYW1vZGJcIjtcbmltcG9ydCB7IElDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuXG5pbnRlcmZhY2UgQ29uZmlnIHtcbiAgZHVyYXRpb246IFwiU0hPUlRcIiB8IFwiTUVESVVNXCIgfCBcIkxPTkdcIjtcbn1cblxuLyoqXG4gKiBBc3BlY3QgdGhhdCBhdXRvbWF0aWNhbGx5IGFwcGxpZXMgY29uZmlndXJhdGlvbi1hd2FyZSBkZWZhdWx0cyB0byBEeW5hbW9EQiBUYWJsZXNcbiAqXG4gKiBWaXNpdHMgYWxsIGNvbnN0cnVjdHMgaW4gdGhlIHNjb3BlIGFuZCBhdXRvbWF0aWNhbGx5IGFwcGxpZXMgY29uZmlndXJhdGlvbi1zcGVjaWZpY1xuICogcmVtb3ZhbCBwb2xpY2llcyBhbmQgcG9pbnQtaW4tdGltZSByZWNvdmVyeSBzZXR0aW5ncyB0byBEeW5hbW9EQiB0YWJsZXMuXG4gKiBEaWZmZXJlbnQgY29uZmlndXJhdGlvbnMgYmFsYW5jZSBiZXR3ZWVuIGNvc3Qgb3B0aW1pemF0aW9uIGFuZCBkYXRhIHJldGVudGlvbiBuZWVkcy5cbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQXBwbHkgY29uZmlndXJhdGlvbi1zcGVjaWZpYyBkZWZhdWx0cyB0byBhbGwgdGFibGVzXG4gKiBBc3BlY3RzLm9mKGFwcCkuYWRkKG5ldyBEeW5hbW9EYkRlZmF1bHRzQXNwZWN0KHsgZHVyYXRpb246ICdTSE9SVCcgfSkpO1xuICpcbiAqIC8vIFRhYmxlcyBhdXRvbWF0aWNhbGx5IGluaGVyaXQgY29uZmlndXJhdGlvbiBkZWZhdWx0c1xuICogbmV3IFRhYmxlKHN0YWNrLCAnTXlUYWJsZScsIHtcbiAqICAgLy8gcG9pbnQtaW4tdGltZSByZWNvdmVyeSBhbmQgcmVtb3ZhbCBwb2xpY3kgYXBwbGllZCBhdXRvbWF0aWNhbGx5XG4gKiB9KTtcbiAqIGBgYFxuICpcbiAqIEBzZWUgaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL2Nkay9hcGkvdjIvZG9jcy9hd3MtY2RrLWxpYi5hd3NfZHluYW1vZGIuVGFibGUuaHRtbFxuICovXG5leHBvcnQgY2xhc3MgRHluYW1vRGJEZWZhdWx0c0FzcGVjdCBpbXBsZW1lbnRzIElBc3BlY3Qge1xuICBwcml2YXRlIHJlYWRvbmx5IGRlZmF1bHRQcm9wczogVGFibGVQcm9wcztcblxuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBEeW5hbW9EYkRlZmF1bHRzQXNwZWN0XG4gICAqXG4gICAqIEBwYXJhbSBjb25maWcgLSBDb25maWd1cmF0aW9uIGlkZW50aWZpZXIgdXNlZCB0byBzZWxlY3QgYXBwcm9wcmlhdGUgZGVmYXVsdHMuXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihjb25maWc6IENvbmZpZykge1xuICAgIHRoaXMuZGVmYXVsdFByb3BzID0gdGhpcy5yZXRlbnRpb25Qcm9wZXJ0aWVzKGNvbmZpZy5kdXJhdGlvbik7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGR1cmF0aW9uLXNwZWNpZmljIER5bmFtb0RCIHRhYmxlIHByb3BlcnRpZXNcbiAgICpcbiAgICogQHBhcmFtIGR1cmF0aW9uIC0gVGhlIGR1cmF0aW9uIHRvIGdldCB0aGUgdGFibGUgcHJvcGVydGllcyBmb3JcbiAgICogQHJldHVybnMgVGhlIHRhYmxlIHByb3BlcnRpZXMgZm9yIHRoZSBkdXJhdGlvblxuICAgKi9cbiAgcHJpdmF0ZSByZXRlbnRpb25Qcm9wZXJ0aWVzKFxuICAgIGR1cmF0aW9uOiBcIlNIT1JUXCIgfCBcIk1FRElVTVwiIHwgXCJMT05HXCJcbiAgKTogVGFibGVQcm9wcyB7XG4gICAgc3dpdGNoIChkdXJhdGlvbikge1xuICAgICAgY2FzZSBcIlNIT1JUXCI6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgYmlsbGluZ01vZGU6IEJpbGxpbmdNb2RlLlBST1ZJU0lPTkVELFxuICAgICAgICAgIHJlYWRDYXBhY2l0eTogMSxcbiAgICAgICAgICB3cml0ZUNhcGFjaXR5OiAxLFxuICAgICAgICAgIHJlbW92YWxQb2xpY3k6IFJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgICAgfTtcbiAgICAgIGNhc2UgXCJNRURJVU1cIjpcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBiaWxsaW5nTW9kZTogQmlsbGluZ01vZGUuUEFZX1BFUl9SRVFVRVNULFxuICAgICAgICAgIG1heFJlYWRSZXF1ZXN0VW5pdHM6IDEwMCxcbiAgICAgICAgICBtYXhXcml0ZVJlcXVlc3RVbml0czogMTAwLFxuICAgICAgICAgIHJlbW92YWxQb2xpY3k6IFJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgICAgfTtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgYmlsbGluZ01vZGU6IEJpbGxpbmdNb2RlLlBBWV9QRVJfUkVRVUVTVCxcbiAgICAgICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LlJFVEFJTixcbiAgICAgICAgfTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGlzUHJvdmlzaW9uZWRUaHJvdWdocHV0Q29uZmlndXJlZCgpIHtcbiAgICBjb25zdCB7IGJpbGxpbmdNb2RlLCByZWFkQ2FwYWNpdHksIHdyaXRlQ2FwYWNpdHkgfSA9IHRoaXMuZGVmYXVsdFByb3BzO1xuXG4gICAgcmV0dXJuIChcbiAgICAgIGJpbGxpbmdNb2RlID09PSBCaWxsaW5nTW9kZS5QUk9WSVNJT05FRCAmJlxuICAgICAgcmVhZENhcGFjaXR5ICE9PSB1bmRlZmluZWQgJiZcbiAgICAgIHdyaXRlQ2FwYWNpdHkgIT09IHVuZGVmaW5lZFxuICAgICk7XG4gIH1cblxuICBwcml2YXRlIGlzT25EZW1hbmRUaHJvdWdocHV0Q29uZmlndXJlZCgpIHtcbiAgICBjb25zdCB7IGJpbGxpbmdNb2RlLCBtYXhSZWFkUmVxdWVzdFVuaXRzLCBtYXhXcml0ZVJlcXVlc3RVbml0cyB9ID1cbiAgICAgIHRoaXMuZGVmYXVsdFByb3BzO1xuXG4gICAgcmV0dXJuIChcbiAgICAgIGJpbGxpbmdNb2RlID09PSBCaWxsaW5nTW9kZS5QQVlfUEVSX1JFUVVFU1QgJiZcbiAgICAgIG1heFJlYWRSZXF1ZXN0VW5pdHMgIT09IHVuZGVmaW5lZCAmJlxuICAgICAgbWF4V3JpdGVSZXF1ZXN0VW5pdHMgIT09IHVuZGVmaW5lZFxuICAgICk7XG4gIH1cblxuICAvKipcbiAgICogVmlzaXRzIGEgY29uc3RydWN0IGFuZCBhcHBsaWVzIGNvbmZpZ3VyYXRpb24tYXBwcm9wcmlhdGUgZGVmYXVsdHNcbiAgICpcbiAgICogQXBwbGllcyBjb25maWd1cmF0aW9uLXNwZWNpZmljIGJpbGxpbmcgbW9kZSwgdGhyb3VnaHB1dCwgcG9pbnQtaW4tdGltZSByZWNvdmVyeSxcbiAgICogYW5kIHJlbW92YWwgcG9saWNpZXMgdG8gdGFibGVzIHRoYXQgZG9uJ3QgYWxyZWFkeSBoYXZlIHRoZXNlIHByb3BlcnRpZXMgZXhwbGljaXRseSBzZXQuXG4gICAqXG4gICAqIEBwYXJhbSBub2RlIC0gVGhlIGNvbnN0cnVjdCB0byBwb3RlbnRpYWxseSBtb2RpZnlcbiAgICovXG4gIHZpc2l0KG5vZGU6IElDb25zdHJ1Y3QpOiB2b2lkIHtcbiAgICBpZiAobm9kZSBpbnN0YW5jZW9mIFRhYmxlKSB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGJpbGxpbmdNb2RlLFxuICAgICAgICByZWFkQ2FwYWNpdHksXG4gICAgICAgIHdyaXRlQ2FwYWNpdHksXG4gICAgICAgIG1heFJlYWRSZXF1ZXN0VW5pdHMsXG4gICAgICAgIG1heFdyaXRlUmVxdWVzdFVuaXRzLFxuICAgICAgICByZW1vdmFsUG9saWN5LFxuICAgICAgfSA9IHRoaXMuZGVmYXVsdFByb3BzO1xuXG4gICAgICBpZiAocmVtb3ZhbFBvbGljeSkge1xuICAgICAgICBub2RlLmFwcGx5UmVtb3ZhbFBvbGljeShyZW1vdmFsUG9saWN5KTtcbiAgICAgIH1cblxuICAgICAgY29uc3QgY2ZuVGFibGUgPSBub2RlLm5vZGUuZGVmYXVsdENoaWxkIGFzIENmblRhYmxlO1xuICAgICAgaWYgKCFjZm5UYWJsZSkgcmV0dXJuO1xuXG4gICAgICBpZiAoY2ZuVGFibGUuYmlsbGluZ01vZGUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjZm5UYWJsZS5iaWxsaW5nTW9kZSA9IGJpbGxpbmdNb2RlO1xuICAgICAgfVxuXG4gICAgICBpZiAoXG4gICAgICAgIGNmblRhYmxlLnByb3Zpc2lvbmVkVGhyb3VnaHB1dCA9PT0gdW5kZWZpbmVkICYmXG4gICAgICAgIHRoaXMuaXNQcm92aXNpb25lZFRocm91Z2hwdXRDb25maWd1cmVkKClcbiAgICAgICkge1xuICAgICAgICBjZm5UYWJsZS5wcm92aXNpb25lZFRocm91Z2hwdXQgPSB7XG4gICAgICAgICAgcmVhZENhcGFjaXR5VW5pdHM6IHJlYWRDYXBhY2l0eSEsXG4gICAgICAgICAgd3JpdGVDYXBhY2l0eVVuaXRzOiB3cml0ZUNhcGFjaXR5ISxcbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgaWYgKFxuICAgICAgICBjZm5UYWJsZS5vbkRlbWFuZFRocm91Z2hwdXQgPT09IHVuZGVmaW5lZCAmJlxuICAgICAgICB0aGlzLmlzT25EZW1hbmRUaHJvdWdocHV0Q29uZmlndXJlZCgpXG4gICAgICApIHtcbiAgICAgICAgY2ZuVGFibGUub25EZW1hbmRUaHJvdWdocHV0ID0ge1xuICAgICAgICAgIG1heFJlYWRSZXF1ZXN0VW5pdHMsXG4gICAgICAgICAgbWF4V3JpdGVSZXF1ZXN0VW5pdHMsXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type IAspect } from "aws-cdk-lib";
|
|
2
|
+
import { IConstruct } from "constructs";
|
|
3
|
+
interface Config {
|
|
4
|
+
duration: "SHORT" | "MEDIUM" | "LONG";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Aspect that automatically applies configuration-aware defaults to S3 Buckets
|
|
8
|
+
*
|
|
9
|
+
* Visits all constructs in the scope and automatically applies configuration-specific
|
|
10
|
+
* lifecycle and removal policies to S3 buckets. Different configurations balance
|
|
11
|
+
* between cost optimization and data retention needs.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Apply configuration-specific defaults to all buckets
|
|
16
|
+
* Aspects.of(app).add(new S3DefaultsAspect({ autoDelete: true, duration: 'SHORT' }));
|
|
17
|
+
*
|
|
18
|
+
* // Buckets automatically inherit configuration defaults
|
|
19
|
+
* new Bucket(stack, 'MyBucket', {
|
|
20
|
+
* // lifecycle and removal policy applied automatically
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html
|
|
25
|
+
*/
|
|
26
|
+
export declare class S3DefaultsAspect implements IAspect {
|
|
27
|
+
private readonly defaultProps;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new S3DefaultsAspect
|
|
30
|
+
*
|
|
31
|
+
* @param config - Configuration identifier used to select appropriate defaults.
|
|
32
|
+
*/
|
|
33
|
+
constructor(config: Config);
|
|
34
|
+
/**
|
|
35
|
+
* Get duration-specific object expiration
|
|
36
|
+
*
|
|
37
|
+
* @param duration - The duration to get the expiration for
|
|
38
|
+
* @returns The expiration Duration, or undefined for LONG retention
|
|
39
|
+
*/
|
|
40
|
+
private retentionProperties;
|
|
41
|
+
/**
|
|
42
|
+
* Visits a construct and applies configuration-appropriate defaults
|
|
43
|
+
*
|
|
44
|
+
* Applies a removal policy and lifecycle rules to buckets that don't
|
|
45
|
+
* already have a lifecycle configuration explicitly set.
|
|
46
|
+
*
|
|
47
|
+
* @param node - The construct to potentially modify
|
|
48
|
+
*/
|
|
49
|
+
visit(node: IConstruct): void;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.S3DefaultsAspect = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
const aws_s3_1 = require("aws-cdk-lib/aws-s3");
|
|
6
|
+
/**
|
|
7
|
+
* Aspect that automatically applies configuration-aware defaults to S3 Buckets
|
|
8
|
+
*
|
|
9
|
+
* Visits all constructs in the scope and automatically applies configuration-specific
|
|
10
|
+
* lifecycle and removal policies to S3 buckets. Different configurations balance
|
|
11
|
+
* between cost optimization and data retention needs.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Apply configuration-specific defaults to all buckets
|
|
16
|
+
* Aspects.of(app).add(new S3DefaultsAspect({ autoDelete: true, duration: 'SHORT' }));
|
|
17
|
+
*
|
|
18
|
+
* // Buckets automatically inherit configuration defaults
|
|
19
|
+
* new Bucket(stack, 'MyBucket', {
|
|
20
|
+
* // lifecycle and removal policy applied automatically
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @see https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html
|
|
25
|
+
*/
|
|
26
|
+
class S3DefaultsAspect {
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new S3DefaultsAspect
|
|
29
|
+
*
|
|
30
|
+
* @param config - Configuration identifier used to select appropriate defaults.
|
|
31
|
+
*/
|
|
32
|
+
constructor(config) {
|
|
33
|
+
const props = this.retentionProperties(config.duration);
|
|
34
|
+
this.defaultProps = { ...props };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get duration-specific object expiration
|
|
38
|
+
*
|
|
39
|
+
* @param duration - The duration to get the expiration for
|
|
40
|
+
* @returns The expiration Duration, or undefined for LONG retention
|
|
41
|
+
*/
|
|
42
|
+
retentionProperties(duration) {
|
|
43
|
+
switch (duration) {
|
|
44
|
+
case "SHORT":
|
|
45
|
+
return {
|
|
46
|
+
lifecycleRules: [{ expiration: aws_cdk_lib_1.Duration.days(30) }],
|
|
47
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
48
|
+
};
|
|
49
|
+
case "MEDIUM":
|
|
50
|
+
return {
|
|
51
|
+
lifecycleRules: [{ expiration: aws_cdk_lib_1.Duration.days(90) }],
|
|
52
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY,
|
|
53
|
+
};
|
|
54
|
+
default:
|
|
55
|
+
return {
|
|
56
|
+
lifecycleRules: [],
|
|
57
|
+
removalPolicy: aws_cdk_lib_1.RemovalPolicy.RETAIN,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Visits a construct and applies configuration-appropriate defaults
|
|
63
|
+
*
|
|
64
|
+
* Applies a removal policy and lifecycle rules to buckets that don't
|
|
65
|
+
* already have a lifecycle configuration explicitly set.
|
|
66
|
+
*
|
|
67
|
+
* @param node - The construct to potentially modify
|
|
68
|
+
*/
|
|
69
|
+
visit(node) {
|
|
70
|
+
if (node instanceof aws_s3_1.Bucket) {
|
|
71
|
+
const { lifecycleRules, removalPolicy } = this.defaultProps;
|
|
72
|
+
if (removalPolicy) {
|
|
73
|
+
node.applyRemovalPolicy(removalPolicy);
|
|
74
|
+
}
|
|
75
|
+
if (lifecycleRules === null || lifecycleRules === void 0 ? void 0 : lifecycleRules.length) {
|
|
76
|
+
const cfnBucket = node.node.defaultChild;
|
|
77
|
+
if (cfnBucket && cfnBucket.lifecycleConfiguration === undefined) {
|
|
78
|
+
lifecycleRules.forEach(rule => node.addLifecycleRule(rule));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.S3DefaultsAspect = S3DefaultsAspect;
|
|
85
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiczMtYnVja2V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiczMtYnVja2V0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZDQUFvRTtBQUNwRSwrQ0FBeUU7QUFPekU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSCxNQUFhLGdCQUFnQjtJQUczQjs7OztPQUlHO0lBQ0gsWUFBWSxNQUFjO1FBQ3hCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDeEQsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLEdBQUcsS0FBSyxFQUFFLENBQUM7SUFDbkMsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ssbUJBQW1CLENBQUMsUUFBcUM7UUFDL0QsUUFBUSxRQUFRLEVBQUUsQ0FBQztZQUNqQixLQUFLLE9BQU87Z0JBQ1YsT0FBTztvQkFDTCxjQUFjLEVBQUUsQ0FBQyxFQUFFLFVBQVUsRUFBRSxzQkFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO29CQUNuRCxhQUFhLEVBQUUsMkJBQWEsQ0FBQyxPQUFPO2lCQUNyQyxDQUFDO1lBQ0osS0FBSyxRQUFRO2dCQUNYLE9BQU87b0JBQ0wsY0FBYyxFQUFFLENBQUMsRUFBRSxVQUFVLEVBQUUsc0JBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQztvQkFDbkQsYUFBYSxFQUFFLDJCQUFhLENBQUMsT0FBTztpQkFDckMsQ0FBQztZQUNKO2dCQUNFLE9BQU87b0JBQ0wsY0FBYyxFQUFFLEVBQUU7b0JBQ2xCLGFBQWEsRUFBRSwyQkFBYSxDQUFDLE1BQU07aUJBQ3BDLENBQUM7UUFDTixDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxLQUFLLENBQUMsSUFBZ0I7UUFDcEIsSUFBSSxJQUFJLFlBQVksZUFBTSxFQUFFLENBQUM7WUFDM0IsTUFBTSxFQUFFLGNBQWMsRUFBRSxhQUFhLEVBQUUsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDO1lBQzVELElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQ2xCLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUN6QyxDQUFDO1lBRUQsSUFBSSxjQUFjLGFBQWQsY0FBYyx1QkFBZCxjQUFjLENBQUUsTUFBTSxFQUFFLENBQUM7Z0JBQzNCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBeUIsQ0FBQztnQkFDdEQsSUFBSSxTQUFTLElBQUksU0FBUyxDQUFDLHNCQUFzQixLQUFLLFNBQVMsRUFBRSxDQUFDO29CQUNoRSxjQUFjLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7Z0JBQzlELENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7Q0FDRjtBQTlERCw0Q0E4REMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEdXJhdGlvbiwgUmVtb3ZhbFBvbGljeSwgdHlwZSBJQXNwZWN0IH0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBCdWNrZXQsIENmbkJ1Y2tldCwgdHlwZSBCdWNrZXRQcm9wcyB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtczNcIjtcbmltcG9ydCB7IElDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuXG5pbnRlcmZhY2UgQ29uZmlnIHtcbiAgZHVyYXRpb246IFwiU0hPUlRcIiB8IFwiTUVESVVNXCIgfCBcIkxPTkdcIjtcbn1cblxuLyoqXG4gKiBBc3BlY3QgdGhhdCBhdXRvbWF0aWNhbGx5IGFwcGxpZXMgY29uZmlndXJhdGlvbi1hd2FyZSBkZWZhdWx0cyB0byBTMyBCdWNrZXRzXG4gKlxuICogVmlzaXRzIGFsbCBjb25zdHJ1Y3RzIGluIHRoZSBzY29wZSBhbmQgYXV0b21hdGljYWxseSBhcHBsaWVzIGNvbmZpZ3VyYXRpb24tc3BlY2lmaWNcbiAqIGxpZmVjeWNsZSBhbmQgcmVtb3ZhbCBwb2xpY2llcyB0byBTMyBidWNrZXRzLiBEaWZmZXJlbnQgY29uZmlndXJhdGlvbnMgYmFsYW5jZVxuICogYmV0d2VlbiBjb3N0IG9wdGltaXphdGlvbiBhbmQgZGF0YSByZXRlbnRpb24gbmVlZHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEFwcGx5IGNvbmZpZ3VyYXRpb24tc3BlY2lmaWMgZGVmYXVsdHMgdG8gYWxsIGJ1Y2tldHNcbiAqIEFzcGVjdHMub2YoYXBwKS5hZGQobmV3IFMzRGVmYXVsdHNBc3BlY3QoeyBhdXRvRGVsZXRlOiB0cnVlLCBkdXJhdGlvbjogJ1NIT1JUJyB9KSk7XG4gKlxuICogLy8gQnVja2V0cyBhdXRvbWF0aWNhbGx5IGluaGVyaXQgY29uZmlndXJhdGlvbiBkZWZhdWx0c1xuICogbmV3IEJ1Y2tldChzdGFjaywgJ015QnVja2V0Jywge1xuICogICAvLyBsaWZlY3ljbGUgYW5kIHJlbW92YWwgcG9saWN5IGFwcGxpZWQgYXV0b21hdGljYWxseVxuICogfSk7XG4gKiBgYGBcbiAqXG4gKiBAc2VlIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9jZGsvYXBpL3YyL2RvY3MvYXdzLWNkay1saWIuYXdzX3MzLkJ1Y2tldC5odG1sXG4gKi9cbmV4cG9ydCBjbGFzcyBTM0RlZmF1bHRzQXNwZWN0IGltcGxlbWVudHMgSUFzcGVjdCB7XG4gIHByaXZhdGUgcmVhZG9ubHkgZGVmYXVsdFByb3BzOiBCdWNrZXRQcm9wcztcblxuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBTM0RlZmF1bHRzQXNwZWN0XG4gICAqXG4gICAqIEBwYXJhbSBjb25maWcgLSBDb25maWd1cmF0aW9uIGlkZW50aWZpZXIgdXNlZCB0byBzZWxlY3QgYXBwcm9wcmlhdGUgZGVmYXVsdHMuXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihjb25maWc6IENvbmZpZykge1xuICAgIGNvbnN0IHByb3BzID0gdGhpcy5yZXRlbnRpb25Qcm9wZXJ0aWVzKGNvbmZpZy5kdXJhdGlvbik7XG4gICAgdGhpcy5kZWZhdWx0UHJvcHMgPSB7IC4uLnByb3BzIH07XG4gIH1cblxuICAvKipcbiAgICogR2V0IGR1cmF0aW9uLXNwZWNpZmljIG9iamVjdCBleHBpcmF0aW9uXG4gICAqXG4gICAqIEBwYXJhbSBkdXJhdGlvbiAtIFRoZSBkdXJhdGlvbiB0byBnZXQgdGhlIGV4cGlyYXRpb24gZm9yXG4gICAqIEByZXR1cm5zIFRoZSBleHBpcmF0aW9uIER1cmF0aW9uLCBvciB1bmRlZmluZWQgZm9yIExPTkcgcmV0ZW50aW9uXG4gICAqL1xuICBwcml2YXRlIHJldGVudGlvblByb3BlcnRpZXMoZHVyYXRpb246IFwiU0hPUlRcIiB8IFwiTUVESVVNXCIgfCBcIkxPTkdcIikge1xuICAgIHN3aXRjaCAoZHVyYXRpb24pIHtcbiAgICAgIGNhc2UgXCJTSE9SVFwiOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGxpZmVjeWNsZVJ1bGVzOiBbeyBleHBpcmF0aW9uOiBEdXJhdGlvbi5kYXlzKDMwKSB9XSxcbiAgICAgICAgICByZW1vdmFsUG9saWN5OiBSZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICAgIH07XG4gICAgICBjYXNlIFwiTUVESVVNXCI6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgbGlmZWN5Y2xlUnVsZXM6IFt7IGV4cGlyYXRpb246IER1cmF0aW9uLmRheXMoOTApIH1dLFxuICAgICAgICAgIHJlbW92YWxQb2xpY3k6IFJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgICAgfTtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgbGlmZWN5Y2xlUnVsZXM6IFtdLFxuICAgICAgICAgIHJlbW92YWxQb2xpY3k6IFJlbW92YWxQb2xpY3kuUkVUQUlOLFxuICAgICAgICB9O1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBWaXNpdHMgYSBjb25zdHJ1Y3QgYW5kIGFwcGxpZXMgY29uZmlndXJhdGlvbi1hcHByb3ByaWF0ZSBkZWZhdWx0c1xuICAgKlxuICAgKiBBcHBsaWVzIGEgcmVtb3ZhbCBwb2xpY3kgYW5kIGxpZmVjeWNsZSBydWxlcyB0byBidWNrZXRzIHRoYXQgZG9uJ3RcbiAgICogYWxyZWFkeSBoYXZlIGEgbGlmZWN5Y2xlIGNvbmZpZ3VyYXRpb24gZXhwbGljaXRseSBzZXQuXG4gICAqXG4gICAqIEBwYXJhbSBub2RlIC0gVGhlIGNvbnN0cnVjdCB0byBwb3RlbnRpYWxseSBtb2RpZnlcbiAgICovXG4gIHZpc2l0KG5vZGU6IElDb25zdHJ1Y3QpOiB2b2lkIHtcbiAgICBpZiAobm9kZSBpbnN0YW5jZW9mIEJ1Y2tldCkge1xuICAgICAgY29uc3QgeyBsaWZlY3ljbGVSdWxlcywgcmVtb3ZhbFBvbGljeSB9ID0gdGhpcy5kZWZhdWx0UHJvcHM7XG4gICAgICBpZiAocmVtb3ZhbFBvbGljeSkge1xuICAgICAgICBub2RlLmFwcGx5UmVtb3ZhbFBvbGljeShyZW1vdmFsUG9saWN5KTtcbiAgICAgIH1cblxuICAgICAgaWYgKGxpZmVjeWNsZVJ1bGVzPy5sZW5ndGgpIHtcbiAgICAgICAgY29uc3QgY2ZuQnVja2V0ID0gbm9kZS5ub2RlLmRlZmF1bHRDaGlsZCBhcyBDZm5CdWNrZXQ7XG4gICAgICAgIGlmIChjZm5CdWNrZXQgJiYgY2ZuQnVja2V0LmxpZmVjeWNsZUNvbmZpZ3VyYXRpb24gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIGxpZmVjeWNsZVJ1bGVzLmZvckVhY2gocnVsZSA9PiBub2RlLmFkZExpZmVjeWNsZVJ1bGUocnVsZSkpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { IAspect } from "aws-cdk-lib";
|
|
2
|
+
import { IConstruct } from "constructs";
|
|
3
|
+
export interface ResourcePrefixAspectProps {
|
|
4
|
+
/**
|
|
5
|
+
* The prefix to apply to all resource names.
|
|
6
|
+
* e.g. 'myapp-prod' → 'myapp-prod-orders'
|
|
7
|
+
*/
|
|
8
|
+
prefix: string;
|
|
9
|
+
/**
|
|
10
|
+
* Resource types to skip entirely.
|
|
11
|
+
* e.g. ['AWS::IAM::Role'] to leave IAM role names untouched.
|
|
12
|
+
* @default []
|
|
13
|
+
*/
|
|
14
|
+
exclude?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* CDK Aspect that automatically prefixes physical resource names across AWS resources.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* **Critical Implementation Notes:**
|
|
21
|
+
*
|
|
22
|
+
* 1. **Stage Synthesis Boundaries**:
|
|
23
|
+
* - CDK `Stage` constructs create synthesis boundaries that prevent App-level aspects
|
|
24
|
+
* from traversing into them
|
|
25
|
+
* - This aspect MUST be applied to each Stage individually, not to the App
|
|
26
|
+
* - Example: `Aspects.of(stage).add(new ResourcePrefixAspect({...}))`
|
|
27
|
+
*
|
|
28
|
+
* 2. **Property Name Casing**:
|
|
29
|
+
* - CloudFormation properties use PascalCase (e.g., `FunctionName`, `BucketName`)
|
|
30
|
+
* - Using camelCase (e.g., `functionName`) will cause errors with versioning aspects
|
|
31
|
+
* - When using `addPropertyOverride()`, always use the exact CloudFormation property name
|
|
32
|
+
*
|
|
33
|
+
* 3. **Aspect Priority with Versioning**:
|
|
34
|
+
* - When combining with LambdaAndStepFunctionVersioningAspect, apply this aspect BEFORE versioning
|
|
35
|
+
* - Use CDK's priority system: `Aspects.of(stage).add(prefixAspect, { priority: 100 })`
|
|
36
|
+
* - Then: `Aspects.of(stage).add(versioningAspect, { priority: 200 })`
|
|
37
|
+
* - Lower priority numbers run first
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Correct usage with staging
|
|
42
|
+
* const stage = new ApplicationStage(app, 'prod');
|
|
43
|
+
* Aspects.of(stage).add(new ResourcePrefixAspect({ prefix: 'myapp' }), { priority: 100 });
|
|
44
|
+
* Aspects.of(stage).add(new LambdaAndStepFunctionVersioningAspect(), { priority: 200 });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare class ResourcePrefixAspect implements IAspect {
|
|
48
|
+
private readonly prefix;
|
|
49
|
+
private readonly exclude;
|
|
50
|
+
constructor(props: ResourcePrefixAspectProps);
|
|
51
|
+
visit(node: IConstruct): void;
|
|
52
|
+
private isAlreadyPrefixed;
|
|
53
|
+
private deriveNameFromLogicalId;
|
|
54
|
+
/**
|
|
55
|
+
* Builds the prefixed name, handling special cases for specific resource types.
|
|
56
|
+
*
|
|
57
|
+
* @param baseName - The base resource name (without prefix)
|
|
58
|
+
* @param cfnResourceType - The CloudFormation resource type
|
|
59
|
+
* @param cfnProperties - The CloudFormation resource properties
|
|
60
|
+
* @returns The final prefixed name with any special case handling applied
|
|
61
|
+
*/
|
|
62
|
+
private buildPrefixedName;
|
|
63
|
+
/**
|
|
64
|
+
* Validates resource-specific naming requirements that AWS enforces.
|
|
65
|
+
* Throws synthesis errors for violations.
|
|
66
|
+
*/
|
|
67
|
+
private validateResourceName;
|
|
68
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResourcePrefixAspect = void 0;
|
|
4
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
|
+
/**
|
|
6
|
+
* Maps CloudFormation resource types to their name property and AWS length limits.
|
|
7
|
+
* Sources: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
|
|
8
|
+
*/
|
|
9
|
+
const RESOURCE_CONFIG = {
|
|
10
|
+
// Compute
|
|
11
|
+
"AWS::Lambda::Function": { cfnName: "FunctionName", maxLength: 64 },
|
|
12
|
+
// Storage
|
|
13
|
+
"AWS::S3::Bucket": { cfnName: "BucketName", maxLength: 63 },
|
|
14
|
+
"AWS::DynamoDB::Table": { cfnName: "TableName", maxLength: 255 },
|
|
15
|
+
"AWS::DynamoDB::GlobalTable": { cfnName: "TableName", maxLength: 255 },
|
|
16
|
+
// Messaging
|
|
17
|
+
"AWS::SQS::Queue": { cfnName: "QueueName", maxLength: 80 },
|
|
18
|
+
"AWS::SNS::Topic": { cfnName: "TopicName", maxLength: 256 },
|
|
19
|
+
// Eventing & Orchestration
|
|
20
|
+
"AWS::Events::EventBus": { cfnName: "Name", maxLength: 256 },
|
|
21
|
+
"AWS::Events::Rule": { cfnName: "Name", maxLength: 64 },
|
|
22
|
+
"AWS::Events::Connection": { cfnName: "Name", maxLength: 64 },
|
|
23
|
+
"AWS::Pipes::Pipe": { cfnName: "Name", maxLength: 64 },
|
|
24
|
+
"AWS::StepFunctions::StateMachine": {
|
|
25
|
+
cfnName: "StateMachineName",
|
|
26
|
+
maxLength: 80,
|
|
27
|
+
},
|
|
28
|
+
"AWS::StepFunctions::Activity": { cfnName: "Name", maxLength: 80 },
|
|
29
|
+
"AWS::Scheduler::Schedule": { cfnName: "Name", maxLength: 64 },
|
|
30
|
+
"AWS::Scheduler::ScheduleGroup": { cfnName: "Name", maxLength: 64 },
|
|
31
|
+
// API
|
|
32
|
+
"AWS::ApiGateway::RestApi": { cfnName: "Name", maxLength: 128 },
|
|
33
|
+
"AWS::ApiGateway::UsagePlan": { cfnName: "UsagePlanName", maxLength: 128 },
|
|
34
|
+
"AWS::ApiGateway::ApiKey": { cfnName: "Name", maxLength: 128 },
|
|
35
|
+
"AWS::ApiGatewayV2::Api": { cfnName: "Name", maxLength: 128 },
|
|
36
|
+
"AWS::ApiGatewayV2::Authorizer": { cfnName: "Name", maxLength: 128 },
|
|
37
|
+
// Secrets & Config
|
|
38
|
+
"AWS::SecretsManager::Secret": { cfnName: "Name", maxLength: 512 },
|
|
39
|
+
"AWS::SSM::Parameter": { cfnName: "Name", maxLength: 2048 },
|
|
40
|
+
"AWS::AppConfig::Application": { cfnName: "Name", maxLength: 64 },
|
|
41
|
+
"AWS::AppConfig::Environment": { cfnName: "Name", maxLength: 64 },
|
|
42
|
+
// Notifications
|
|
43
|
+
"AWS::Notifications::NotificationConfiguration": {
|
|
44
|
+
cfnName: "Name",
|
|
45
|
+
maxLength: 64,
|
|
46
|
+
},
|
|
47
|
+
// Observability
|
|
48
|
+
"AWS::Logs::LogGroup": { cfnName: "LogGroupName", maxLength: 512 },
|
|
49
|
+
"AWS::CloudWatch::Alarm": { cfnName: "AlarmName", maxLength: 255 },
|
|
50
|
+
// IAM
|
|
51
|
+
"AWS::IAM::Role": { cfnName: "RoleName", maxLength: 64 },
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* CDK Aspect that automatically prefixes physical resource names across AWS resources.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* **Critical Implementation Notes:**
|
|
58
|
+
*
|
|
59
|
+
* 1. **Stage Synthesis Boundaries**:
|
|
60
|
+
* - CDK `Stage` constructs create synthesis boundaries that prevent App-level aspects
|
|
61
|
+
* from traversing into them
|
|
62
|
+
* - This aspect MUST be applied to each Stage individually, not to the App
|
|
63
|
+
* - Example: `Aspects.of(stage).add(new ResourcePrefixAspect({...}))`
|
|
64
|
+
*
|
|
65
|
+
* 2. **Property Name Casing**:
|
|
66
|
+
* - CloudFormation properties use PascalCase (e.g., `FunctionName`, `BucketName`)
|
|
67
|
+
* - Using camelCase (e.g., `functionName`) will cause errors with versioning aspects
|
|
68
|
+
* - When using `addPropertyOverride()`, always use the exact CloudFormation property name
|
|
69
|
+
*
|
|
70
|
+
* 3. **Aspect Priority with Versioning**:
|
|
71
|
+
* - When combining with LambdaAndStepFunctionVersioningAspect, apply this aspect BEFORE versioning
|
|
72
|
+
* - Use CDK's priority system: `Aspects.of(stage).add(prefixAspect, { priority: 100 })`
|
|
73
|
+
* - Then: `Aspects.of(stage).add(versioningAspect, { priority: 200 })`
|
|
74
|
+
* - Lower priority numbers run first
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Correct usage with staging
|
|
79
|
+
* const stage = new ApplicationStage(app, 'prod');
|
|
80
|
+
* Aspects.of(stage).add(new ResourcePrefixAspect({ prefix: 'myapp' }), { priority: 100 });
|
|
81
|
+
* Aspects.of(stage).add(new LambdaAndStepFunctionVersioningAspect(), { priority: 200 });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
class ResourcePrefixAspect {
|
|
85
|
+
constructor(props) {
|
|
86
|
+
var _a;
|
|
87
|
+
if (!props.prefix || !/^[a-z0-9-]+$/.test(props.prefix))
|
|
88
|
+
throw new Error(`ResourcePrefixAspect: prefix must contain only lowercase alphanumeric characters and hyphens, got "${props.prefix}"`);
|
|
89
|
+
this.prefix = props.prefix;
|
|
90
|
+
this.exclude = new Set((_a = props.exclude) !== null && _a !== void 0 ? _a : []);
|
|
91
|
+
}
|
|
92
|
+
visit(node) {
|
|
93
|
+
var _a, _b;
|
|
94
|
+
if (!(node instanceof aws_cdk_lib_1.CfnResource))
|
|
95
|
+
return;
|
|
96
|
+
const resourceType = node.cfnResourceType;
|
|
97
|
+
const config = RESOURCE_CONFIG[resourceType];
|
|
98
|
+
if (!config || this.exclude.has(resourceType)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const { cfnName, maxLength } = config;
|
|
102
|
+
const cfnProperties = (_b = (_a = node._cfnProperties) !== null && _a !== void 0 ? _a : node.cfnProperties) !== null && _b !== void 0 ? _b : {};
|
|
103
|
+
// Get any explicitly set name, or derive one from the logical ID
|
|
104
|
+
const existingName = typeof cfnProperties[cfnName] === "string"
|
|
105
|
+
? cfnProperties[cfnName]
|
|
106
|
+
: undefined;
|
|
107
|
+
const baseName = existingName !== null && existingName !== void 0 ? existingName : this.deriveNameFromLogicalId(node);
|
|
108
|
+
if (this.isAlreadyPrefixed(baseName)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const finalName = this.buildPrefixedName(baseName, resourceType, cfnProperties);
|
|
112
|
+
const hasError = this.validateResourceName(finalName, resourceType, maxLength, node);
|
|
113
|
+
if (hasError)
|
|
114
|
+
return;
|
|
115
|
+
node.addPropertyOverride(cfnName, finalName);
|
|
116
|
+
}
|
|
117
|
+
isAlreadyPrefixed(name) {
|
|
118
|
+
return (name.startsWith(`${this.prefix}-`) || name.startsWith(`/${this.prefix}/`));
|
|
119
|
+
}
|
|
120
|
+
deriveNameFromLogicalId(node) {
|
|
121
|
+
const logicalId = aws_cdk_lib_1.Stack.of(node).getLogicalId(node);
|
|
122
|
+
return logicalId
|
|
123
|
+
.replace(/[^a-zA-Z0-9]+/g, "-") // non-alphanumeric → dash
|
|
124
|
+
.replace(/^-+|-+$/g, ""); // trim leading/trailing dashes
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds the prefixed name, handling special cases for specific resource types.
|
|
128
|
+
*
|
|
129
|
+
* @param baseName - The base resource name (without prefix)
|
|
130
|
+
* @param cfnResourceType - The CloudFormation resource type
|
|
131
|
+
* @param cfnProperties - The CloudFormation resource properties
|
|
132
|
+
* @returns The final prefixed name with any special case handling applied
|
|
133
|
+
*/
|
|
134
|
+
buildPrefixedName(baseName, cfnResourceType, cfnProperties) {
|
|
135
|
+
// Special case: FIFO queues must end with .fifo suffix
|
|
136
|
+
if (cfnResourceType === "AWS::SQS::Queue" &&
|
|
137
|
+
cfnProperties.fifoQueue === true &&
|
|
138
|
+
!baseName.endsWith(".fifo")) {
|
|
139
|
+
return `${this.prefix}-${baseName}.fifo`;
|
|
140
|
+
}
|
|
141
|
+
// Special case: FIFO topics must end with .fifo suffix
|
|
142
|
+
if (cfnResourceType === "AWS::SNS::Topic" &&
|
|
143
|
+
cfnProperties.fifoTopic === true &&
|
|
144
|
+
!baseName.endsWith(".fifo")) {
|
|
145
|
+
return `${this.prefix}-${baseName}.fifo`;
|
|
146
|
+
}
|
|
147
|
+
// Special case: SSM parameter names use path-style prefix
|
|
148
|
+
if (cfnResourceType === "AWS::SSM::Parameter") {
|
|
149
|
+
return `/${this.prefix}/${baseName}`;
|
|
150
|
+
}
|
|
151
|
+
// Special case: S3 bucket name be lowercase only
|
|
152
|
+
if (cfnResourceType === "AWS::S3::Bucket") {
|
|
153
|
+
return `${this.prefix}-${baseName}`.toLowerCase();
|
|
154
|
+
}
|
|
155
|
+
// Default: simple prefix
|
|
156
|
+
return `${this.prefix}-${baseName}`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validates resource-specific naming requirements that AWS enforces.
|
|
160
|
+
* Throws synthesis errors for violations.
|
|
161
|
+
*/
|
|
162
|
+
validateResourceName(name, cfnResourceType, maxLength, node) {
|
|
163
|
+
let hasError = false;
|
|
164
|
+
// S3 bucket names cannot contain underscores
|
|
165
|
+
if (cfnResourceType === "AWS::S3::Bucket") {
|
|
166
|
+
if (name !== name.toLowerCase()) {
|
|
167
|
+
aws_cdk_lib_1.Annotations.of(node).addError(`[ResourcePrefixAspect] S3 bucket name "${name}" contains uppercase letters. ` +
|
|
168
|
+
`Bucket names must be lowercase only.`);
|
|
169
|
+
hasError = true;
|
|
170
|
+
}
|
|
171
|
+
if (name.includes("_")) {
|
|
172
|
+
aws_cdk_lib_1.Annotations.of(node).addError(`[ResourcePrefixAspect] S3 bucket name "${name}" contains underscores. ` +
|
|
173
|
+
`Bucket names cannot contain underscores. Use hyphens instead.`);
|
|
174
|
+
hasError = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (name.length > maxLength) {
|
|
178
|
+
aws_cdk_lib_1.Annotations.of(node).addError(`[ResourcePrefixAspect] "${name}" (${name.length} chars) exceeds the ` +
|
|
179
|
+
`maximum allowed length of ${maxLength} for ${cfnResourceType}. ` +
|
|
180
|
+
`Shorten the resource base name or your prefix ("${this.prefix}").`);
|
|
181
|
+
hasError = true;
|
|
182
|
+
}
|
|
183
|
+
return hasError;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.ResourcePrefixAspect = ResourcePrefixAspect;
|
|
187
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb3VyY2UtcHJlZml4LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVzb3VyY2UtcHJlZml4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZDQUF1RTtBQWdDdkU7OztHQUdHO0FBQ0gsTUFBTSxlQUFlLEdBQXVDO0lBQzFELFVBQVU7SUFDVix1QkFBdUIsRUFBRSxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUVuRSxVQUFVO0lBQ1YsaUJBQWlCLEVBQUUsRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUU7SUFDM0Qsc0JBQXNCLEVBQUUsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUU7SUFDaEUsNEJBQTRCLEVBQUUsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUU7SUFFdEUsWUFBWTtJQUNaLGlCQUFpQixFQUFFLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsRUFBRSxFQUFFO0lBQzFELGlCQUFpQixFQUFFLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFO0lBRTNELDJCQUEyQjtJQUMzQix1QkFBdUIsRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRTtJQUM1RCxtQkFBbUIsRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUN2RCx5QkFBeUIsRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUM3RCxrQkFBa0IsRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUN0RCxrQ0FBa0MsRUFBRTtRQUNsQyxPQUFPLEVBQUUsa0JBQWtCO1FBQzNCLFNBQVMsRUFBRSxFQUFFO0tB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aEIsSUFBSSxRQUFRLEdBQUcsS0FBSyxDQUFDO1FBRXJCLDZDQUE2QztRQUM3QyxJQUFJLGVBQWUsS0FBSyxpQkFBaUIsRUFBRSxDQUFDO1lBQzFDLElBQUksSUFBSSxLQUFLLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO2dCQUNoQyx5QkFBVyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQzNCLDBDQUEwQyxJQUFJLGdDQUFnQztvQkFDNUUsc0NBQXNDLENBQ3pDLENBQUM7Z0JBQ0YsUUFBUSxHQUFHLElBQUksQ0FBQztZQUNsQixDQUFDO1lBRUQsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3ZCLHlCQUFXLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FDM0IsMENBQTBDLElBQUksMEJBQTBCO29CQUN0RSwrREFBK0QsQ0FDbEUsQ0FBQztnQkFDRixRQUFRLEdBQUcsSUFBSSxDQUFDO1lBQ2xCLENBQUM7UUFDSCxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsRUFBRSxDQUFDO1lBQzVCLHlCQUFXLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FDM0IsMkJBQTJCLElBQUksTUFBTSxJQUFJLENBQUMsTUFBTSxzQkFBc0I7Z0JBQ3BFLDZCQUE2QixTQUFTLFFBQVEsZUFBZSxJQUFJO2dCQUNqRSxtREFBbUQsSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUN0RSxDQUFDO1lBQ0YsUUFBUSxHQUFHLElBQUksQ0FBQztRQUNsQixDQUFDO1FBRUQsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztDQUNGO0FBL0pELG9EQStKQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFubm90YXRpb25zLCBDZm5SZXNvdXJjZSwgSUFzcGVjdCwgU3RhY2sgfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCB7IElDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuXG5pbnRlcmZhY2UgUmVzb3VyY2VOYW1lQ29uZmlnIHtcbiAgLyoqIFRoZSBDbG91ZEZvcm1hdGlvbiBwcm9wZXJ0eSB0aGF0IGhvbGRzIHRoZSByZXNvdXJjZSdzIHBoeXNpY2FsIG5hbWUgKi9cbiAgY2ZuTmFtZTogc3RyaW5nO1xuICAvKiogTWF4aW11bSBhbGxvd2VkIGNoYXJhY3RlciBsZW5ndGggZm9yIHRoaXMgcmVzb3VyY2UgdHlwZSAqL1xuICBtYXhMZW5ndGg6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBFeHRlbnNpb24gdHlwZSB0byBhY2Nlc3MgaW50ZXJuYWwgQ2xvdWRGb3JtYXRpb24gcHJvcGVydGllcyBvbiBDZm5SZXNvdXJjZS5cbiAqXG4gKiBDREsgZG9lcyBub3QgcHJvdmlkZSBhIHB1YmxpYyBBUEkgdG8gcmVhZCByYXcgQ2xvdWRGb3JtYXRpb24gcHJvcGVydGllcyB0aGF0IGhhdmVcbiAqIGJlZW4gc2V0IG9uIGEgQ2ZuUmVzb3VyY2UuIFRoZSBwcm9wZXJ0aWVzIGBfY2ZuUHJvcGVydGllc2AgYW5kIGBjZm5Qcm9wZXJ0aWVzYCBhcmVcbiAqIHByb3RlY3RlZCBhbmQgb25seSBhY2Nlc3NpYmxlIHdpdGhpbiB0aGUgQ2ZuUmVzb3VyY2UgY2xhc3MgaGllcmFyY2h5LlxuICpcbiAqIFdlIGludmVzdGlnYXRlZCB1c2luZyBDREsncyBwdWJsaWMgQVBJcywgYnV0IHRoZXJlIGlzIG5vIHB1YmxpYyBtZXRob2QgdG8gcmV0cmlldmVcbiAqIHRoZSByYXcgcHJvcGVydHkgdmFsdWVzIHRoYXQgaGF2ZSBiZWVuIHNldCAoZS5nLiwgdmlhIGNvbnN0cnVjdG9yIHByb3BzIG9yIGFkZFByb3BlcnR5T3ZlcnJpZGUpLlxuICogVGhlIG9ubHkgcHVibGljIEFQSXMgYXJlIGZvciB3cml0aW5nIChhZGRQcm9wZXJ0eU92ZXJyaWRlLCBhZGRQcm9wZXJ0eURlbGV0aW9uT3ZlcnJpZGUpIG9yXG4gKiBnZXR0aW5nIGF0dHJpYnV0ZSByZWZlcmVuY2VzIChnZXRBdHQpLCBub3QgZm9yIHJlYWRpbmcgbGl0ZXJhbCBwcm9wZXJ0eSB2YWx1ZXMuXG4gKlxuICogVGhpcyB0eXBlIGFzc2VydGlvbiBpcyBzYWZlIGJlY2F1c2U6XG4gKiAtIFdlIG9ubHkgcmVhZCB0aGVzZSBwcm9wZXJ0aWVzLCBuZXZlciBtb2RpZnkgdGhlbVxuICogLSBXZSBuZWVkIHRvIGNoZWNrIGlmIGEgdXNlciBleHBsaWNpdGx5IHNldCBhIG5hbWUgcHJvcGVydHkgYmVmb3JlIGFwcGx5aW5nIG91ciBwcmVmaXhcbiAqIC0gVGhlIHByb3BlcnRpZXMgZXhpc3QgYXQgcnVudGltZSBhbmQgYXJlIHBhcnQgb2YgQ0RLJ3MgaW50ZXJuYWwgaW1wbGVtZW50YXRpb25cbiAqL1xudHlwZSBDZm5SZXNvdXJjZVdpdGhQcm9wcyA9IENmblJlc291cmNlICYge1xuICByZWFkb25seSBfY2ZuUHJvcGVydGllcz86IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICByZWFkb25seSBjZm5Qcm9wZXJ0aWVzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG59O1xuXG4vKipcbiAqIE1hcHMgQ2xvdWRGb3JtYXRpb24gcmVzb3VyY2UgdHlwZXMgdG8gdGhlaXIgbmFtZSBwcm9wZXJ0eSBhbmQgQVdTIGxlbmd0aCBsaW1pdHMuXG4gKiBTb3VyY2VzOiBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQVdTQ2xvdWRGb3JtYXRpb24vbGF0ZXN0L1VzZXJHdWlkZS9hd3MtdGVtcGxhdGUtcmVzb3VyY2UtdHlwZS1yZWYuaHRtbFxuICovXG5jb25zdCBSRVNPVVJDRV9DT05GSUc6IFJlY29yZDxzdHJpbmcsIFJlc291cmNlTmFtZUNvbmZpZz4gPSB7XG4gIC8vIENvbXB1dGVcbiAgXCJBV1M6OkxhbWJkYTo6RnVuY3Rpb25cIjogeyBjZm5OYW1lOiBcIkZ1bmN0aW9uTmFtZVwiLCBtYXhMZW5ndGg6IDY0IH0sXG5cbiAgLy8gU3RvcmFnZVxuICBcIkFXUzo6UzM6OkJ1Y2tldFwiOiB7IGNmbk5hbWU6IFwiQnVja2V0TmFtZVwiLCBtYXhMZW5ndGg6IDYzIH0sXG4gIFwiQVdTOjpEeW5hbW9EQjo6VGFibGVcIjogeyBjZm5OYW1lOiBcIlRhYmxlTmFtZVwiLCBtYXhMZW5ndGg6IDI1NSB9LFxuICBcIkFXUzo6RHluYW1vREI6Okdsb2JhbFRhYmxlXCI6IHsgY2ZuTmFtZTogXCJUYWJsZU5hbWVcIiwgbWF4TGVuZ3RoOiAyNTUgfSxcblxuICAvLyBNZXNzYWdpbmdcbiAgXCJBV1M6OlNRUzo6UXVldWVcIjogeyBjZm5OYW1lOiBcIlF1ZXVlTmFtZVwiLCBtYXhMZW5ndGg6IDgwIH0sXG4gIFwiQVdTOjpTTlM6OlRvcGljXCI6IHsgY2ZuTmFtZTogXCJUb3BpY05hbWVcIiwgbWF4TGVuZ3RoOiAyNTYgfSxcblxuICAvLyBFdmVudGluZyAmIE9yY2hlc3RyYXRpb25cbiAgXCJBV1M6OkV2ZW50czo6RXZlbnRCdXNcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiAyNTYgfSxcbiAgXCJBV1M6OkV2ZW50czo6UnVsZVwiOiB7IGNmbk5hbWU6IFwiTmFtZVwiLCBtYXhMZW5ndGg6IDY0IH0sXG4gIFwiQVdTOjpFdmVudHM6OkNvbm5lY3Rpb25cIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiA2NCB9LFxuICBcIkFXUzo6UGlwZXM6OlBpcGVcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiA2NCB9LFxuICBcIkFXUzo6U3RlcEZ1bmN0aW9uczo6U3RhdGVNYWNoaW5lXCI6IHtcbiAgICBjZm5OYW1lOiBcIlN0YXRlTWFjaGluZU5hbWVcIixcbiAgICBtYXhMZW5ndGg6IDgwLFxuICB9LFxuICBcIkFXUzo6U3RlcEZ1bmN0aW9uczo6QWN0aXZpdHlcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiA4MCB9LFxuICBcIkFXUzo6U2NoZWR1bGVyOjpTY2hlZHVsZVwiOiB7IGNmbk5hbWU6IFwiTmFtZVwiLCBtYXhMZW5ndGg6IDY0IH0sXG4gIFwiQVdTOjpTY2hlZHVsZXI6OlNjaGVkdWxlR3JvdXBcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiA2NCB9LFxuXG4gIC8vIEFQSVxuICBcIkFXUzo6QXBpR2F0ZXdheTo6UmVzdEFwaVwiOiB7IGNmbk5hbWU6IFwiTmFtZVwiLCBtYXhMZW5ndGg6IDEyOCB9LFxuICBcIkFXUzo6QXBpR2F0ZXdheTo6VXNhZ2VQbGFuXCI6IHsgY2ZuTmFtZTogXCJVc2FnZVBsYW5OYW1lXCIsIG1heExlbmd0aDogMTI4IH0sXG4gIFwiQVdTOjpBcGlHYXRld2F5OjpBcGlLZXlcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiAxMjggfSxcbiAgXCJBV1M6OkFwaUdhdGV3YXlWMjo6QXBpXCI6IHsgY2ZuTmFtZTogXCJOYW1lXCIsIG1heExlbmd0aDogMTI4IH0sXG4gIFwiQVdTOjpBcGlHYXRld2F5VjI6OkF1dGhvcml6ZXJcIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiAxMjggfSxcblxuICAvLyBTZWNyZXRzICYgQ29uZmlnXG4gIFwiQVdTOjpTZWNyZXRzTWFuYWdlcjo6U2VjcmV0XCI6IHsgY2ZuTmFtZTogXCJOYW1lXCIsIG1heExlbmd0aDogNTEyIH0sXG4gIFwiQVdTOjpTU006OlBhcmFtZXRlclwiOiB7IGNmbk5hbWU6IFwiTmFtZVwiLCBtYXhMZW5ndGg6IDIwNDggfSxcbiAgXCJBV1M6OkFwcENvbmZpZzo6QXBwbGljYXRpb25cIjogeyBjZm5OYW1lOiBcIk5hbWVcIiwgbWF4TGVuZ3RoOiA2NCB9LFxuICBcIkFXUzo6QXBwQ29uZmlnOjpFbnZpcm9ubWVudFwiOiB7IGNmbk5hbWU6IFwiTmFtZVwiLCBtYXhMZW5ndGg6IDY0IH0sXG5cbiAgLy8gTm90aWZpY2F0aW9uc1xuICBcIkFXUzo6Tm90aWZpY2F0aW9uczo6Tm90aWZpY2F0aW9uQ29uZmlndXJhdGlvblwiOiB7XG4gICAgY2ZuTmFtZTogXCJOYW1lXCIsXG4gICAgbWF4TGVuZ3RoOiA2NCxcbiAgfSxcblxuICAvLyBPYnNlcnZhYmlsaXR5XG4gIFwiQVdTOjpMb2dzOjpMb2dHcm91cFwiOiB7IGNmbk5hbWU6IFwiTG9nR3JvdXBOYW1lXCIsIG1heExlbmd0aDogNTEyIH0sXG4gIFwiQVdTOjpDbG91ZFdhdGNoOjpBbGFybVwiOiB7IGNmbk5hbWU6IFwiQWxhcm1OYW1lXCIsIG1heExlbmd0aDogMjU1IH0sXG5cbiAgLy8gSUFNXG4gIFwiQVdTOjpJQU06OlJvbGVcIjogeyBjZm5OYW1lOiBcIlJvbGVOYW1lXCIsIG1heExlbmd0aDogNjQgfSxcbn07XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb3VyY2VQcmVmaXhBc3BlY3RQcm9wcyB7XG4gIC8qKlxuICAgKiBUaGUgcHJlZml4IHRvIGFwcGx5IHRvIGFsbCByZXNvdXJjZSBuYW1lcy5cbiAgICogZS5nLiAnbXlhcHAtcHJvZCcg4oaSICdteWFwcC1wcm9kLW9yZGVycydcbiAgICovXG4gIHByZWZpeDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBSZXNvdXJjZSB0eXBlcyB0byBza2lwIGVudGlyZWx5LlxuICAgKiBlLmcuIFsnQVdTOjpJQU06OlJvbGUnXSB0byBsZWF2ZSBJQU0gcm9sZSBuYW1lcyB1bnRvdWNoZWQuXG4gICAqIEBkZWZhdWx0IFtdXG4gICAqL1xuICBleGNsdWRlPzogc3RyaW5nW107XG59XG5cbi8qKlxuICogQ0RLIEFzcGVjdCB0aGF0IGF1dG9tYXRpY2FsbHkgcHJlZml4ZXMgcGh5c2ljYWwgcmVzb3VyY2UgbmFtZXMgYWNyb3NzIEFXUyByZXNvdXJjZXMuXG4gKlxuICogQHJlbWFya3NcbiAqICoqQ3JpdGljYWwgSW1wbGVtZW50YXRpb24gTm90ZXM6KipcbiAqXG4gKiAxLiAqKlN0YWdlIFN5bnRoZXNpcyBCb3VuZGFyaWVzKio6XG4gKiAgICAtIENESyBgU3RhZ2VgIGNvbnN0cnVjdHMgY3JlYXRlIHN5bnRoZXNpcyBib3VuZGFyaWVzIHRoYXQgcHJldmVudCBBcHAtbGV2ZWwgYXNwZWN0c1xuICogICAgICBmcm9tIHRyYXZlcnNpbmcgaW50byB0aGVtXG4gKiAgICAtIFRoaXMgYXNwZWN0IE1VU1QgYmUgYXBwbGllZCB0byBlYWNoIFN0YWdlIGluZGl2aWR1YWxseSwgbm90IHRvIHRoZSBBcHBcbiAqICAgIC0gRXhhbXBsZTogYEFzcGVjdHMub2Yoc3RhZ2UpLmFkZChuZXcgUmVzb3VyY2VQcmVmaXhBc3BlY3Qoey4uLn0pKWBcbiAqXG4gKiAyLiAqKlByb3BlcnR5IE5hbWUgQ2FzaW5nKio6XG4gKiAgICAtIENsb3VkRm9ybWF0aW9uIHByb3BlcnRpZXMgdXNlIFBhc2NhbENhc2UgKGUuZy4sIGBGdW5jdGlvbk5hbWVgLCBgQnVja2V0TmFtZWApXG4gKiAgICAtIFVzaW5nIGNhbWVsQ2FzZSAoZS5nLiwgYGZ1bmN0aW9uTmFtZWApIHdpbGwgY2F1c2UgZXJyb3JzIHdpdGggdmVyc2lvbmluZyBhc3BlY3RzXG4gKiAgICAtIFdoZW4gdXNpbmcgYGFkZFByb3BlcnR5T3ZlcnJpZGUoKWAsIGFsd2F5cyB1c2UgdGhlIGV4YWN0IENsb3VkRm9ybWF0aW9uIHByb3BlcnR5IG5hbWVcbiAqXG4gKiAzLiAqKkFzcGVjdCBQcmlvcml0eSB3aXRoIFZlcnNpb25pbmcqKjpcbiAqICAgIC0gV2hlbiBjb21iaW5pbmcgd2l0aCBMYW1iZGFBbmRTdGVwRnVuY3Rpb25WZXJzaW9uaW5nQXNwZWN0LCBhcHBseSB0aGlzIGFzcGVjdCBCRUZPUkUgdmVyc2lvbmluZ1xuICogICAgLSBVc2UgQ0RLJ3MgcHJpb3JpdHkgc3lzdGVtOiBgQXNwZWN0cy5vZihzdGFnZSkuYWRkKHByZWZpeEFzcGVjdCwgeyBwcmlvcml0eTogMTAwIH0pYFxuICogICAgLSBUaGVuOiBgQXNwZWN0cy5vZihzdGFnZSkuYWRkKHZlcnNpb25pbmdBc3BlY3QsIHsgcHJpb3JpdHk6IDIwMCB9KWBcbiAqICAgIC0gTG93ZXIgcHJpb3JpdHkgbnVtYmVycyBydW4gZmlyc3RcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ29ycmVjdCB1c2FnZSB3aXRoIHN0YWdpbmdcbiAqIGNvbnN0IHN0YWdlID0gbmV3IEFwcGxpY2F0aW9uU3RhZ2UoYXBwLCAncHJvZCcpO1xuICogQXNwZWN0cy5vZihzdGFnZSkuYWRkKG5ldyBSZXNvdXJjZVByZWZpeEFzcGVjdCh7IHByZWZpeDogJ215YXBwJyB9KSwgeyBwcmlvcml0eTogMTAwIH0pO1xuICogQXNwZWN0cy5vZihzdGFnZSkuYWRkKG5ldyBMYW1iZGFBbmRTdGVwRnVuY3Rpb25WZXJzaW9uaW5nQXNwZWN0KCksIHsgcHJpb3JpdHk6IDIwMCB9KTtcbiAqIGBgYFxuICovXG5leHBvcnQgY2xhc3MgUmVzb3VyY2VQcmVmaXhBc3BlY3QgaW1wbGVtZW50cyBJQXNwZWN0IHtcbiAgcHJpdmF0ZSByZWFkb25seSBwcmVmaXg6IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBleGNsdWRlOiBTZXQ8c3RyaW5nPjtcblxuICBjb25zdHJ1Y3Rvcihwcm9wczogUmVzb3VyY2VQcmVmaXhBc3BlY3RQcm9wcykge1xuICAgIGlmICghcHJvcHMucHJlZml4IHx8ICEvXlthLXowLTktXSskLy50ZXN0KHByb3BzLnByZWZpeCkpXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBSZXNvdXJjZVByZWZpeEFzcGVjdDogcHJlZml4IG11c3QgY29udGFpbiBvbmx5IGxvd2VyY2FzZSBhbHBoYW51bWVyaWMgY2hhcmFjdGVycyBhbmQgaHlwaGVucywgZ290IFwiJHtwcm9wcy5wcmVmaXh9XCJgXG4gICAgICApO1xuXG4gICAgdGhpcy5wcmVmaXggPSBwcm9wcy5wcmVmaXg7XG4gICAgdGhpcy5leGNsdWRlID0gbmV3IFNldChwcm9wcy5leGNsdWRlID8/IFtdKTtcbiAgfVxuXG4gIHZpc2l0KG5vZGU6IElDb25zdHJ1Y3QpIHtcbiAgICBpZiAoIShub2RlIGluc3RhbmNlb2YgQ2ZuUmVzb3VyY2UpKSByZXR1cm47XG5cbiAgICBjb25zdCByZXNvdXJjZVR5cGUgPSBub2RlLmNmblJlc291cmNlVHlwZTtcbiAgICBjb25zdCBjb25maWcgPSBSRVNPVVJDRV9DT05GSUdbcmVzb3VyY2VUeXBlXTtcblxuICAgIGlmICghY29uZmlnIHx8IHRoaXMuZXhjbHVkZS5oYXMocmVzb3VyY2VUeXBlKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IHsgY2ZuTmFtZSwgbWF4TGVuZ3RoIH0gPSBjb25maWc7XG4gICAgY29uc3QgY2ZuUHJvcGVydGllczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPVxuICAgICAgKG5vZGUgYXMgQ2ZuUmVzb3VyY2VXaXRoUHJvcHMpLl9jZm5Qcm9wZXJ0aWVzID8/XG4gICAgICAobm9kZSBhcyBDZm5SZXNvdXJjZVdpdGhQcm9wcykuY2ZuUHJvcGVydGllcyA/P1xuICAgICAge307XG5cbiAgICAvLyBHZXQgYW55IGV4cGxpY2l0bHkgc2V0IG5hbWUsIG9yIGRlcml2ZSBvbmUgZnJvbSB0aGUgbG9naWNhbCBJRFxuICAgIGNvbnN0IGV4aXN0aW5nTmFtZSA9XG4gICAgICB0eXBlb2YgY2ZuUHJvcGVydGllc1tjZm5OYW1lXSA9PT0gXCJzdHJpbmdcIlxuICAgICAgICA/IGNmblByb3BlcnRpZXNbY2ZuTmFtZV1cbiAgICAgICAgOiB1bmRlZmluZWQ7XG4gICAgY29uc3QgYmFzZU5hbWUgPSBleGlzdGluZ05hbWUgPz8gdGhpcy5kZXJpdmVOYW1lRnJvbUxvZ2ljYWxJZChub2RlKTtcblxuICAgIGlmICh0aGlzLmlzQWxyZWFkeVByZWZpeGVkKGJhc2VOYW1lKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGZpbmFsTmFtZSA9IHRoaXMuYnVpbGRQcmVmaXhlZE5hbWUoXG4gICAgICBiYXNlTmFtZSxcbiAgICAgIHJlc291cmNlVHlwZSxcbiAgICAgIGNmblByb3BlcnRpZXNcbiAgICApO1xuXG4gICAgY29uc3QgaGFzRXJyb3IgPSB0aGlzLnZhbGlkYXRlUmVzb3VyY2VOYW1lKFxuICAgICAgZmluYWxOYW1lLFxuICAgICAgcmVzb3VyY2VUeXBlLFxuICAgICAgbWF4TGVuZ3RoLFxuICAgICAgbm9kZVxuICAgICk7XG5cbiAgICBpZiAoaGFzRXJyb3IpIHJldHVybjtcblxuICAgIG5vZGUuYWRkUHJvcGVydHlPdmVycmlkZShjZm5OYW1lLCBmaW5hbE5hbWUpO1xuICB9XG5cbiAgcHJpdmF0ZSBpc0FscmVhZHlQcmVmaXhlZChuYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgbmFtZS5zdGFydHNXaXRoKGAke3RoaXMucHJlZml4fS1gKSB8fCBuYW1lLnN0YXJ0c1dpdGgoYC8ke3RoaXMucHJlZml4fS9gKVxuICAgICk7XG4gIH1cblxuICBwcml2YXRlIGRlcml2ZU5hbWVGcm9tTG9naWNhbElkKG5vZGU6IENmblJlc291cmNlKSB7XG4gICAgY29uc3QgbG9naWNhbElkID0gU3RhY2sub2Yobm9kZSkuZ2V0TG9naWNhbElkKG5vZGUpO1xuICAgIHJldHVybiBsb2dpY2FsSWRcbiAgICAgIC5yZXBsYWNlKC9bXmEtekEtWjAtOV0rL2csIFwiLVwiKSAvLyBub24tYWxwaGFudW1lcmljIOKGkiBkYXNoXG4gICAgICAucmVwbGFjZSgvXi0rfC0rJC9nLCBcIlwiKTsgLy8gdHJpbSBsZWFkaW5nL3RyYWlsaW5nIGRhc2hlc1xuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkcyB0aGUgcHJlZml4ZWQgbmFtZSwgaGFuZGxpbmcgc3BlY2lhbCBjYXNlcyBmb3Igc3BlY2lmaWMgcmVzb3VyY2UgdHlwZXMuXG4gICAqXG4gICAqIEBwYXJhbSBiYXNlTmFtZSAtIFRoZSBiYXNlIHJlc291cmNlIG5hbWUgKHdpdGhvdXQgcHJlZml4KVxuICAgKiBAcGFyYW0gY2ZuUmVzb3VyY2VUeXBlIC0gVGhlIENsb3VkRm9ybWF0aW9uIHJlc291cmNlIHR5cGVcbiAgICogQHBhcmFtIGNmblByb3BlcnRpZXMgLSBUaGUgQ2xvdWRGb3JtYXRpb24gcmVzb3VyY2UgcHJvcGVydGllc1xuICAgKiBAcmV0dXJucyBUaGUgZmluYWwgcHJlZml4ZWQgbmFtZSB3aXRoIGFueSBzcGVjaWFsIGNhc2UgaGFuZGxpbmcgYXBwbGllZFxuICAgKi9cbiAgcHJpdmF0ZSBidWlsZFByZWZpeGVkTmFtZShcbiAgICBiYXNlTmFtZTogc3RyaW5nLFxuICAgIGNmblJlc291cmNlVHlwZTogc3RyaW5nLFxuICAgIGNmblByb3BlcnRpZXM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+XG4gICk6IHN0cmluZyB7XG4gICAgLy8gU3BlY2lhbCBjYXNlOiBGSUZPIHF1ZXVlcyBtdXN0IGVuZCB3aXRoIC5maWZvIHN1ZmZpeFxuICAgIGlmIChcbiAgICAgIGNmblJlc291cmNlVHlwZSA9PT0gXCJBV1M6OlNRUzo6UXVldWVcIiAmJlxuICAgICAgY2ZuUHJvcGVydGllcy5maWZvUXVldWUgPT09IHRydWUgJiZcbiAgICAgICFiYXNlTmFtZS5lbmRzV2l0aChcIi5maWZvXCIpXG4gICAgKSB7XG4gICAgICByZXR1cm4gYCR7dGhpcy5wcmVmaXh9LSR7YmFzZU5hbWV9LmZpZm9gO1xuICAgIH1cblxuICAgIC8vIFNwZWNpYWwgY2FzZTogRklGTyB0b3BpY3MgbXVzdCBlbmQgd2l0aCAuZmlmbyBzdWZmaXhcbiAgICBpZiAoXG4gICAgICBjZm5SZXNvdXJjZVR5cGUgPT09IFwiQVdTOjpTTlM6OlRvcGljXCIgJiZcbiAgICAgIGNmblByb3BlcnRpZXMuZmlmb1RvcGljID09PSB0cnVlICYmXG4gICAgICAhYmFzZU5hbWUuZW5kc1dpdGgoXCIuZmlmb1wiKVxuICAgICkge1xuICAgICAgcmV0dXJuIGAke3RoaXMucHJlZml4fS0ke2Jhc2VOYW1lfS5maWZvYDtcbiAgICB9XG5cbiAgICAvLyBTcGVjaWFsIGNhc2U6IFNTTSBwYXJhbWV0ZXIgbmFtZXMgdXNlIHBhdGgtc3R5bGUgcHJlZml4XG4gICAgaWYgKGNmblJlc291cmNlVHlwZSA9PT0gXCJBV1M6OlNTTTo6UGFyYW1ldGVyXCIpIHtcbiAgICAgIHJldHVybiBgLyR7dGhpcy5wcmVmaXh9LyR7YmFzZU5hbWV9YDtcbiAgICB9XG5cbiAgICAvLyBTcGVjaWFsIGNhc2U6IFMzIGJ1Y2tldCBuYW1lIGJlIGxvd2VyY2FzZSBvbmx5XG4gICAgaWYgKGNmblJlc291cmNlVHlwZSA9PT0gXCJBV1M6OlMzOjpCdWNrZXRcIikge1xuICAgICAgcmV0dXJuIGAke3RoaXMucHJlZml4fS0ke2Jhc2VOYW1lfWAudG9Mb3dlckNhc2UoKTtcbiAgICB9XG5cbiAgICAvLyBEZWZhdWx0OiBzaW1wbGUgcHJlZml4XG4gICAgcmV0dXJuIGAke3RoaXMucHJlZml4fS0ke2Jhc2VOYW1lfWA7XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGVzIHJlc291cmNlLXNwZWNpZmljIG5hbWluZyByZXF1aXJlbWVudHMgdGhhdCBBV1MgZW5mb3JjZXMuXG4gICAqIFRocm93cyBzeW50aGVzaXMgZXJyb3JzIGZvciB2aW9sYXRpb25zLlxuICAgKi9cbiAgcHJpdmF0ZSB2YWxpZGF0ZVJlc291cmNlTmFtZShcbiAgICBuYW1lOiBzdHJpbmcsXG4gICAgY2ZuUmVzb3VyY2VUeXBlOiBzdHJpbmcsXG4gICAgbWF4TGVuZ3RoOiBudW1iZXIsXG4gICAgbm9kZTogSUNvbnN0cnVjdFxuICApOiBib29sZWFuIHtcbiAgICBsZXQgaGFzRXJyb3IgPSBmYWxzZTtcblxuICAgIC8vIFMzIGJ1Y2tldCBuYW1lcyBjYW5ub3QgY29udGFpbiB1bmRlcnNjb3Jlc1xuICAgIGlmIChjZm5SZXNvdXJjZVR5cGUgPT09IFwiQVdTOjpTMzo6QnVja2V0XCIpIHtcbiAgICAgIGlmIChuYW1lICE9PSBuYW1lLnRvTG93ZXJDYXNlKCkpIHtcbiAgICAgICAgQW5ub3RhdGlvbnMub2Yobm9kZSkuYWRkRXJyb3IoXG4gICAgICAgICAgYFtSZXNvdXJjZVByZWZpeEFzcGVjdF0gUzMgYnVja2V0IG5hbWUgXCIke25hbWV9XCIgY29udGFpbnMgdXBwZXJjYXNlIGxldHRlcnMuIGAgK1xuICAgICAgICAgICAgYEJ1Y2tldCBuYW1lcyBtdXN0IGJlIGxvd2VyY2FzZSBvbmx5LmBcbiAgICAgICAgKTtcbiAgICAgICAgaGFzRXJyb3IgPSB0cnVlO1xuICAgICAgfVxuXG4gICAgICBpZiAobmFtZS5pbmNsdWRlcyhcIl9cIikpIHtcbiAgICAgICAgQW5ub3RhdGlvbnMub2Yobm9kZSkuYWRkRXJyb3IoXG4gICAgICAgICAgYFtSZXNvdXJjZVByZWZpeEFzcGVjdF0gUzMgYnVja2V0IG5hbWUgXCIke25hbWV9XCIgY29udGFpbnMgdW5kZXJzY29yZXMuIGAgK1xuICAgICAgICAgICAgYEJ1Y2tldCBuYW1lcyBjYW5ub3QgY29udGFpbiB1bmRlcnNjb3Jlcy4gVXNlIGh5cGhlbnMgaW5zdGVhZC5gXG4gICAgICAgICk7XG4gICAgICAgIGhhc0Vycm9yID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAobmFtZS5sZW5ndGggPiBtYXhMZW5ndGgpIHtcbiAgICAgIEFubm90YXRpb25zLm9mKG5vZGUpLmFkZEVycm9yKFxuICAgICAgICBgW1Jlc291cmNlUHJlZml4QXNwZWN0XSBcIiR7bmFtZX1cIiAoJHtuYW1lLmxlbmd0aH0gY2hhcnMpIGV4Y2VlZHMgdGhlIGAgK1xuICAgICAgICAgIGBtYXhpbXVtIGFsbG93ZWQgbGVuZ3RoIG9mICR7bWF4TGVuZ3RofSBmb3IgJHtjZm5SZXNvdXJjZVR5cGV9LiBgICtcbiAgICAgICAgICBgU2hvcnRlbiB0aGUgcmVzb3VyY2UgYmFzZSBuYW1lIG9yIHlvdXIgcHJlZml4IChcIiR7dGhpcy5wcmVmaXh9XCIpLmBcbiAgICAgICk7XG4gICAgICBoYXNFcnJvciA9IHRydWU7XG4gICAgfVxuXG4gICAgcmV0dXJuIGhhc0Vycm9yO1xuICB9XG59XG4iXX0=
|