@fjall/components-infrastructure 0.78.6 → 0.79.1

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.
@@ -1,278 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LoadBalancer = exports.LoadBalancerFactory = void 0;
4
- const constructs_1 = require("constructs");
5
- const aws_elasticloadbalancingv2_1 = require("aws-cdk-lib/aws-elasticloadbalancingv2");
6
- const aws_certificatemanager_1 = require("aws-cdk-lib/aws-certificatemanager");
7
- const aws_route53_1 = require("aws-cdk-lib/aws-route53");
8
- const aws_route53_targets_1 = require("aws-cdk-lib/aws-route53-targets");
9
- const aws_ec2_1 = require("aws-cdk-lib/aws-ec2");
10
- const aws_cdk_lib_1 = require("aws-cdk-lib");
11
- const cfnOutput_1 = require("../../resources/aws/utilities/cfnOutput");
12
- const hostedZone_1 = require("./hostedZone");
13
- /**
14
- * Validates load balancer props and logs warnings for misconfigured options.
15
- */
16
- function validateLoadBalancerProps(props) {
17
- if (props.healthCheck) {
18
- if (props.healthCheck.interval !== undefined) {
19
- if (props.healthCheck.interval < 5 || props.healthCheck.interval > 300) {
20
- throw new Error("Health check interval must be between 5 and 300 seconds.");
21
- }
22
- }
23
- if (props.healthCheck.timeout !== undefined) {
24
- if (props.healthCheck.timeout < 2 || props.healthCheck.timeout > 120) {
25
- throw new Error("Health check timeout must be between 2 and 120 seconds.");
26
- }
27
- }
28
- }
29
- }
30
- /**
31
- * Factory for creating LoadBalancer constructs.
32
- *
33
- * @example
34
- * // Simple HTTP load balancer
35
- * const alb = app.addCompute(
36
- * LoadBalancerFactory.build("MainALB", {})
37
- * );
38
- *
39
- * @example
40
- * // HTTPS load balancer with domain
41
- * const alb = app.addCompute(
42
- * LoadBalancerFactory.build("MainALB", {
43
- * domain: "app.example.com"
44
- * })
45
- * );
46
- *
47
- * @example
48
- * // Internal load balancer
49
- * const alb = app.addCompute(
50
- * LoadBalancerFactory.build("InternalALB", {
51
- * public: false
52
- * })
53
- * );
54
- */
55
- class LoadBalancerFactory {
56
- static build(id, props) {
57
- return (app, scope) => {
58
- validateLoadBalancerProps(props);
59
- const resolvedProps = {
60
- ...props,
61
- vpc: props.vpc || app.getVpc()
62
- };
63
- return new LoadBalancer(scope, id, resolvedProps);
64
- };
65
- }
66
- }
67
- exports.LoadBalancerFactory = LoadBalancerFactory;
68
- /**
69
- * Application Load Balancer construct that can be shared across multiple ECS services.
70
- *
71
- * Use LoadBalancerFactory.build() to create instances via app.addCompute().
72
- */
73
- class LoadBalancer extends constructs_1.Construct {
74
- constructor(scope, id, props) {
75
- super(scope, id);
76
- this.defaultHealthCheck = props.healthCheck || {};
77
- this.isHttps = !!props.domain;
78
- // Create security group
79
- this.securityGroup = new aws_ec2_1.SecurityGroup(this, "SecurityGroup", {
80
- vpc: props.vpc,
81
- description: `Security group for ${id} load balancer`,
82
- allowAllOutbound: true
83
- });
84
- // Allow inbound traffic
85
- const inboundPort = this.isHttps ? 443 : 80;
86
- this.securityGroup.addIngressRule({ connections: { allowFromAnyIpv4: () => { } } }, aws_ec2_1.Port.tcp(inboundPort), `Allow inbound ${this.isHttps ? "HTTPS" : "HTTP"}`);
87
- // Create ALB
88
- const loadBalancerName = this.truncateName(`${id}ALB`, 32);
89
- this.alb = new aws_elasticloadbalancingv2_1.ApplicationLoadBalancer(this, "ALB", {
90
- vpc: props.vpc,
91
- internetFacing: props.public !== false,
92
- securityGroup: this.securityGroup,
93
- loadBalancerName,
94
- vpcSubnets: {
95
- subnetType: props.public !== false ? aws_ec2_1.SubnetType.PUBLIC : aws_ec2_1.SubnetType.PRIVATE_WITH_EGRESS
96
- }
97
- });
98
- // Set up domain if configured
99
- if (props.domain) {
100
- const domainConfig = this.normaliseDomainConfig(props.domain);
101
- this.setupDomain(domainConfig);
102
- }
103
- // Create listener
104
- if (this.isHttps && this.certificate) {
105
- this.listener = this.alb.addListener("Listener", {
106
- port: 443,
107
- protocol: aws_elasticloadbalancingv2_1.ApplicationProtocol.HTTPS,
108
- certificates: [this.certificate]
109
- });
110
- // Add HTTP redirect
111
- this.alb.addListener("HttpRedirect", {
112
- port: 80,
113
- protocol: aws_elasticloadbalancingv2_1.ApplicationProtocol.HTTP,
114
- defaultAction: {
115
- redirect: {
116
- protocol: "HTTPS",
117
- port: "443",
118
- permanent: true
119
- }
120
- }
121
- });
122
- }
123
- else {
124
- this.listener = this.alb.addListener("Listener", {
125
- port: 80,
126
- protocol: aws_elasticloadbalancingv2_1.ApplicationProtocol.HTTP
127
- });
128
- }
129
- // Add default action (returns 503 if no targets match)
130
- this.listener.addAction("DefaultAction", {
131
- action: {
132
- fixedResponse: {
133
- statusCode: 503,
134
- contentType: "text/plain",
135
- messageBody: "No service available"
136
- }
137
- }
138
- });
139
- // Set URL
140
- this.url = this.isHttps
141
- ? `https://${this.alb.loadBalancerDnsName}`
142
- : `http://${this.alb.loadBalancerDnsName}`;
143
- // Set up connections
144
- this.connections = new aws_ec2_1.Connections({
145
- securityGroups: [this.securityGroup],
146
- defaultPort: aws_ec2_1.Port.tcp(inboundPort)
147
- });
148
- // Outputs
149
- new cfnOutput_1.CfnOutput(this, "LoadBalancerDnsName", {
150
- key: `${id}LoadBalancerDnsName`,
151
- exportName: `${id}LoadBalancerDnsName`,
152
- value: this.alb.loadBalancerDnsName
153
- });
154
- new cfnOutput_1.CfnOutput(this, "LoadBalancerUrl", {
155
- key: `${id}LoadBalancerUrl`,
156
- exportName: `${id}LoadBalancerUrl`,
157
- value: this.url,
158
- description: `Load Balancer URL for ${id}`
159
- });
160
- new cfnOutput_1.CfnOutput(this, "LoadBalancerArn", {
161
- key: `${id}LoadBalancerArn`,
162
- exportName: `${id}LoadBalancerArn`,
163
- value: this.alb.loadBalancerArn,
164
- description: `Load Balancer ARN for ${id}`
165
- });
166
- }
167
- /**
168
- * Register a target group with routing rules.
169
- * Called internally by EcsCluster when loadBalancer.target is specified.
170
- *
171
- * @param targetGroup - The target group to register
172
- * @param routing - Routing configuration (path, host, priority)
173
- */
174
- registerTargetGroup(targetGroup, routing) {
175
- const conditions = [];
176
- if (routing.path) {
177
- const paths = Array.isArray(routing.path) ? routing.path : [routing.path];
178
- conditions.push(aws_elasticloadbalancingv2_1.ListenerCondition.pathPatterns(paths));
179
- }
180
- if (routing.host) {
181
- const hosts = Array.isArray(routing.host) ? routing.host : [routing.host];
182
- conditions.push(aws_elasticloadbalancingv2_1.ListenerCondition.hostHeaders(hosts));
183
- }
184
- if (conditions.length === 0) {
185
- throw new Error("At least one routing condition (path or host) must be specified.");
186
- }
187
- this.listener.addTargetGroups(`Rule${routing.priority}`, {
188
- targetGroups: [targetGroup],
189
- conditions,
190
- priority: routing.priority
191
- });
192
- }
193
- /**
194
- * Create a target group for an ECS service.
195
- * Returns the target group for the service to register with.
196
- *
197
- * @param id - Unique identifier for the target group
198
- * @param port - Container port
199
- * @param vpc - VPC for the target group
200
- * @param healthCheckPath - Optional health check path override
201
- */
202
- createTargetGroup(id, port, vpc, healthCheckPath) {
203
- const targetGroup = new aws_elasticloadbalancingv2_1.ApplicationTargetGroup(this, `${id}TargetGroup`, {
204
- vpc,
205
- port,
206
- protocol: aws_elasticloadbalancingv2_1.ApplicationProtocol.HTTP,
207
- targetType: aws_elasticloadbalancingv2_1.TargetType.IP,
208
- healthCheck: {
209
- path: healthCheckPath || this.defaultHealthCheck.path || "/",
210
- interval: aws_cdk_lib_1.Duration.seconds(this.defaultHealthCheck.interval || 30),
211
- timeout: aws_cdk_lib_1.Duration.seconds(this.defaultHealthCheck.timeout || 5),
212
- healthyThresholdCount: this.defaultHealthCheck.healthyThreshold || 2,
213
- unhealthyThresholdCount: this.defaultHealthCheck.unhealthyThreshold || 3
214
- }
215
- });
216
- return targetGroup;
217
- }
218
- /**
219
- * Allow traffic from this load balancer to a connectable resource.
220
- */
221
- allowTo(connectable, port) {
222
- this.connections.allowTo(connectable, port || connectable.connections.defaultPort || aws_ec2_1.Port.allTcp());
223
- }
224
- normaliseDomainConfig(domain) {
225
- if (typeof domain === "string") {
226
- return { name: domain };
227
- }
228
- return domain;
229
- }
230
- setupDomain(config) {
231
- // Get or create hosted zone
232
- if (config.hostedZone) {
233
- this.hostedZone = config.hostedZone.getInternalHostedZone();
234
- }
235
- else {
236
- const hostedZone = new hostedZone_1.HostedZone(this, "HostedZone", {
237
- zoneName: config.name
238
- });
239
- this.hostedZone = hostedZone.getInternalHostedZone();
240
- }
241
- // Get or create certificate
242
- if (config.certificate) {
243
- this.certificate = config.certificate;
244
- }
245
- else {
246
- this.certificate = new aws_certificatemanager_1.Certificate(this, "Certificate", {
247
- domainName: config.name,
248
- validation: aws_certificatemanager_1.CertificateValidation.fromDns(this.hostedZone)
249
- });
250
- }
251
- // Create DNS record pointing to ALB
252
- new aws_route53_1.ARecord(this, "AliasRecord", {
253
- zone: this.hostedZone,
254
- recordName: config.name,
255
- target: aws_route53_1.RecordTarget.fromAlias(new aws_route53_targets_1.LoadBalancerTarget(this.alb))
256
- });
257
- }
258
- truncateName(name, maxLength) {
259
- if (name.length <= maxLength) {
260
- return name;
261
- }
262
- return name.substring(0, maxLength).replace(/-+$/, "");
263
- }
264
- /**
265
- * Get the DNS name of the load balancer.
266
- */
267
- getDnsName() {
268
- return this.alb.loadBalancerDnsName;
269
- }
270
- /**
271
- * Get the ARN of the load balancer.
272
- */
273
- getArn() {
274
- return this.alb.loadBalancerArn;
275
- }
276
- }
277
- exports.LoadBalancer = LoadBalancer;
278
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZEJhbGFuY2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vbGliL3BhdHRlcm5zL2F3cy9sb2FkQmFsYW5jZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsMkNBQXVDO0FBQ3ZDLHVGQVFnRDtBQUNoRCwrRUFHNEM7QUFDNUMseURBQWtGO0FBQ2xGLHlFQUFxRTtBQUNyRSxpREFPNkI7QUFDN0IsNkNBQXVDO0FBR3ZDLHVFQUFvRTtBQUNwRSw2Q0FBNkQ7QUFxRjdEOztHQUVHO0FBQ0gsU0FBUyx5QkFBeUIsQ0FBQyxLQUF5QjtJQUMxRCxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUN0QixJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUMsUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzdDLElBQUksS0FBSyxDQUFDLFdBQVcsQ0FBQyxRQUFRLEdBQUcsQ0FBQyxJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUMsUUFBUSxHQUFHLEdBQUcsRUFBRSxDQUFDO2dCQUN2RSxNQUFNLElBQUksS0FBSyxDQUNiLDBEQUEwRCxDQUMzRCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7UUFDRCxJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUMsT0FBTyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzVDLElBQUksS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUFPLEdBQUcsQ0FBQyxJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUMsT0FBTyxHQUFHLEdBQUcsRUFBRSxDQUFDO2dCQUNyRSxNQUFNLElBQUksS0FBSyxDQUNiLHlEQUF5RCxDQUMxRCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F3Qkc7QUFDSCxNQUFhLG1CQUFtQjtJQUM5QixNQUFNLENBQUMsS0FBSyxDQUFDLEVBQVUsRUFBRSxLQUF5QjtRQUNoRCxPQUFPLENBQUMsR0FBUSxFQUFFLEtBQWdCLEVBQWdCLEVBQUU7WUFDbEQseUJBQXlCLENBQUMsS0FBSyxDQUFDLENBQUM7WUFFakMsTUFBTSxhQUFhLEdBQThCO2dCQUMvQyxHQUFHLEtBQUs7Z0JBQ1IsR0FBRyxFQUFFLEtBQUssQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLE1BQU0sRUFBRTthQUMvQixDQUFDO1lBRUYsT0FBTyxJQUFJLFlBQVksQ0FBQyxLQUFLLEVBQUUsRUFBRSxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBQ3BELENBQUMsQ0FBQztJQUNKLENBQUM7Q0FDRjtBQWJELGtEQWFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQWEsWUFBYSxTQUFRLHNCQUFTO0lBWXpDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBZ0M7UUFDeEUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixJQUFJLENBQUMsa0JBQWtCLEdBQUcsS0FBSyxDQUFDLFdBQVcsSUFBSSxFQUFFLENBQUM7UUFDbEQsSUFBSSxDQUFDLE9BQU8sR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQztRQUU5Qix3QkFBd0I7UUFDeEIsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFJLHVCQUFhLENBQUMsSUFBSSxFQUFFLGVBQWUsRUFBRTtZQUM1RCxHQUFHLEVBQUUsS0FBSyxDQUFDLEdBQUc7WUFDZCxXQUFXLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCO1lBQ3JELGdCQUFnQixFQUFFLElBQUk7U0FDdkIsQ0FBQyxDQUFDO1FBRUgsd0JBQXdCO1FBQ3hCLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzVDLElBQUksQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUMvQixFQUFFLFdBQVcsRUFBRSxFQUFFLGdCQUFnQixFQUFFLEdBQUcsRUFBRSxHQUFFLENBQUMsRUFBRSxFQUFTLEVBQ3RELGNBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLEVBQ3JCLGlCQUFpQixJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUNuRCxDQUFDO1FBRUYsYUFBYTtRQUNiLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzNELElBQUksQ0FBQyxHQUFHLEdBQUcsSUFBSSxvREFBdUIsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFO1lBQ2xELEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRztZQUNkLGNBQWMsRUFBRSxLQUFLLENBQUMsTUFBTSxLQUFLLEtBQUs7WUFDdEMsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFhO1lBQ2pDLGdCQUFnQjtZQUNoQixVQUFVLEVBQUU7Z0JBQ1YsVUFBVSxFQUNSLEtBQUssQ0FBQyxNQUFNLEtBQUssS0FBSyxDQUFDLENBQUMsQ0FBQyxvQkFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsb0JBQVUsQ0FBQyxtQkFBbUI7YUFDOUU7U0FDRixDQUFDLENBQUM7UUFFSCw4QkFBOEI7UUFDOUIsSUFBSSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDakIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM5RCxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ2pDLENBQUM7UUFFRCxrQkFBa0I7UUFDbEIsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNyQyxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRTtnQkFDL0MsSUFBSSxFQUFFLEdBQUc7Z0JBQ1QsUUFBUSxFQUFFLGdEQUFtQixDQUFDLEtBQUs7Z0JBQ25DLFlBQVksRUFBRSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUM7YUFDakMsQ0FBQyxDQUFDO1lBRUgsb0JBQW9CO1lBQ3BCLElBQUksQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLGNBQWMsRUFBRTtnQkFDbkMsSUFBSSxFQUFFLEVBQUU7Z0JBQ1IsUUFBUSxFQUFFLGdEQUFtQixDQUFDLElBQUk7Z0JBQ2xDLGFBQWEsRUFBRTtvQkFDYixRQUFRLEVBQUU7d0JBQ1IsUUFBUSxFQUFFLE9BQU87d0JBQ2pCLElBQUksRUFBRSxLQUFLO3dCQUNYLFNBQVMsRUFBRSxJQUFJO3FCQUNoQjtpQkFDSzthQUNULENBQUMsQ0FBQztRQUNMLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUU7Z0JBQy9DLElBQUksRUFBRSxFQUFFO2dCQUNSLFFBQVEsRUFBRSxnREFBbUIsQ0FBQyxJQUFJO2FBQ25DLENBQUMsQ0FBQztRQUNMLENBQUM7UUFFRCx1REFBdUQ7UUFDdkQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsZUFBZSxFQUFFO1lBQ3ZDLE1BQU0sRUFBRTtnQkFDTixhQUFhLEVBQUU7b0JBQ2IsVUFBVSxFQUFFLEdBQUc7b0JBQ2YsV0FBVyxFQUFFLFlBQVk7b0JBQ3pCLFdBQVcsRUFBRSxzQkFBc0I7aUJBQ3BDO2FBQ0s7U0FDVCxDQUFDLENBQUM7UUFFSCxVQUFVO1FBQ1YsSUFBSSxDQUFDLEdBQUcsR0FBRyxJQUFJLENBQUMsT0FBTztZQUNyQixDQUFDLENBQUMsV0FBVyxJQUFJLENBQUMsR0FBRyxDQUFDLG1CQUFtQixFQUFFO1lBQzNDLENBQUMsQ0FBQyxVQUFVLElBQUksQ0FBQyxHQUFHLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUU3QyxxQkFBcUI7UUFDckIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLHFCQUFXLENBQUM7WUFDakMsY0FBYyxFQUFFLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQztZQUNwQyxXQUFXLEVBQUUsY0FBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUM7U0FDbkMsQ0FBQyxDQUFDO1FBRUgsVUFBVTtRQUNWLElBQUkscUJBQVMsQ0FBQyxJQUFJLEVBQUUscUJBQXFCLEVBQUU7WUFDekMsR0FBRyxFQUFFLEdBQUcsRUFBRSxxQkFBcUI7WUFDL0IsVUFBVSxFQUFFLEdBQUcsRUFBRSxxQkFBcUI7WUFDdEMsS0FBSyxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsbUJBQW1CO1NBQ3BDLENBQUMsQ0FBQztRQUVILElBQUkscUJBQVMsQ0FBQyxJQUFJLEVBQUUsaUJBQWlCLEVBQUU7WUFDckMsR0FBRyxFQUFFLEdBQUcsRUFBRSxpQkFBaUI7WUFDM0IsVUFBVSxFQUFFLEdBQUcsRUFBRSxpQkFBaUI7WUFDbEMsS0FBSyxFQUFFLElBQUksQ0FBQyxHQUFHO1lBQ2YsV0FBVyxFQUFFLHlCQUF5QixFQUFFLEVBQUU7U0FDM0MsQ0FBQyxDQUFDO1FBRUgsSUFBSSxxQkFBUyxDQUFDLElBQUksRUFBRSxpQkFBaUIsRUFBRTtZQUNyQyxHQUFHLEVBQUUsR0FBRyxFQUFFLGlCQUFpQjtZQUMzQixVQUFVLEVBQUUsR0FBRyxFQUFFLGlCQUFpQjtZQUNsQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxlQUFlO1lBQy9CLFdBQVcsRUFBRSx5QkFBeUIsRUFBRSxFQUFFO1NBQzNDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSSxtQkFBbUIsQ0FDeEIsV0FBb0MsRUFDcEMsT0FBa0M7UUFFbEMsTUFBTSxVQUFVLEdBQXdCLEVBQUUsQ0FBQztRQUUzQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNqQixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDMUUsVUFBVSxDQUFDLElBQUksQ0FBQyw4Q0FBaUIsQ0FBQyxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN6RCxDQUFDO1FBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDakIsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzFFLFVBQVUsQ0FBQyxJQUFJLENBQUMsOENBQWlCLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDeEQsQ0FBQztRQUVELElBQUksVUFBVSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksS0FBSyxDQUNiLGtFQUFrRSxDQUNuRSxDQUFDO1FBQ0osQ0FBQztRQUVELElBQUksQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLE9BQU8sT0FBTyxDQUFDLFFBQVEsRUFBRSxFQUFFO1lBQ3ZELFlBQVksRUFBRSxDQUFDLFdBQVcsQ0FBQztZQUMzQixVQUFVO1lBQ1YsUUFBUSxFQUFFLE9BQU8sQ0FBQyxRQUFRO1NBQzNCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7Ozs7Ozs7T0FRRztJQUNJLGlCQUFpQixDQUN0QixFQUFVLEVBQ1YsSUFBWSxFQUNaLEdBQVMsRUFDVCxlQUF3QjtRQUV4QixNQUFNLFdBQVcsR0FBRyxJQUFJLG1EQUFzQixDQUFDLElBQUksRUFBRSxHQUFHLEVBQUUsYUFBYSxFQUFFO1lBQ3ZFLEdBQUc7WUFDSCxJQUFJO1lBQ0osUUFBUSxFQUFFLGdEQUFtQixDQUFDLElBQUk7WUFDbEMsVUFBVSxFQUFFLHVDQUFVLENBQUMsRUFBRTtZQUN6QixXQUFXLEVBQUU7Z0JBQ1gsSUFBSSxFQUFFLGVBQWUsSUFBSSxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxJQUFJLEdBQUc7Z0JBQzVELFFBQVEsRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FDeEIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFFBQVEsSUFBSSxFQUFFLENBQ3ZDO2dCQUNELE9BQU8sRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FDdkIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sSUFBSSxDQUFDLENBQ3JDO2dCQUNELHFCQUFxQixFQUFFLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDO2dCQUNwRSx1QkFBdUIsRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsa0JBQWtCLElBQUksQ0FBQzthQUN6RTtTQUNGLENBQUMsQ0FBQztRQUVILE9BQU8sV0FBVyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7T0FFRztJQUNJLE9BQU8sQ0FBQyxXQUF5QixFQUFFLElBQVc7UUFDbkQsSUFBSSxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQ3RCLFdBQVcsRUFDWCxJQUFJLElBQUksV0FBVyxDQUFDLFdBQVcsQ0FBQyxXQUFXLElBQUksY0FBSSxDQUFDLE1BQU0sRUFBRSxDQUM3RCxDQUFDO0lBQ0osQ0FBQztJQUVPLHFCQUFxQixDQUMzQixNQUF5QztRQUV6QyxJQUFJLE9BQU8sTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQy9CLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7UUFDMUIsQ0FBQztRQUNELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFTyxXQUFXLENBQUMsTUFBZ0M7UUFDbEQsNEJBQTRCO1FBQzVCLElBQUksTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ3JCLElBQVksQ0FBQyxVQUFVLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQ3ZFLENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxVQUFVLEdBQUcsSUFBSSx1QkFBZSxDQUFDLElBQUksRUFBRSxZQUFZLEVBQUU7Z0JBQ3pELFFBQVEsRUFBRSxNQUFNLENBQUMsSUFBSTthQUN0QixDQUFDLENBQUM7WUFDRixJQUFZLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQ2hFLENBQUM7UUFFRCw0QkFBNEI7UUFDNUIsSUFBSSxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDdEIsSUFBWSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUMsV0FBVyxDQUFDO1FBQ2pELENBQUM7YUFBTSxDQUFDO1lBQ0wsSUFBWSxDQUFDLFdBQVcsR0FBRyxJQUFJLG9DQUFXLENBQUMsSUFBSSxFQUFFLGFBQWEsRUFBRTtnQkFDL0QsVUFBVSxFQUFFLE1BQU0sQ0FBQyxJQUFJO2dCQUN2QixVQUFVLEVBQUUsOENBQXFCLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUM7YUFDM0QsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELG9DQUFvQztRQUNwQyxJQUFJLHFCQUFPLENBQUMsSUFBSSxFQUFFLGFBQWEsRUFBRTtZQUMvQixJQUFJLEVBQUUsSUFBSSxDQUFDLFVBQVc7WUFDdEIsVUFBVSxFQUFFLE1BQU0sQ0FBQyxJQUFJO1lBQ3ZCLE1BQU0sRUFBRSwwQkFBWSxDQUFDLFNBQVMsQ0FBQyxJQUFJLHdDQUFrQixDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUNqRSxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU8sWUFBWSxDQUFDLElBQVksRUFBRSxTQUFpQjtRQUNsRCxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUksU0FBUyxFQUFFLENBQUM7WUFDN0IsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ3pELENBQUM7SUFFRDs7T0FFRztJQUNJLFVBQVU7UUFDZixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7SUFDdEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksTUFBTTtRQUNYLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUM7SUFDbEMsQ0FBQztDQUNGO0FBdlFELG9DQXVRQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5pbXBvcnQge1xuICBBcHBsaWNhdGlvbkxvYWRCYWxhbmNlcixcbiAgQXBwbGljYXRpb25MaXN0ZW5lcixcbiAgQXBwbGljYXRpb25Qcm90b2NvbCxcbiAgQXBwbGljYXRpb25UYXJnZXRHcm91cCxcbiAgTGlzdGVuZXJDb25kaXRpb24sXG4gIFRhcmdldFR5cGUsXG4gIHR5cGUgSUFwcGxpY2F0aW9uVGFyZ2V0R3JvdXBcbn0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1lbGFzdGljbG9hZGJhbGFuY2luZ3YyXCI7XG5pbXBvcnQge1xuICBDZXJ0aWZpY2F0ZSxcbiAgQ2VydGlmaWNhdGVWYWxpZGF0aW9uXG59IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2VydGlmaWNhdGVtYW5hZ2VyXCI7XG5pbXBvcnQgeyBBUmVjb3JkLCBSZWNvcmRUYXJnZXQsIHR5cGUgSUhvc3RlZFpvbmUgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcbmltcG9ydCB7IExvYWRCYWxhbmNlclRhcmdldCB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3Mtcm91dGU1My10YXJnZXRzXCI7XG5pbXBvcnQge1xuICBDb25uZWN0aW9ucyxcbiAgdHlwZSBJQ29ubmVjdGFibGUsXG4gIHR5cGUgSVZwYyxcbiAgUG9ydCxcbiAgU2VjdXJpdHlHcm91cCxcbiAgU3VibmV0VHlwZVxufSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWVjMlwiO1xuaW1wb3J0IHsgRHVyYXRpb24gfSBmcm9tIFwiYXdzLWNkay1saWJcIjtcblxuaW1wb3J0IHR5cGUgQXBwIGZyb20gXCIuLi8uLi9hcHBcIjtcbmltcG9ydCB7IENmbk91dHB1dCB9IGZyb20gXCIuLi8uLi9yZXNvdXJjZXMvYXdzL3V0aWxpdGllcy9jZm5PdXRwdXRcIjtcbmltcG9ydCB7IEhvc3RlZFpvbmUgYXMgRmphbGxIb3N0ZWRab25lIH0gZnJvbSBcIi4vaG9zdGVkWm9uZVwiO1xuXG4vKipcbiAqIFJvdXRpbmcgY29uZmlndXJhdGlvbiBmb3IgcmVnaXN0ZXJpbmcgc2VydmljZXMgd2l0aCB0aGUgbG9hZCBiYWxhbmNlci5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBMb2FkQmFsYW5jZXJSb3V0aW5nQ29uZmlnIHtcbiAgLyoqXG4gICAqIFBhdGggcGF0dGVybihzKSB0byBtYXRjaC4gRXhhbXBsZXM6IFwiL2FwaS8qXCIsIFtcIi9hcGkvKlwiLCBcIi92MS8qXCJdXG4gICAqL1xuICBwYXRoPzogc3RyaW5nIHwgc3RyaW5nW107XG4gIC8qKlxuICAgKiBIb3N0IHBhdHRlcm4ocykgdG8gbWF0Y2guIEV4YW1wbGVzOiBcImFwaS5leGFtcGxlLmNvbVwiLCBbXCJhcGkuZXhhbXBsZS5jb21cIiwgXCJ3d3cuZXhhbXBsZS5jb21cIl1cbiAgICovXG4gIGhvc3Q/OiBzdHJpbmcgfCBzdHJpbmdbXTtcbiAgLyoqXG4gICAqIFByaW9yaXR5IGZvciB0aGlzIHJvdXRpbmcgcnVsZS4gTG93ZXIgbnVtYmVycyA9IGhpZ2hlciBwcmlvcml0eS5cbiAgICogTXVzdCBiZSB1bmlxdWUgcGVyIGxpc3RlbmVyLiBSYW5nZTogMS01MDAwMC5cbiAgICovXG4gIHByaW9yaXR5OiBudW1iZXI7XG4gIC8qKlxuICAgKiBIZWFsdGggY2hlY2sgcGF0aCBmb3IgdGhpcyB0YXJnZXQgZ3JvdXAuIERlZmF1bHQ6IFwiL1wiXG4gICAqL1xuICBoZWFsdGhDaGVja1BhdGg/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogSGVhbHRoIGNoZWNrIGNvbmZpZ3VyYXRpb24gZm9yIHRoZSBsb2FkIGJhbGFuY2VyLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIExvYWRCYWxhbmNlckhlYWx0aENoZWNrQ29uZmlnIHtcbiAgLyoqIEhlYWx0aCBjaGVjayBwYXRoLiBEZWZhdWx0OiBcIi9cIiAqL1xuICBwYXRoPzogc3RyaW5nO1xuICAvKiogSW50ZXJ2YWwgYmV0d2VlbiBoZWFsdGggY2hlY2tzIGluIHNlY29uZHMuIERlZmF1bHQ6IDMwICovXG4gIGludGVydmFsPzogbnVtYmVyO1xuICAvKiogSGVhbHRoIGNoZWNrIHRpbWVvdXQgaW4gc2Vjb25kcy4gRGVmYXVsdDogNSAqL1xuICB0aW1lb3V0PzogbnVtYmVyO1xuICAvKiogTnVtYmVyIG9mIGNvbnNlY3V0aXZlIHN1Y2Nlc3NlcyBiZWZvcmUgaGVhbHRoeS4gRGVmYXVsdDogMiAqL1xuICBoZWFsdGh5VGhyZXNob2xkPzogbnVtYmVyO1xuICAvKiogTnVtYmVyIG9mIGNvbnNlY3V0aXZlIGZhaWx1cmVzIGJlZm9yZSB1bmhlYWx0aHkuIERlZmF1bHQ6IDMgKi9cbiAgdW5oZWFsdGh5VGhyZXNob2xkPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIERvbWFpbiBjb25maWd1cmF0aW9uIGZvciBIVFRQUyBhbmQgRE5TLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIExvYWRCYWxhbmNlckRvbWFpbkNvbmZpZyB7XG4gIC8qKiBEb21haW4gbmFtZSAoZS5nLiwgXCJhcGkuZXhhbXBsZS5jb21cIikgKi9cbiAgbmFtZTogc3RyaW5nO1xuICAvKiogRXhpc3RpbmcgaG9zdGVkIHpvbmUuIElmIG9taXR0ZWQsIGNyZWF0ZXMgbmV3IG9uZS4gKi9cbiAgaG9zdGVkWm9uZT86IEZqYWxsSG9zdGVkWm9uZTtcbiAgLyoqIEV4aXN0aW5nIGNlcnRpZmljYXRlLiBJZiBvbWl0dGVkLCBjcmVhdGVzIG5ldyBvbmUgd2l0aCBETlMgdmFsaWRhdGlvbi4gKi9cbiAgY2VydGlmaWNhdGU/OiBDZXJ0aWZpY2F0ZTtcbn1cblxuLyoqXG4gKiBQcm9wZXJ0aWVzIGZvciBMb2FkQmFsYW5jZXJGYWN0b3J5LmJ1aWxkKClcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBJTG9hZEJhbGFuY2VyUHJvcHMge1xuICAvKiogVlBDIHRvIHBsYWNlIHRoZSBsb2FkIGJhbGFuY2VyIGluLiBEZWZhdWx0OiBhcHAncyBkZWZhdWx0IFZQQyAqL1xuICB2cGM/OiBJVnBjO1xuICAvKipcbiAgICogRG9tYWluIGNvbmZpZ3VyYXRpb24gZm9yIEhUVFBTLlxuICAgKiAtIHN0cmluZzogRG9tYWluIG5hbWUgKGNyZWF0ZXMgaG9zdGVkIHpvbmUgYW5kIGNlcnRpZmljYXRlKVxuICAgKiAtIExvYWRCYWxhbmNlckRvbWFpbkNvbmZpZzogRnVsbCBjb25maWd1cmF0aW9uXG4gICAqIC0gT21pdDogSFRUUCBvbmx5XG4gICAqL1xuICBkb21haW4/OiBzdHJpbmcgfCBMb2FkQmFsYW5jZXJEb21haW5Db25maWc7XG4gIC8qKlxuICAgKiBXaGV0aGVyIHRoZSBsb2FkIGJhbGFuY2VyIGlzIGludGVybmV0LWZhY2luZy5cbiAgICogRGVmYXVsdDogdHJ1ZVxuICAgKi9cbiAgcHVibGljPzogYm9vbGVhbjtcbiAgLyoqXG4gICAqIERlZmF1bHQgaGVhbHRoIGNoZWNrIGNvbmZpZ3VyYXRpb24uXG4gICAqIEluZGl2aWR1YWwgc2VydmljZXMgY2FuIG92ZXJyaWRlIHRoaXMuXG4gICAqL1xuICBoZWFsdGhDaGVjaz86IExvYWRCYWxhbmNlckhlYWx0aENoZWNrQ29uZmlnO1xufVxuXG4vKipcbiAqIEludGVybmFsIHByb3BzIHdpdGggcmVzb2x2ZWQgVlBDLlxuICovXG5pbnRlcmZhY2UgTG9hZEJhbGFuY2VyUHJvcHNSZXNvbHZlZCBleHRlbmRzIElMb2FkQmFsYW5jZXJQcm9wcyB7XG4gIHZwYzogSVZwYztcbn1cblxuLyoqXG4gKiBWYWxpZGF0ZXMgbG9hZCBiYWxhbmNlciBwcm9wcyBhbmQgbG9ncyB3YXJuaW5ncyBmb3IgbWlzY29uZmlndXJlZCBvcHRpb25zLlxuICovXG5mdW5jdGlvbiB2YWxpZGF0ZUxvYWRCYWxhbmNlclByb3BzKHByb3BzOiBJTG9hZEJhbGFuY2VyUHJvcHMpOiB2b2lkIHtcbiAgaWYgKHByb3BzLmhlYWx0aENoZWNrKSB7XG4gICAgaWYgKHByb3BzLmhlYWx0aENoZWNrLmludGVydmFsICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmIChwcm9wcy5oZWFsdGhDaGVjay5pbnRlcnZhbCA8IDUgfHwgcHJvcHMuaGVhbHRoQ2hlY2suaW50ZXJ2YWwgPiAzMDApIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgIFwiSGVhbHRoIGNoZWNrIGludGVydmFsIG11c3QgYmUgYmV0d2VlbiA1IGFuZCAzMDAgc2Vjb25kcy5cIlxuICAgICAgICApO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAocHJvcHMuaGVhbHRoQ2hlY2sudGltZW91dCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBpZiAocHJvcHMuaGVhbHRoQ2hlY2sudGltZW91dCA8IDIgfHwgcHJvcHMuaGVhbHRoQ2hlY2sudGltZW91dCA+IDEyMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgXCJIZWFsdGggY2hlY2sgdGltZW91dCBtdXN0IGJlIGJldHdlZW4gMiBhbmQgMTIwIHNlY29uZHMuXCJcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLyoqXG4gKiBGYWN0b3J5IGZvciBjcmVhdGluZyBMb2FkQmFsYW5jZXIgY29uc3RydWN0cy5cbiAqXG4gKiBAZXhhbXBsZVxuICogLy8gU2ltcGxlIEhUVFAgbG9hZCBiYWxhbmNlclxuICogY29uc3QgYWxiID0gYXBwLmFkZENvbXB1dGUoXG4gKiAgIExvYWRCYWxhbmNlckZhY3RvcnkuYnVpbGQoXCJNYWluQUxCXCIsIHt9KVxuICogKTtcbiAqXG4gKiBAZXhhbXBsZVxuICogLy8gSFRUUFMgbG9hZCBiYWxhbmNlciB3aXRoIGRvbWFpblxuICogY29uc3QgYWxiID0gYXBwLmFkZENvbXB1dGUoXG4gKiAgIExvYWRCYWxhbmNlckZhY3RvcnkuYnVpbGQoXCJNYWluQUxCXCIsIHtcbiAqICAgICBkb21haW46IFwiYXBwLmV4YW1wbGUuY29tXCJcbiAqICAgfSlcbiAqICk7XG4gKlxuICogQGV4YW1wbGVcbiAqIC8vIEludGVybmFsIGxvYWQgYmFsYW5jZXJcbiAqIGNvbnN0IGFsYiA9IGFwcC5hZGRDb21wdXRlKFxuICogICBMb2FkQmFsYW5jZXJGYWN0b3J5LmJ1aWxkKFwiSW50ZXJuYWxBTEJcIiwge1xuICogICAgIHB1YmxpYzogZmFsc2VcbiAqICAgfSlcbiAqICk7XG4gKi9cbmV4cG9ydCBjbGFzcyBMb2FkQmFsYW5jZXJGYWN0b3J5IHtcbiAgc3RhdGljIGJ1aWxkKGlkOiBzdHJpbmcsIHByb3BzOiBJTG9hZEJhbGFuY2VyUHJvcHMpIHtcbiAgICByZXR1cm4gKGFwcDogQXBwLCBzY29wZTogQ29uc3RydWN0KTogTG9hZEJhbGFuY2VyID0+IHtcbiAgICAgIHZhbGlkYXRlTG9hZEJhbGFuY2VyUHJvcHMocHJvcHMpO1xuXG4gICAgICBjb25zdCByZXNvbHZlZFByb3BzOiBMb2FkQmFsYW5jZXJQcm9wc1Jlc29sdmVkID0ge1xuICAgICAgICAuLi5wcm9wcyxcbiAgICAgICAgdnBjOiBwcm9wcy52cGMgfHwgYXBwLmdldFZwYygpXG4gICAgICB9O1xuXG4gICAgICByZXR1cm4gbmV3IExvYWRCYWxhbmNlcihzY29wZSwgaWQsIHJlc29sdmVkUHJvcHMpO1xuICAgIH07XG4gIH1cbn1cblxuLyoqXG4gKiBBcHBsaWNhdGlvbiBMb2FkIEJhbGFuY2VyIGNvbnN0cnVjdCB0aGF0IGNhbiBiZSBzaGFyZWQgYWNyb3NzIG11bHRpcGxlIEVDUyBzZXJ2aWNlcy5cbiAqXG4gKiBVc2UgTG9hZEJhbGFuY2VyRmFjdG9yeS5idWlsZCgpIHRvIGNyZWF0ZSBpbnN0YW5jZXMgdmlhIGFwcC5hZGRDb21wdXRlKCkuXG4gKi9cbmV4cG9ydCBjbGFzcyBMb2FkQmFsYW5jZXIgZXh0ZW5kcyBDb25zdHJ1Y3QgaW1wbGVtZW50cyBJQ29ubmVjdGFibGUge1xuICBwdWJsaWMgcmVhZG9ubHkgYWxiOiBBcHBsaWNhdGlvbkxvYWRCYWxhbmNlcjtcbiAgcHVibGljIHJlYWRvbmx5IGxpc3RlbmVyOiBBcHBsaWNhdGlvbkxpc3RlbmVyO1xuICBwdWJsaWMgcmVhZG9ubHkgc2VjdXJpdHlHcm91cDogU2VjdXJpdHlHcm91cDtcbiAgcHVibGljIHJlYWRvbmx5IHVybDogc3RyaW5nO1xuICBwdWJsaWMgcmVhZG9ubHkgY29ubmVjdGlvbnM6IENvbm5lY3Rpb25zO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgaG9zdGVkWm9uZT86IElIb3N0ZWRab25lO1xuICBwcml2YXRlIHJlYWRvbmx5IGNlcnRpZmljYXRlPzogQ2VydGlmaWNhdGU7XG4gIHByaXZhdGUgcmVhZG9ubHkgZGVmYXVsdEhlYWx0aENoZWNrOiBMb2FkQmFsYW5jZXJIZWFsdGhDaGVja0NvbmZpZztcbiAgcHJpdmF0ZSByZWFkb25seSBpc0h0dHBzOiBib29sZWFuO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBMb2FkQmFsYW5jZXJQcm9wc1Jlc29sdmVkKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIHRoaXMuZGVmYXVsdEhlYWx0aENoZWNrID0gcHJvcHMuaGVhbHRoQ2hlY2sgfHwge307XG4gICAgdGhpcy5pc0h0dHBzID0gISFwcm9wcy5kb21haW47XG5cbiAgICAvLyBDcmVhdGUgc2VjdXJpdHkgZ3JvdXBcbiAgICB0aGlzLnNlY3VyaXR5R3JvdXAgPSBuZXcgU2VjdXJpdHlHcm91cCh0aGlzLCBcIlNlY3VyaXR5R3JvdXBcIiwge1xuICAgICAgdnBjOiBwcm9wcy52cGMsXG4gICAgICBkZXNjcmlwdGlvbjogYFNlY3VyaXR5IGdyb3VwIGZvciAke2lkfSBsb2FkIGJhbGFuY2VyYCxcbiAgICAgIGFsbG93QWxsT3V0Ym91bmQ6IHRydWVcbiAgICB9KTtcblxuICAgIC8vIEFsbG93IGluYm91bmQgdHJhZmZpY1xuICAgIGNvbnN0IGluYm91bmRQb3J0ID0gdGhpcy5pc0h0dHBzID8gNDQzIDogODA7XG4gICAgdGhpcy5zZWN1cml0eUdyb3VwLmFkZEluZ3Jlc3NSdWxlKFxuICAgICAgeyBjb25uZWN0aW9uczogeyBhbGxvd0Zyb21BbnlJcHY0OiAoKSA9PiB7fSB9IH0gYXMgYW55LFxuICAgICAgUG9ydC50Y3AoaW5ib3VuZFBvcnQpLFxuICAgICAgYEFsbG93IGluYm91bmQgJHt0aGlzLmlzSHR0cHMgPyBcIkhUVFBTXCIgOiBcIkhUVFBcIn1gXG4gICAgKTtcblxuICAgIC8vIENyZWF0ZSBBTEJcbiAgICBjb25zdCBsb2FkQmFsYW5jZXJOYW1lID0gdGhpcy50cnVuY2F0ZU5hbWUoYCR7aWR9QUxCYCwgMzIpO1xuICAgIHRoaXMuYWxiID0gbmV3IEFwcGxpY2F0aW9uTG9hZEJhbGFuY2VyKHRoaXMsIFwiQUxCXCIsIHtcbiAgICAgIHZwYzogcHJvcHMudnBjLFxuICAgICAgaW50ZXJuZXRGYWNpbmc6IHByb3BzLnB1YmxpYyAhPT0gZmFsc2UsXG4gICAgICBzZWN1cml0eUdyb3VwOiB0aGlzLnNlY3VyaXR5R3JvdXAsXG4gICAgICBsb2FkQmFsYW5jZXJOYW1lLFxuICAgICAgdnBjU3VibmV0czoge1xuICAgICAgICBzdWJuZXRUeXBlOlxuICAgICAgICAgIHByb3BzLnB1YmxpYyAhPT0gZmFsc2UgPyBTdWJuZXRUeXBlLlBVQkxJQyA6IFN1Ym5ldFR5cGUuUFJJVkFURV9XSVRIX0VHUkVTU1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgLy8gU2V0IHVwIGRvbWFpbiBpZiBjb25maWd1cmVkXG4gICAgaWYgKHByb3BzLmRvbWFpbikge1xuICAgICAgY29uc3QgZG9tYWluQ29uZmlnID0gdGhpcy5ub3JtYWxpc2VEb21haW5Db25maWcocHJvcHMuZG9tYWluKTtcbiAgICAgIHRoaXMuc2V0dXBEb21haW4oZG9tYWluQ29uZmlnKTtcbiAgICB9XG5cbiAgICAvLyBDcmVhdGUgbGlzdGVuZXJcbiAgICBpZiAodGhpcy5pc0h0dHBzICYmIHRoaXMuY2VydGlmaWNhdGUpIHtcbiAgICAgIHRoaXMubGlzdGVuZXIgPSB0aGlzLmFsYi5hZGRMaXN0ZW5lcihcIkxpc3RlbmVyXCIsIHtcbiAgICAgICAgcG9ydDogNDQzLFxuICAgICAgICBwcm90b2NvbDogQXBwbGljYXRpb25Qcm90b2NvbC5IVFRQUyxcbiAgICAgICAgY2VydGlmaWNhdGVzOiBbdGhpcy5jZXJ0aWZpY2F0ZV1cbiAgICAgIH0pO1xuXG4gICAgICAvLyBBZGQgSFRUUCByZWRpcmVjdFxuICAgICAgdGhpcy5hbGIuYWRkTGlzdGVuZXIoXCJIdHRwUmVkaXJlY3RcIiwge1xuICAgICAgICBwb3J0OiA4MCxcbiAgICAgICAgcHJvdG9jb2w6IEFwcGxpY2F0aW9uUHJvdG9jb2wuSFRUUCxcbiAgICAgICAgZGVmYXVsdEFjdGlvbjoge1xuICAgICAgICAgIHJlZGlyZWN0OiB7XG4gICAgICAgICAgICBwcm90b2NvbDogXCJIVFRQU1wiLFxuICAgICAgICAgICAgcG9ydDogXCI0NDNcIixcbiAgICAgICAgICAgIHBlcm1hbmVudDogdHJ1ZVxuICAgICAgICAgIH1cbiAgICAgICAgfSBhcyBhbnlcbiAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxpc3RlbmVyID0gdGhpcy5hbGIuYWRkTGlzdGVuZXIoXCJMaXN0ZW5lclwiLCB7XG4gICAgICAgIHBvcnQ6IDgwLFxuICAgICAgICBwcm90b2NvbDogQXBwbGljYXRpb25Qcm90b2NvbC5IVFRQXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICAvLyBBZGQgZGVmYXVsdCBhY3Rpb24gKHJldHVybnMgNTAzIGlmIG5vIHRhcmdldHMgbWF0Y2gpXG4gICAgdGhpcy5saXN0ZW5lci5hZGRBY3Rpb24oXCJEZWZhdWx0QWN0aW9uXCIsIHtcbiAgICAgIGFjdGlvbjoge1xuICAgICAgICBmaXhlZFJlc3BvbnNlOiB7XG4gICAgICAgICAgc3RhdHVzQ29kZTogNTAzLFxuICAgICAgICAgIGNvbnRlbnRUeXBlOiBcInRleHQvcGxhaW5cIixcbiAgICAgICAgICBtZXNzYWdlQm9keTogXCJObyBzZXJ2aWNlIGF2YWlsYWJsZVwiXG4gICAgICAgIH1cbiAgICAgIH0gYXMgYW55XG4gICAgfSk7XG5cbiAgICAvLyBTZXQgVVJMXG4gICAgdGhpcy51cmwgPSB0aGlzLmlzSHR0cHNcbiAgICAgID8gYGh0dHBzOi8vJHt0aGlzLmFsYi5sb2FkQmFsYW5jZXJEbnNOYW1lfWBcbiAgICAgIDogYGh0dHA6Ly8ke3RoaXMuYWxiLmxvYWRCYWxhbmNlckRuc05hbWV9YDtcblxuICAgIC8vIFNldCB1cCBjb25uZWN0aW9uc1xuICAgIHRoaXMuY29ubmVjdGlvbnMgPSBuZXcgQ29ubmVjdGlvbnMoe1xuICAgICAgc2VjdXJpdHlHcm91cHM6IFt0aGlzLnNlY3VyaXR5R3JvdXBdLFxuICAgICAgZGVmYXVsdFBvcnQ6IFBvcnQudGNwKGluYm91bmRQb3J0KVxuICAgIH0pO1xuXG4gICAgLy8gT3V0cHV0c1xuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJMb2FkQmFsYW5jZXJEbnNOYW1lXCIsIHtcbiAgICAgIGtleTogYCR7aWR9TG9hZEJhbGFuY2VyRG5zTmFtZWAsXG4gICAgICBleHBvcnROYW1lOiBgJHtpZH1Mb2FkQmFsYW5jZXJEbnNOYW1lYCxcbiAgICAgIHZhbHVlOiB0aGlzLmFsYi5sb2FkQmFsYW5jZXJEbnNOYW1lXG4gICAgfSk7XG5cbiAgICBuZXcgQ2ZuT3V0cHV0KHRoaXMsIFwiTG9hZEJhbGFuY2VyVXJsXCIsIHtcbiAgICAgIGtleTogYCR7aWR9TG9hZEJhbGFuY2VyVXJsYCxcbiAgICAgIGV4cG9ydE5hbWU6IGAke2lkfUxvYWRCYWxhbmNlclVybGAsXG4gICAgICB2YWx1ZTogdGhpcy51cmwsXG4gICAgICBkZXNjcmlwdGlvbjogYExvYWQgQmFsYW5jZXIgVVJMIGZvciAke2lkfWBcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJMb2FkQmFsYW5jZXJBcm5cIiwge1xuICAgICAga2V5OiBgJHtpZH1Mb2FkQmFsYW5jZXJBcm5gLFxuICAgICAgZXhwb3J0TmFtZTogYCR7aWR9TG9hZEJhbGFuY2VyQXJuYCxcbiAgICAgIHZhbHVlOiB0aGlzLmFsYi5sb2FkQmFsYW5jZXJBcm4sXG4gICAgICBkZXNjcmlwdGlvbjogYExvYWQgQmFsYW5jZXIgQVJOIGZvciAke2lkfWBcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWdpc3RlciBhIHRhcmdldCBncm91cCB3aXRoIHJvdXRpbmcgcnVsZXMuXG4gICAqIENhbGxlZCBpbnRlcm5hbGx5IGJ5IEVjc0NsdXN0ZXIgd2hlbiBsb2FkQmFsYW5jZXIudGFyZ2V0IGlzIHNwZWNpZmllZC5cbiAgICpcbiAgICogQHBhcmFtIHRhcmdldEdyb3VwIC0gVGhlIHRhcmdldCBncm91cCB0byByZWdpc3RlclxuICAgKiBAcGFyYW0gcm91dGluZyAtIFJvdXRpbmcgY29uZmlndXJhdGlvbiAocGF0aCwgaG9zdCwgcHJpb3JpdHkpXG4gICAqL1xuICBwdWJsaWMgcmVnaXN0ZXJUYXJnZXRHcm91cChcbiAgICB0YXJnZXRHcm91cDogSUFwcGxpY2F0aW9uVGFyZ2V0R3JvdXAsXG4gICAgcm91dGluZzogTG9hZEJhbGFuY2VyUm91dGluZ0NvbmZpZ1xuICApOiB2b2lkIHtcbiAgICBjb25zdCBjb25kaXRpb25zOiBMaXN0ZW5lckNvbmRpdGlvbltdID0gW107XG5cbiAgICBpZiAocm91dGluZy5wYXRoKSB7XG4gICAgICBjb25zdCBwYXRocyA9IEFycmF5LmlzQXJyYXkocm91dGluZy5wYXRoKSA/IHJvdXRpbmcucGF0aCA6IFtyb3V0aW5nLnBhdGhdO1xuICAgICAgY29uZGl0aW9ucy5wdXNoKExpc3RlbmVyQ29uZGl0aW9uLnBhdGhQYXR0ZXJucyhwYXRocykpO1xuICAgIH1cblxuICAgIGlmIChyb3V0aW5nLmhvc3QpIHtcbiAgICAgIGNvbnN0IGhvc3RzID0gQXJyYXkuaXNBcnJheShyb3V0aW5nLmhvc3QpID8gcm91dGluZy5ob3N0IDogW3JvdXRpbmcuaG9zdF07XG4gICAgICBjb25kaXRpb25zLnB1c2goTGlzdGVuZXJDb25kaXRpb24uaG9zdEhlYWRlcnMoaG9zdHMpKTtcbiAgICB9XG5cbiAgICBpZiAoY29uZGl0aW9ucy5sZW5ndGggPT09IDApIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgXCJBdCBsZWFzdCBvbmUgcm91dGluZyBjb25kaXRpb24gKHBhdGggb3IgaG9zdCkgbXVzdCBiZSBzcGVjaWZpZWQuXCJcbiAgICAgICk7XG4gICAgfVxuXG4gICAgdGhpcy5saXN0ZW5lci5hZGRUYXJnZXRHcm91cHMoYFJ1bGUke3JvdXRpbmcucHJpb3JpdHl9YCwge1xuICAgICAgdGFyZ2V0R3JvdXBzOiBbdGFyZ2V0R3JvdXBdLFxuICAgICAgY29uZGl0aW9ucyxcbiAgICAgIHByaW9yaXR5OiByb3V0aW5nLnByaW9yaXR5XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgdGFyZ2V0IGdyb3VwIGZvciBhbiBFQ1Mgc2VydmljZS5cbiAgICogUmV0dXJucyB0aGUgdGFyZ2V0IGdyb3VwIGZvciB0aGUgc2VydmljZSB0byByZWdpc3RlciB3aXRoLlxuICAgKlxuICAgKiBAcGFyYW0gaWQgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHRhcmdldCBncm91cFxuICAgKiBAcGFyYW0gcG9ydCAtIENvbnRhaW5lciBwb3J0XG4gICAqIEBwYXJhbSB2cGMgLSBWUEMgZm9yIHRoZSB0YXJnZXQgZ3JvdXBcbiAgICogQHBhcmFtIGhlYWx0aENoZWNrUGF0aCAtIE9wdGlvbmFsIGhlYWx0aCBjaGVjayBwYXRoIG92ZXJyaWRlXG4gICAqL1xuICBwdWJsaWMgY3JlYXRlVGFyZ2V0R3JvdXAoXG4gICAgaWQ6IHN0cmluZyxcbiAgICBwb3J0OiBudW1iZXIsXG4gICAgdnBjOiBJVnBjLFxuICAgIGhlYWx0aENoZWNrUGF0aD86IHN0cmluZ1xuICApOiBBcHBsaWNhdGlvblRhcmdldEdyb3VwIHtcbiAgICBjb25zdCB0YXJnZXRHcm91cCA9IG5ldyBBcHBsaWNhdGlvblRhcmdldEdyb3VwKHRoaXMsIGAke2lkfVRhcmdldEdyb3VwYCwge1xuICAgICAgdnBjLFxuICAgICAgcG9ydCxcbiAgICAgIHByb3RvY29sOiBBcHBsaWNhdGlvblByb3RvY29sLkhUVFAsXG4gICAgICB0YXJnZXRUeXBlOiBUYXJnZXRUeXBlLklQLFxuICAgICAgaGVhbHRoQ2hlY2s6IHtcbiAgICAgICAgcGF0aDogaGVhbHRoQ2hlY2tQYXRoIHx8IHRoaXMuZGVmYXVsdEhlYWx0aENoZWNrLnBhdGggfHwgXCIvXCIsXG4gICAgICAgIGludGVydmFsOiBEdXJhdGlvbi5zZWNvbmRzKFxuICAgICAgICAgIHRoaXMuZGVmYXVsdEhlYWx0aENoZWNrLmludGVydmFsIHx8IDMwXG4gICAgICAgICksXG4gICAgICAgIHRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHMoXG4gICAgICAgICAgdGhpcy5kZWZhdWx0SGVhbHRoQ2hlY2sudGltZW91dCB8fCA1XG4gICAgICAgICksXG4gICAgICAgIGhlYWx0aHlUaHJlc2hvbGRDb3VudDogdGhpcy5kZWZhdWx0SGVhbHRoQ2hlY2suaGVhbHRoeVRocmVzaG9sZCB8fCAyLFxuICAgICAgICB1bmhlYWx0aHlUaHJlc2hvbGRDb3VudDogdGhpcy5kZWZhdWx0SGVhbHRoQ2hlY2sudW5oZWFsdGh5VGhyZXNob2xkIHx8IDNcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHJldHVybiB0YXJnZXRHcm91cDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBbGxvdyB0cmFmZmljIGZyb20gdGhpcyBsb2FkIGJhbGFuY2VyIHRvIGEgY29ubmVjdGFibGUgcmVzb3VyY2UuXG4gICAqL1xuICBwdWJsaWMgYWxsb3dUbyhjb25uZWN0YWJsZTogSUNvbm5lY3RhYmxlLCBwb3J0PzogUG9ydCk6IHZvaWQge1xuICAgIHRoaXMuY29ubmVjdGlvbnMuYWxsb3dUbyhcbiAgICAgIGNvbm5lY3RhYmxlLFxuICAgICAgcG9ydCB8fCBjb25uZWN0YWJsZS5jb25uZWN0aW9ucy5kZWZhdWx0UG9ydCB8fCBQb3J0LmFsbFRjcCgpXG4gICAgKTtcbiAgfVxuXG4gIHByaXZhdGUgbm9ybWFsaXNlRG9tYWluQ29uZmlnKFxuICAgIGRvbWFpbjogc3RyaW5nIHwgTG9hZEJhbGFuY2VyRG9tYWluQ29uZmlnXG4gICk6IExvYWRCYWxhbmNlckRvbWFpbkNvbmZpZyB7XG4gICAgaWYgKHR5cGVvZiBkb21haW4gPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHJldHVybiB7IG5hbWU6IGRvbWFpbiB9O1xuICAgIH1cbiAgICByZXR1cm4gZG9tYWluO1xuICB9XG5cbiAgcHJpdmF0ZSBzZXR1cERvbWFpbihjb25maWc6IExvYWRCYWxhbmNlckRvbWFpbkNvbmZpZyk6IHZvaWQge1xuICAgIC8vIEdldCBvciBjcmVhdGUgaG9zdGVkIHpvbmVcbiAgICBpZiAoY29uZmlnLmhvc3RlZFpvbmUpIHtcbiAgICAgICh0aGlzIGFzIGFueSkuaG9zdGVkWm9uZSA9IGNvbmZpZy5ob3N0ZWRab25lLmdldEludGVybmFsSG9zdGVkWm9uZSgpO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBob3N0ZWRab25lID0gbmV3IEZqYWxsSG9zdGVkWm9uZSh0aGlzLCBcIkhvc3RlZFpvbmVcIiwge1xuICAgICAgICB6b25lTmFtZTogY29uZmlnLm5hbWVcbiAgICAgIH0pO1xuICAgICAgKHRoaXMgYXMgYW55KS5ob3N0ZWRab25lID0gaG9zdGVkWm9uZS5nZXRJbnRlcm5hbEhvc3RlZFpvbmUoKTtcbiAgICB9XG5cbiAgICAvLyBHZXQgb3IgY3JlYXRlIGNlcnRpZmljYXRlXG4gICAgaWYgKGNvbmZpZy5jZXJ0aWZpY2F0ZSkge1xuICAgICAgKHRoaXMgYXMgYW55KS5jZXJ0aWZpY2F0ZSA9IGNvbmZpZy5jZXJ0aWZpY2F0ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgKHRoaXMgYXMgYW55KS5jZXJ0aWZpY2F0ZSA9IG5ldyBDZXJ0aWZpY2F0ZSh0aGlzLCBcIkNlcnRpZmljYXRlXCIsIHtcbiAgICAgICAgZG9tYWluTmFtZTogY29uZmlnLm5hbWUsXG4gICAgICAgIHZhbGlkYXRpb246IENlcnRpZmljYXRlVmFsaWRhdGlvbi5mcm9tRG5zKHRoaXMuaG9zdGVkWm9uZSlcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSBETlMgcmVjb3JkIHBvaW50aW5nIHRvIEFMQlxuICAgIG5ldyBBUmVjb3JkKHRoaXMsIFwiQWxpYXNSZWNvcmRcIiwge1xuICAgICAgem9uZTogdGhpcy5ob3N0ZWRab25lISxcbiAgICAgIHJlY29yZE5hbWU6IGNvbmZpZy5uYW1lLFxuICAgICAgdGFyZ2V0OiBSZWNvcmRUYXJnZXQuZnJvbUFsaWFzKG5ldyBMb2FkQmFsYW5jZXJUYXJnZXQodGhpcy5hbGIpKVxuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSB0cnVuY2F0ZU5hbWUobmFtZTogc3RyaW5nLCBtYXhMZW5ndGg6IG51bWJlcik6IHN0cmluZyB7XG4gICAgaWYgKG5hbWUubGVuZ3RoIDw9IG1heExlbmd0aCkge1xuICAgICAgcmV0dXJuIG5hbWU7XG4gICAgfVxuICAgIHJldHVybiBuYW1lLnN1YnN0cmluZygwLCBtYXhMZW5ndGgpLnJlcGxhY2UoLy0rJC8sIFwiXCIpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgRE5TIG5hbWUgb2YgdGhlIGxvYWQgYmFsYW5jZXIuXG4gICAqL1xuICBwdWJsaWMgZ2V0RG5zTmFtZSgpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLmFsYi5sb2FkQmFsYW5jZXJEbnNOYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgQVJOIG9mIHRoZSBsb2FkIGJhbGFuY2VyLlxuICAgKi9cbiAgcHVibGljIGdldEFybigpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLmFsYi5sb2FkQmFsYW5jZXJBcm47XG4gIH1cbn1cbiJdfQ==
@@ -1,20 +0,0 @@
1
- import { Construct } from "constructs";
2
- /**
3
- * Props for CapacityProviderDrainWaiter
4
- */
5
- interface CapacityProviderDrainWaiterProps {
6
- /** The ECS cluster name */
7
- clusterName: string;
8
- /** The capacity provider name to wait for */
9
- capacityProviderName: string;
10
- }
11
- /**
12
- * Custom resource that waits for ECS services to drain before allowing
13
- * capacity provider disassociation.
14
- *
15
- * @see https://github.com/aws/aws-cdk/issues/14732
16
- */
17
- export declare class CapacityProviderDrainWaiter extends Construct {
18
- constructor(scope: Construct, id: string, props: CapacityProviderDrainWaiterProps);
19
- }
20
- export {};
@@ -1,180 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CapacityProviderDrainWaiter = void 0;
4
- const aws_cdk_lib_1 = require("aws-cdk-lib");
5
- const aws_iam_1 = require("aws-cdk-lib/aws-iam");
6
- const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
7
- const constructs_1 = require("constructs");
8
- const customResource_1 = require("../utilities/customResource");
9
- /**
10
- * Custom resource that waits for ECS services to drain before allowing
11
- * capacity provider disassociation.
12
- *
13
- * @see https://github.com/aws/aws-cdk/issues/14732
14
- */
15
- class CapacityProviderDrainWaiter extends constructs_1.Construct {
16
- constructor(scope, id, props) {
17
- super(scope, id);
18
- const { clusterName, capacityProviderName } = props;
19
- // Lambda inline code for the custom resource handler
20
- const handlerCode = `
21
- const { ECSClient, ListServicesCommand, DescribeServicesCommand, PutClusterCapacityProvidersCommand, DescribeClustersCommand } = require("@aws-sdk/client-ecs");
22
-
23
- const ecsClient = new ECSClient({});
24
- const POLL_INTERVAL_MS = 5000;
25
- const MAX_WAIT_MS = 300000; // 5 minutes
26
-
27
- async function sleep(ms) {
28
- return new Promise(resolve => setTimeout(resolve, ms));
29
- }
30
-
31
- async function getClusterCapacityProviders(clusterName) {
32
- const response = await ecsClient.send(new DescribeClustersCommand({
33
- clusters: [clusterName]
34
- }));
35
- return response.clusters?.[0]?.capacityProviders || [];
36
- }
37
-
38
- async function waitForServicesDrained(clusterName, capacityProviderName) {
39
- const startTime = Date.now();
40
-
41
- while (Date.now() - startTime < MAX_WAIT_MS) {
42
- // List all services in the cluster
43
- const listResponse = await ecsClient.send(new ListServicesCommand({
44
- cluster: clusterName,
45
- maxResults: 100
46
- }));
47
-
48
- const serviceArns = listResponse.serviceArns || [];
49
-
50
- if (serviceArns.length === 0) {
51
- console.log("No services found in cluster - drain complete");
52
- return true;
53
- }
54
-
55
- // Describe services to check running counts
56
- const describeResponse = await ecsClient.send(new DescribeServicesCommand({
57
- cluster: clusterName,
58
- services: serviceArns
59
- }));
60
-
61
- const activeServices = (describeResponse.services || []).filter(svc => {
62
- // Check if service uses this capacity provider
63
- const usesCapacityProvider = (svc.capacityProviderStrategy || [])
64
- .some(strategy => strategy.capacityProvider === capacityProviderName);
65
-
66
- // Only care about services using this capacity provider that still have running tasks
67
- return usesCapacityProvider && svc.runningCount > 0;
68
- });
69
-
70
- if (activeServices.length === 0) {
71
- console.log("All services using capacity provider have drained");
72
- return true;
73
- }
74
-
75
- console.log(\`Waiting for \${activeServices.length} services to drain: \${activeServices.map(s => s.serviceName).join(", ")}\`);
76
- await sleep(POLL_INTERVAL_MS);
77
- }
78
-
79
- console.log("Timeout waiting for services to drain, proceeding anyway");
80
- return false;
81
- }
82
-
83
- async function disassociateCapacityProvider(clusterName, capacityProviderName) {
84
- try {
85
- // Get current capacity providers
86
- const currentProviders = await getClusterCapacityProviders(clusterName);
87
-
88
- // Filter out the one we want to remove
89
- const remainingProviders = currentProviders.filter(cp => cp !== capacityProviderName);
90
-
91
- if (remainingProviders.length === currentProviders.length) {
92
- console.log(\`Capacity provider \${capacityProviderName} not associated with cluster\`);
93
- return true;
94
- }
95
-
96
- // Ensure we keep at least FARGATE as a provider (required by ECS)
97
- if (remainingProviders.length === 0) {
98
- remainingProviders.push("FARGATE");
99
- }
100
-
101
- console.log(\`Disassociating \${capacityProviderName} from cluster. Remaining: \${remainingProviders.join(", ")}\`);
102
-
103
- await ecsClient.send(new PutClusterCapacityProvidersCommand({
104
- cluster: clusterName,
105
- capacityProviders: remainingProviders,
106
- defaultCapacityProviderStrategy: []
107
- }));
108
-
109
- console.log("Successfully disassociated capacity provider");
110
- return true;
111
- } catch (error) {
112
- // If cluster or capacity provider doesn't exist, that's fine (already deleted)
113
- if (error.name === "ClusterNotFoundException" ||
114
- error.name === "ResourceNotFoundException" ||
115
- error.message?.includes("does not exist")) {
116
- console.log("Cluster or capacity provider not found - already cleaned up");
117
- return true;
118
- }
119
- throw error;
120
- }
121
- }
122
-
123
- exports.handler = async (event) => {
124
- console.log("Event:", JSON.stringify(event, null, 2));
125
-
126
- const clusterName = event.ResourceProperties.ClusterName;
127
- const capacityProviderName = event.ResourceProperties.CapacityProviderName;
128
- const requestType = event.RequestType;
129
-
130
- try {
131
- if (requestType === "Delete") {
132
- console.log(\`Handling DELETE for capacity provider \${capacityProviderName} in cluster \${clusterName}\`);
133
-
134
- // Wait for services to drain
135
- await waitForServicesDrained(clusterName, capacityProviderName);
136
-
137
- // Disassociate capacity provider from cluster
138
- await disassociateCapacityProvider(clusterName, capacityProviderName);
139
- }
140
-
141
- // Return success for all request types
142
- return {
143
- PhysicalResourceId: \`\${clusterName}-\${capacityProviderName}-drain-waiter\`,
144
- Data: {
145
- Message: \`\${requestType} completed successfully\`
146
- }
147
- };
148
- } catch (error) {
149
- console.error("Error:", error);
150
- throw error;
151
- }
152
- };
153
- `;
154
- new customResource_1.CustomResource(this, "DrainWaiter", {
155
- runtime: aws_lambda_1.Runtime.NODEJS_22_X,
156
- inlineCode: handlerCode,
157
- timeout: aws_cdk_lib_1.Duration.minutes(6),
158
- lambdaDescription: `Waits for ECS services to drain before capacity provider disassociation`,
159
- roleDescription: `Role for ${clusterName} capacity provider drain waiter`,
160
- inlinePolicy: [
161
- new aws_iam_1.PolicyStatement({
162
- effect: aws_iam_1.Effect.ALLOW,
163
- actions: [
164
- "ecs:ListServices",
165
- "ecs:DescribeServices",
166
- "ecs:DescribeClusters",
167
- "ecs:PutClusterCapacityProviders"
168
- ],
169
- resources: ["*"]
170
- })
171
- ],
172
- properties: {
173
- ClusterName: clusterName,
174
- CapacityProviderName: capacityProviderName
175
- }
176
- });
177
- }
178
- }
179
- exports.CapacityProviderDrainWaiter = CapacityProviderDrainWaiter;
180
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGliL3Jlc291cmNlcy9hd3MvY29tcHV0ZS9jYXBhY2l0eVByb3ZpZGVyRHJhaW5XYWl0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNkNBQXVDO0FBQ3ZDLGlEQUE4RDtBQUM5RCx1REFBaUQ7QUFDakQsMkNBQXVDO0FBQ3ZDLGdFQUE2RDtBQVk3RDs7Ozs7R0FLRztBQUNILE1BQWEsMkJBQTRCLFNBQVEsc0JBQVM7SUFDeEQsWUFDRSxLQUFnQixFQUNoQixFQUFVLEVBQ1YsS0FBdUM7UUFFdkMsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixNQUFNLEVBQUUsV0FBVyxFQUFFLG9CQUFvQixFQUFFLEdBQUcsS0FBSyxDQUFDO1FBRXBELHFEQUFxRDtRQUNyRCxNQUFNLFdBQVcsR0FBRzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQXFJdkIsQ0FBQztRQUVFLElBQUksK0JBQWMsQ0FBQyxJQUFJLEVBQUUsYUFBYSxFQUFFO1lBQ3RDLE9BQU8sRUFBRSxvQkFBTyxDQUFDLFdBQVc7WUFDNUIsVUFBVSxFQUFFLFdBQVc7WUFDdkIsT0FBTyxFQUFFLHNCQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztZQUM1QixpQkFBaUIsRUFBRSx5RUFBeUU7WUFDNUYsZUFBZSxFQUFFLFlBQVksV0FBVyxpQ0FBaUM7WUFDekUsWUFBWSxFQUFFO2dCQUNaLElBQUkseUJBQWUsQ0FBQztvQkFDbEIsTUFBTSxFQUFFLGdCQUFNLENBQUMsS0FBSztvQkFDcEIsT0FBTyxFQUFFO3dCQUNQLGtCQUFrQjt3QkFDbEIsc0JBQXNCO3dCQUN0QixzQkFBc0I7d0JBQ3RCLGlDQUFpQztxQkFDbEM7b0JBQ0QsU0FBUyxFQUFFLENBQUMsR0FBRyxDQUFDO2lCQUNqQixDQUFDO2FBQ0g7WUFDRCxVQUFVLEVBQUU7Z0JBQ1YsV0FBVyxFQUFFLFdBQVc7Z0JBQ3hCLG9CQUFvQixFQUFFLG9CQUFvQjthQUMzQztTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQTFLRCxrRUEwS0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBEdXJhdGlvbiB9IGZyb20gXCJhd3MtY2RrLWxpYlwiO1xuaW1wb3J0IHsgUG9saWN5U3RhdGVtZW50LCBFZmZlY3QgfSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWlhbVwiO1xuaW1wb3J0IHsgUnVudGltZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtbGFtYmRhXCI7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0IHsgQ3VzdG9tUmVzb3VyY2UgfSBmcm9tIFwiLi4vdXRpbGl0aWVzL2N1c3RvbVJlc291cmNlXCI7XG5cbi8qKlxuICogUHJvcHMgZm9yIENhcGFjaXR5UHJvdmlkZXJEcmFpbldhaXRlclxuICovXG5pbnRlcmZhY2UgQ2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyUHJvcHMge1xuICAvKiogVGhlIEVDUyBjbHVzdGVyIG5hbWUgKi9cbiAgY2x1c3Rlck5hbWU6IHN0cmluZztcbiAgLyoqIFRoZSBjYXBhY2l0eSBwcm92aWRlciBuYW1lIHRvIHdhaXQgZm9yICovXG4gIGNhcGFjaXR5UHJvdmlkZXJOYW1lOiBzdHJpbmc7XG59XG5cbi8qKlxuICogQ3VzdG9tIHJlc291cmNlIHRoYXQgd2FpdHMgZm9yIEVDUyBzZXJ2aWNlcyB0byBkcmFpbiBiZWZvcmUgYWxsb3dpbmdcbiAqIGNhcGFjaXR5IHByb3ZpZGVyIGRpc2Fzc29jaWF0aW9uLlxuICpcbiAqIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2F3cy9hd3MtY2RrL2lzc3Vlcy8xNDczMlxuICovXG5leHBvcnQgY2xhc3MgQ2FwYWNpdHlQcm92aWRlckRyYWluV2FpdGVyIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgY29uc3RydWN0b3IoXG4gICAgc2NvcGU6IENvbnN0cnVjdCxcbiAgICBpZDogc3RyaW5nLFxuICAgIHByb3BzOiBDYXBhY2l0eVByb3ZpZGVyRHJhaW5XYWl0ZXJQcm9wc1xuICApIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgeyBjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUgfSA9IHByb3BzO1xuXG4gICAgLy8gTGFtYmRhIGlubGluZSBjb2RlIGZvciB0aGUgY3VzdG9tIHJlc291cmNlIGhhbmRsZXJcbiAgICBjb25zdCBoYW5kbGVyQ29kZSA9IGBcbmNvbnN0IHsgRUNTQ2xpZW50LCBMaXN0U2VydmljZXNDb21tYW5kLCBEZXNjcmliZVNlcnZpY2VzQ29tbWFuZCwgUHV0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzQ29tbWFuZCwgRGVzY3JpYmVDbHVzdGVyc0NvbW1hbmQgfSA9IHJlcXVpcmUoXCJAYXdzLXNkay9jbGllbnQtZWNzXCIpO1xuXG5jb25zdCBlY3NDbGllbnQgPSBuZXcgRUNTQ2xpZW50KHt9KTtcbmNvbnN0IFBPTExfSU5URVJWQUxfTVMgPSA1MDAwO1xuY29uc3QgTUFYX1dBSVRfTVMgPSAzMDAwMDA7IC8vIDUgbWludXRlc1xuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtcykge1xuICByZXR1cm4gbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIG1zKSk7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGdldENsdXN0ZXJDYXBhY2l0eVByb3ZpZGVycyhjbHVzdGVyTmFtZSkge1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjc0NsaWVudC5zZW5kKG5ldyBEZXNjcmliZUNsdXN0ZXJzQ29tbWFuZCh7XG4gICAgY2x1c3RlcnM6IFtjbHVzdGVyTmFtZV1cbiAgfSkpO1xuICByZXR1cm4gcmVzcG9uc2UuY2x1c3RlcnM/LlswXT8uY2FwYWNpdHlQcm92aWRlcnMgfHwgW107XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHdhaXRGb3JTZXJ2aWNlc0RyYWluZWQoY2x1c3Rlck5hbWUsIGNhcGFjaXR5UHJvdmlkZXJOYW1lKSB7XG4gIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG5cbiAgd2hpbGUgKERhdGUubm93KCkgLSBzdGFydFRpbWUgPCBNQVhfV0FJVF9NUykge1xuICAgIC8vIExpc3QgYWxsIHNlcnZpY2VzIGluIHRoZSBjbHVzdGVyXG4gICAgY29uc3QgbGlzdFJlc3BvbnNlID0gYXdhaXQgZWNzQ2xpZW50LnNlbmQobmV3IExpc3RTZXJ2aWNlc0NvbW1hbmQoe1xuICAgICAgY2x1c3RlcjogY2x1c3Rlck5hbWUsXG4gICAgICBtYXhSZXN1bHRzOiAxMDBcbiAgICB9KSk7XG5cbiAgICBjb25zdCBzZXJ2aWNlQXJucyA9IGxpc3RSZXNwb25zZS5zZXJ2aWNlQXJucyB8fCBbXTtcblxuICAgIGlmIChzZXJ2aWNlQXJucy5sZW5ndGggPT09IDApIHtcbiAgICAgIGNvbnNvbGUubG9nKFwiTm8gc2VydmljZXMgZm91bmQgaW4gY2x1c3RlciAtIGRyYWluIGNvbXBsZXRlXCIpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgLy8gRGVzY3JpYmUgc2VydmljZXMgdG8gY2hlY2sgcnVubmluZyBjb3VudHNcbiAgICBjb25zdCBkZXNjcmliZVJlc3BvbnNlID0gYXdhaXQgZWNzQ2xpZW50LnNlbmQobmV3IERlc2NyaWJlU2VydmljZXNDb21tYW5kKHtcbiAgICAgIGNsdXN0ZXI6IGNsdXN0ZXJOYW1lLFxuICAgICAgc2VydmljZXM6IHNlcnZpY2VBcm5zXG4gICAgfSkpO1xuXG4gICAgY29uc3QgYWN0aXZlU2VydmljZXMgPSAoZGVzY3JpYmVSZXNwb25zZS5zZXJ2aWNlcyB8fCBbXSkuZmlsdGVyKHN2YyA9PiB7XG4gICAgICAvLyBDaGVjayBpZiBzZXJ2aWNlIHVzZXMgdGhpcyBjYXBhY2l0eSBwcm92aWRlclxuICAgICAgY29uc3QgdXNlc0NhcGFjaXR5UHJvdmlkZXIgPSAoc3ZjLmNhcGFjaXR5UHJvdmlkZXJTdHJhdGVneSB8fCBbXSlcbiAgICAgICAgLnNvbWUoc3RyYXRlZ3kgPT4gc3RyYXRlZ3kuY2FwYWNpdHlQcm92aWRlciA9PT0gY2FwYWNpdHlQcm92aWRlck5hbWUpO1xuXG4gICAgICAvLyBPbmx5IGNhcmUgYWJvdXQgc2VydmljZXMgdXNpbmcgdGhpcyBjYXBhY2l0eSBwcm92aWRlciB0aGF0IHN0aWxsIGhhdmUgcnVubmluZyB0YXNrc1xuICAgICAgcmV0dXJuIHVzZXNDYXBhY2l0eVByb3ZpZGVyICYmIHN2Yy5ydW5uaW5nQ291bnQgPiAwO1xuICAgIH0pO1xuXG4gICAgaWYgKGFjdGl2ZVNlcnZpY2VzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgY29uc29sZS5sb2coXCJBbGwgc2VydmljZXMgdXNpbmcgY2FwYWNpdHkgcHJvdmlkZXIgaGF2ZSBkcmFpbmVkXCIpO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgY29uc29sZS5sb2coXFxgV2FpdGluZyBmb3IgXFwke2FjdGl2ZVNlcnZpY2VzLmxlbmd0aH0gc2VydmljZXMgdG8gZHJhaW46IFxcJHthY3RpdmVTZXJ2aWNlcy5tYXAocyA9PiBzLnNlcnZpY2VOYW1lKS5qb2luKFwiLCBcIil9XFxgKTtcbiAgICBhd2FpdCBzbGVlcChQT0xMX0lOVEVSVkFMX01TKTtcbiAgfVxuXG4gIGNvbnNvbGUubG9nKFwiVGltZW91dCB3YWl0aW5nIGZvciBzZXJ2aWNlcyB0byBkcmFpbiwgcHJvY2VlZGluZyBhbnl3YXlcIik7XG4gIHJldHVybiBmYWxzZTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGlzYXNzb2NpYXRlQ2FwYWNpdHlQcm92aWRlcihjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUpIHtcbiAgdHJ5IHtcbiAgICAvLyBHZXQgY3VycmVudCBjYXBhY2l0eSBwcm92aWRlcnNcbiAgICBjb25zdCBjdXJyZW50UHJvdmlkZXJzID0gYXdhaXQgZ2V0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzKGNsdXN0ZXJOYW1lKTtcblxuICAgIC8vIEZpbHRlciBvdXQgdGhlIG9uZSB3ZSB3YW50IHRvIHJlbW92ZVxuICAgIGNvbnN0IHJlbWFpbmluZ1Byb3ZpZGVycyA9IGN1cnJlbnRQcm92aWRlcnMuZmlsdGVyKGNwID0+IGNwICE9PSBjYXBhY2l0eVByb3ZpZGVyTmFtZSk7XG5cbiAgICBpZiAocmVtYWluaW5nUHJvdmlkZXJzLmxlbmd0aCA9PT0gY3VycmVudFByb3ZpZGVycy5sZW5ndGgpIHtcbiAgICAgIGNvbnNvbGUubG9nKFxcYENhcGFjaXR5IHByb3ZpZGVyIFxcJHtjYXBhY2l0eVByb3ZpZGVyTmFtZX0gbm90IGFzc29jaWF0ZWQgd2l0aCBjbHVzdGVyXFxgKTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIC8vIEVuc3VyZSB3ZSBrZWVwIGF0IGxlYXN0IEZBUkdBVEUgYXMgYSBwcm92aWRlciAocmVxdWlyZWQgYnkgRUNTKVxuICAgIGlmIChyZW1haW5pbmdQcm92aWRlcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICByZW1haW5pbmdQcm92aWRlcnMucHVzaChcIkZBUkdBVEVcIik7XG4gICAgfVxuXG4gICAgY29uc29sZS5sb2coXFxgRGlzYXNzb2NpYXRpbmcgXFwke2NhcGFjaXR5UHJvdmlkZXJOYW1lfSBmcm9tIGNsdXN0ZXIuIFJlbWFpbmluZzogXFwke3JlbWFpbmluZ1Byb3ZpZGVycy5qb2luKFwiLCBcIil9XFxgKTtcblxuICAgIGF3YWl0IGVjc0NsaWVudC5zZW5kKG5ldyBQdXRDbHVzdGVyQ2FwYWNpdHlQcm92aWRlcnNDb21tYW5kKHtcbiAgICAgIGNsdXN0ZXI6IGNsdXN0ZXJOYW1lLFxuICAgICAgY2FwYWNpdHlQcm92aWRlcnM6IHJlbWFpbmluZ1Byb3ZpZGVycyxcbiAgICAgIGRlZmF1bHRDYXBhY2l0eVByb3ZpZGVyU3RyYXRlZ3k6IFtdXG4gICAgfSkpO1xuXG4gICAgY29uc29sZS5sb2coXCJTdWNjZXNzZnVsbHkgZGlzYXNzb2NpYXRlZCBjYXBhY2l0eSBwcm92aWRlclwiKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAvLyBJZiBjbHVzdGVyIG9yIGNhcGFjaXR5IHByb3ZpZGVyIGRvZXNuJ3QgZXhpc3QsIHRoYXQncyBmaW5lIChhbHJlYWR5IGRlbGV0ZWQpXG4gICAgaWYgKGVycm9yLm5hbWUgPT09IFwiQ2x1c3Rlck5vdEZvdW5kRXhjZXB0aW9uXCIgfHxcbiAgICAgICAgZXJyb3IubmFtZSA9PT0gXCJSZXNvdXJjZU5vdEZvdW5kRXhjZXB0aW9uXCIgfHxcbiAgICAgICAgZXJyb3IubWVzc2FnZT8uaW5jbHVkZXMoXCJkb2VzIG5vdCBleGlzdFwiKSkge1xuICAgICAgY29uc29sZS5sb2coXCJDbHVzdGVyIG9yIGNhcGFjaXR5IHByb3ZpZGVyIG5vdCBmb3VuZCAtIGFscmVhZHkgY2xlYW5lZCB1cFwiKTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxufVxuXG5leHBvcnRzLmhhbmRsZXIgPSBhc3luYyAoZXZlbnQpID0+IHtcbiAgY29uc29sZS5sb2coXCJFdmVudDpcIiwgSlNPTi5zdHJpbmdpZnkoZXZlbnQsIG51bGwsIDIpKTtcblxuICBjb25zdCBjbHVzdGVyTmFtZSA9IGV2ZW50LlJlc291cmNlUHJvcGVydGllcy5DbHVzdGVyTmFtZTtcbiAgY29uc3QgY2FwYWNpdHlQcm92aWRlck5hbWUgPSBldmVudC5SZXNvdXJjZVByb3BlcnRpZXMuQ2FwYWNpdHlQcm92aWRlck5hbWU7XG4gIGNvbnN0IHJlcXVlc3RUeXBlID0gZXZlbnQuUmVxdWVzdFR5cGU7XG5cbiAgdHJ5IHtcbiAgICBpZiAocmVxdWVzdFR5cGUgPT09IFwiRGVsZXRlXCIpIHtcbiAgICAgIGNvbnNvbGUubG9nKFxcYEhhbmRsaW5nIERFTEVURSBmb3IgY2FwYWNpdHkgcHJvdmlkZXIgXFwke2NhcGFjaXR5UHJvdmlkZXJOYW1lfSBpbiBjbHVzdGVyIFxcJHtjbHVzdGVyTmFtZX1cXGApO1xuXG4gICAgICAvLyBXYWl0IGZvciBzZXJ2aWNlcyB0byBkcmFpblxuICAgICAgYXdhaXQgd2FpdEZvclNlcnZpY2VzRHJhaW5lZChjbHVzdGVyTmFtZSwgY2FwYWNpdHlQcm92aWRlck5hbWUpO1xuXG4gICAgICAvLyBEaXNhc3NvY2lhdGUgY2FwYWNpdHkgcHJvdmlkZXIgZnJvbSBjbHVzdGVyXG4gICAgICBhd2FpdCBkaXNhc3NvY2lhdGVDYXBhY2l0eVByb3ZpZGVyKGNsdXN0ZXJOYW1lLCBjYXBhY2l0eVByb3ZpZGVyTmFtZSk7XG4gICAgfVxuXG4gICAgLy8gUmV0dXJuIHN1Y2Nlc3MgZm9yIGFsbCByZXF1ZXN0IHR5cGVzXG4gICAgcmV0dXJuIHtcbiAgICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogXFxgXFwke2NsdXN0ZXJOYW1lfS1cXCR7Y2FwYWNpdHlQcm92aWRlck5hbWV9LWRyYWluLXdhaXRlclxcYCxcbiAgICAgIERhdGE6IHtcbiAgICAgICAgTWVzc2FnZTogXFxgXFwke3JlcXVlc3RUeXBlfSBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5XFxgXG4gICAgICB9XG4gICAgfTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBjb25zb2xlLmVycm9yKFwiRXJyb3I6XCIsIGVycm9yKTtcbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxufTtcbmA7XG5cbiAgICBuZXcgQ3VzdG9tUmVzb3VyY2UodGhpcywgXCJEcmFpbldhaXRlclwiLCB7XG4gICAgICBydW50aW1lOiBSdW50aW1lLk5PREVKU18yMl9YLFxuICAgICAgaW5saW5lQ29kZTogaGFuZGxlckNvZGUsXG4gICAgICB0aW1lb3V0OiBEdXJhdGlvbi5taW51dGVzKDYpLFxuICAgICAgbGFtYmRhRGVzY3JpcHRpb246IGBXYWl0cyBmb3IgRUNTIHNlcnZpY2VzIHRvIGRyYWluIGJlZm9yZSBjYXBhY2l0eSBwcm92aWRlciBkaXNhc3NvY2lhdGlvbmAsXG4gICAgICByb2xlRGVzY3JpcHRpb246IGBSb2xlIGZvciAke2NsdXN0ZXJOYW1lfSBjYXBhY2l0eSBwcm92aWRlciBkcmFpbiB3YWl0ZXJgLFxuICAgICAgaW5saW5lUG9saWN5OiBbXG4gICAgICAgIG5ldyBQb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgIGVmZmVjdDogRWZmZWN0LkFMTE9XLFxuICAgICAgICAgIGFjdGlvbnM6IFtcbiAgICAgICAgICAgIFwiZWNzOkxpc3RTZXJ2aWNlc1wiLFxuICAgICAgICAgICAgXCJlY3M6RGVzY3JpYmVTZXJ2aWNlc1wiLFxuICAgICAgICAgICAgXCJlY3M6RGVzY3JpYmVDbHVzdGVyc1wiLFxuICAgICAgICAgICAgXCJlY3M6UHV0Q2x1c3RlckNhcGFjaXR5UHJvdmlkZXJzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHJlc291cmNlczogW1wiKlwiXVxuICAgICAgICB9KVxuICAgICAgXSxcbiAgICAgIHByb3BlcnRpZXM6IHtcbiAgICAgICAgQ2x1c3Rlck5hbWU6IGNsdXN0ZXJOYW1lLFxuICAgICAgICBDYXBhY2l0eVByb3ZpZGVyTmFtZTogY2FwYWNpdHlQcm92aWRlck5hbWVcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxufVxuIl19
@@ -1,33 +0,0 @@
1
- import { Duration } from "aws-cdk-lib";
2
- import { Construct } from "constructs";
3
- import { CustomResource } from "../utilities/customResource";
4
- /**
5
- * RDS Deletion Waiter - Ensures RDS instance is stable before deletion
6
- *
7
- * Problem: When an RDS read replica is deleted, the primary database enters
8
- * a "modifying" state as it updates its replication configuration. If CloudFormation
9
- * immediately tries to delete the primary with a final snapshot, it fails because
10
- * snapshots require the instance to be in "available" state.
11
- *
12
- * Solution: This custom resource acts as a dependency gate. When CloudFormation
13
- * deletes resources in reverse dependency order:
14
- * 1. Read replica is deleted (triggers primary to enter "modifying" state)
15
- * 2. This waiter's onDelete is invoked - uses AWS SDK waiter until primary is "available"
16
- * 3. Primary database can now be safely deleted with a final snapshot
17
- *
18
- */
19
- interface RdsDeletionWaiterProps {
20
- /**
21
- * The RDS instance identifier to wait for during deletion
22
- */
23
- instanceIdentifier: string;
24
- /**
25
- * Maximum time to wait for the instance to become available (default: 10 minutes)
26
- */
27
- timeout?: Duration;
28
- }
29
- export declare class RdsDeletionWaiter extends Construct {
30
- readonly customResource: CustomResource;
31
- constructor(scope: Construct, id: string, props: RdsDeletionWaiterProps);
32
- }
33
- export {};