@sitblueprint/website-construct 0.1.5 → 0.1.6
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/README.md +83 -0
- package/docs/CHANGELOG.md +20 -0
- package/lambda/index.d.ts +11 -0
- package/lambda/index.js +238 -0
- package/lambda/index.ts +335 -0
- package/lib/index.d.ts +39 -0
- package/lib/index.js +139 -2
- package/lib/index.ts +214 -0
- package/package.json +3 -3
- package/test/website-construct.test.js +127 -1
- package/test/website-construct.test.ts +140 -0
package/lambda/index.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
type APIGatewayProxyEvent = {
|
|
2
|
+
body: string | null;
|
|
3
|
+
path: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type APIGatewayProxyResult = {
|
|
7
|
+
statusCode: number;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const AWS = require("aws-sdk");
|
|
13
|
+
|
|
14
|
+
type SlotDefinition = {
|
|
15
|
+
slotId: number;
|
|
16
|
+
bucketName: string;
|
|
17
|
+
previewUrl: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type SlotLease = {
|
|
21
|
+
slotId: string;
|
|
22
|
+
repoPrKey: string;
|
|
23
|
+
bucketName: string;
|
|
24
|
+
previewUrl: string;
|
|
25
|
+
lastUsedAt?: number;
|
|
26
|
+
leaseExpiresAt?: number;
|
|
27
|
+
commitSha?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ddb = new AWS.DynamoDB.DocumentClient();
|
|
31
|
+
const tableName = process.env.TABLE_NAME ?? "";
|
|
32
|
+
const slotDefinitions: SlotDefinition[] = JSON.parse(
|
|
33
|
+
process.env.SLOT_DEFINITIONS ?? "[]",
|
|
34
|
+
);
|
|
35
|
+
const maxLeaseMs = Number(process.env.MAX_LEASE_MS ?? "86400000");
|
|
36
|
+
|
|
37
|
+
const ok = (body: Record<string, unknown>): APIGatewayProxyResult => ({
|
|
38
|
+
statusCode: 200,
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const badRequest = (message: string): APIGatewayProxyResult => ({
|
|
44
|
+
statusCode: 400,
|
|
45
|
+
headers: { "content-type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ error: message }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const conflict = (message: string): APIGatewayProxyResult => ({
|
|
50
|
+
statusCode: 409,
|
|
51
|
+
headers: { "content-type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ error: message }),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const toRepoPrKey = (repo: string, prNumber: number): string =>
|
|
56
|
+
`${repo}#${prNumber}`;
|
|
57
|
+
|
|
58
|
+
const nowMs = (): number => Date.now();
|
|
59
|
+
const nowEpochSeconds = (): number => Math.floor(Date.now() / 1000);
|
|
60
|
+
|
|
61
|
+
const isConditionalCheckFailure = (error: unknown): boolean => {
|
|
62
|
+
if (!(error instanceof Error)) return false;
|
|
63
|
+
return error.name === "ConditionalCheckFailedException";
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const parseBody = (event: APIGatewayProxyEvent): Record<string, unknown> => {
|
|
67
|
+
if (!event.body) return {};
|
|
68
|
+
return JSON.parse(event.body) as Record<string, unknown>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const getClaimBody = (body: Record<string, unknown>) => {
|
|
72
|
+
const repo = typeof body.repo === "string" ? body.repo : "";
|
|
73
|
+
const prNumber = Number(body.prNumber);
|
|
74
|
+
const commitSha = typeof body.commitSha === "string" ? body.commitSha : "";
|
|
75
|
+
return { repo, prNumber, commitSha };
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getReleaseBody = (body: Record<string, unknown>) => {
|
|
79
|
+
const repo = typeof body.repo === "string" ? body.repo : "";
|
|
80
|
+
const prNumber = Number(body.prNumber);
|
|
81
|
+
return { repo, prNumber };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const queryLeaseByRepoPr = async (
|
|
85
|
+
repoPrKey: string,
|
|
86
|
+
): Promise<SlotLease | null> => {
|
|
87
|
+
const result = await ddb
|
|
88
|
+
.query({
|
|
89
|
+
TableName: tableName,
|
|
90
|
+
IndexName: "RepoPrKeyIndex",
|
|
91
|
+
KeyConditionExpression: "repoPrKey = :repoPrKey",
|
|
92
|
+
ExpressionAttributeValues: {
|
|
93
|
+
":repoPrKey": repoPrKey,
|
|
94
|
+
},
|
|
95
|
+
Limit: 1,
|
|
96
|
+
})
|
|
97
|
+
.promise();
|
|
98
|
+
|
|
99
|
+
if (!result.Items || result.Items.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result.Items[0] as SlotLease;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const loadSlots = async (): Promise<
|
|
107
|
+
Array<SlotDefinition & { lease: SlotLease | null }>
|
|
108
|
+
> => {
|
|
109
|
+
if (slotDefinitions.length === 0) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const result = await ddb
|
|
114
|
+
.batchGet({
|
|
115
|
+
RequestItems: {
|
|
116
|
+
[tableName]: {
|
|
117
|
+
Keys: slotDefinitions.map((slot) => ({
|
|
118
|
+
slotId: String(slot.slotId),
|
|
119
|
+
})),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
.promise();
|
|
124
|
+
|
|
125
|
+
const tableItems = (result.Responses?.[tableName] ?? []) as SlotLease[];
|
|
126
|
+
const bySlotId = new Map<string, SlotLease>();
|
|
127
|
+
tableItems.forEach((item) => bySlotId.set(item.slotId, item));
|
|
128
|
+
|
|
129
|
+
return slotDefinitions.map((slot) => ({
|
|
130
|
+
...slot,
|
|
131
|
+
lease: bySlotId.get(String(slot.slotId)) ?? null,
|
|
132
|
+
}));
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const chooseSlot = (
|
|
136
|
+
slots: Array<SlotDefinition & { lease: SlotLease | null }>,
|
|
137
|
+
repoPrKey: string,
|
|
138
|
+
now: number,
|
|
139
|
+
): {
|
|
140
|
+
slot: SlotDefinition & { lease: SlotLease | null };
|
|
141
|
+
expectedLastUsedAt: number | null;
|
|
142
|
+
} => {
|
|
143
|
+
const existing = slots.find((slot) => slot.lease?.repoPrKey === repoPrKey);
|
|
144
|
+
if (existing) {
|
|
145
|
+
return {
|
|
146
|
+
slot: existing,
|
|
147
|
+
expectedLastUsedAt: Number(existing.lease?.lastUsedAt ?? 0),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const available = slots
|
|
152
|
+
.filter(
|
|
153
|
+
(slot) => !slot.lease || Number(slot.lease.leaseExpiresAt ?? 0) < now,
|
|
154
|
+
)
|
|
155
|
+
.sort(
|
|
156
|
+
(a, b) =>
|
|
157
|
+
Number(a.lease?.lastUsedAt ?? 0) - Number(b.lease?.lastUsedAt ?? 0),
|
|
158
|
+
);
|
|
159
|
+
if (available.length > 0) {
|
|
160
|
+
return {
|
|
161
|
+
slot: available[0],
|
|
162
|
+
expectedLastUsedAt: available[0].lease
|
|
163
|
+
? Number(available[0].lease.lastUsedAt ?? 0)
|
|
164
|
+
: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const lru = [...slots].sort(
|
|
169
|
+
(a, b) =>
|
|
170
|
+
Number(a.lease?.lastUsedAt ?? 0) - Number(b.lease?.lastUsedAt ?? 0),
|
|
171
|
+
)[0];
|
|
172
|
+
return {
|
|
173
|
+
slot: lru,
|
|
174
|
+
expectedLastUsedAt: Number(lru.lease?.lastUsedAt ?? 0),
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const claim = async (
|
|
179
|
+
repo: string,
|
|
180
|
+
prNumber: number,
|
|
181
|
+
commitSha: string,
|
|
182
|
+
): Promise<APIGatewayProxyResult> => {
|
|
183
|
+
if (!repo || !Number.isInteger(prNumber)) {
|
|
184
|
+
return badRequest("repo and integer prNumber are required");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const repoPrKey = toRepoPrKey(repo, prNumber);
|
|
188
|
+
for (let attempts = 0; attempts < 5; attempts += 1) {
|
|
189
|
+
const now = nowMs();
|
|
190
|
+
const slots = await loadSlots();
|
|
191
|
+
if (slots.length === 0) {
|
|
192
|
+
return badRequest("No preview slots configured");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const selection = chooseSlot(slots, repoPrKey, now);
|
|
196
|
+
const slot = selection.slot;
|
|
197
|
+
|
|
198
|
+
const expressionAttributeValues: Record<string, unknown> = {
|
|
199
|
+
":repo": repo,
|
|
200
|
+
":prNumber": prNumber,
|
|
201
|
+
":repoPrKey": repoPrKey,
|
|
202
|
+
":commitSha": commitSha,
|
|
203
|
+
":now": now,
|
|
204
|
+
":expiresAt": now + maxLeaseMs,
|
|
205
|
+
":ttlEpochSeconds": nowEpochSeconds() + Math.floor(maxLeaseMs / 1000),
|
|
206
|
+
":bucketName": slot.bucketName,
|
|
207
|
+
":previewUrl": slot.previewUrl,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
let conditionExpression = "attribute_not_exists(slotId)";
|
|
211
|
+
if (selection.expectedLastUsedAt !== null) {
|
|
212
|
+
conditionExpression =
|
|
213
|
+
"lastUsedAt = :expectedLastUsedAt OR repoPrKey = :repoPrKey";
|
|
214
|
+
expressionAttributeValues[":expectedLastUsedAt"] =
|
|
215
|
+
selection.expectedLastUsedAt;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await ddb
|
|
220
|
+
.update({
|
|
221
|
+
TableName: tableName,
|
|
222
|
+
Key: { slotId: String(slot.slotId) },
|
|
223
|
+
UpdateExpression:
|
|
224
|
+
"SET repo = :repo, prNumber = :prNumber, repoPrKey = :repoPrKey, commitSha = :commitSha, bucketName = :bucketName, previewUrl = :previewUrl, leasedAt = if_not_exists(leasedAt, :now), lastUsedAt = :now, leaseExpiresAt = :expiresAt, ttlEpochSeconds = :ttlEpochSeconds",
|
|
225
|
+
ConditionExpression: conditionExpression,
|
|
226
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
227
|
+
})
|
|
228
|
+
.promise();
|
|
229
|
+
|
|
230
|
+
return ok({
|
|
231
|
+
slotId: slot.slotId,
|
|
232
|
+
bucketName: slot.bucketName,
|
|
233
|
+
previewUrl: slot.previewUrl,
|
|
234
|
+
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (!isConditionalCheckFailure(error)) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return conflict("Failed to claim preview slot due to concurrent updates");
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const heartbeat = async (
|
|
246
|
+
repo: string,
|
|
247
|
+
prNumber: number,
|
|
248
|
+
commitSha: string,
|
|
249
|
+
): Promise<APIGatewayProxyResult> => {
|
|
250
|
+
if (!repo || !Number.isInteger(prNumber)) {
|
|
251
|
+
return badRequest("repo and integer prNumber are required");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const repoPrKey = toRepoPrKey(repo, prNumber);
|
|
255
|
+
const existing = await queryLeaseByRepoPr(repoPrKey);
|
|
256
|
+
if (!existing) {
|
|
257
|
+
return badRequest("No active lease found for this pull request");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const now = nowMs();
|
|
261
|
+
await ddb
|
|
262
|
+
.update({
|
|
263
|
+
TableName: tableName,
|
|
264
|
+
Key: { slotId: existing.slotId },
|
|
265
|
+
UpdateExpression:
|
|
266
|
+
"SET commitSha = :commitSha, lastUsedAt = :now, leaseExpiresAt = :expiresAt, ttlEpochSeconds = :ttlEpochSeconds",
|
|
267
|
+
ConditionExpression: "repoPrKey = :repoPrKey",
|
|
268
|
+
ExpressionAttributeValues: {
|
|
269
|
+
":repoPrKey": repoPrKey,
|
|
270
|
+
":commitSha": commitSha || existing.commitSha || "",
|
|
271
|
+
":now": now,
|
|
272
|
+
":expiresAt": now + maxLeaseMs,
|
|
273
|
+
":ttlEpochSeconds": nowEpochSeconds() + Math.floor(maxLeaseMs / 1000),
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
.promise();
|
|
277
|
+
|
|
278
|
+
return ok({
|
|
279
|
+
slotId: existing.slotId,
|
|
280
|
+
bucketName: existing.bucketName,
|
|
281
|
+
previewUrl: existing.previewUrl,
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const release = async (
|
|
286
|
+
repo: string,
|
|
287
|
+
prNumber: number,
|
|
288
|
+
): Promise<APIGatewayProxyResult> => {
|
|
289
|
+
if (!repo || !Number.isInteger(prNumber)) {
|
|
290
|
+
return badRequest("repo and integer prNumber are required");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const repoPrKey = toRepoPrKey(repo, prNumber);
|
|
294
|
+
const existing = await queryLeaseByRepoPr(repoPrKey);
|
|
295
|
+
if (!existing) {
|
|
296
|
+
return ok({ released: false });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await ddb
|
|
300
|
+
.delete({
|
|
301
|
+
TableName: tableName,
|
|
302
|
+
Key: { slotId: existing.slotId },
|
|
303
|
+
ConditionExpression: "repoPrKey = :repoPrKey",
|
|
304
|
+
ExpressionAttributeValues: {
|
|
305
|
+
":repoPrKey": repoPrKey,
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
.promise();
|
|
309
|
+
|
|
310
|
+
return ok({ released: true, slotId: existing.slotId });
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const handler = async (
|
|
314
|
+
event: APIGatewayProxyEvent,
|
|
315
|
+
): Promise<APIGatewayProxyResult> => {
|
|
316
|
+
const body = parseBody(event);
|
|
317
|
+
const path = event.path ?? "";
|
|
318
|
+
|
|
319
|
+
if (path.endsWith("/claim")) {
|
|
320
|
+
const { repo, prNumber, commitSha } = getClaimBody(body);
|
|
321
|
+
return claim(repo, prNumber, commitSha);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (path.endsWith("/heartbeat")) {
|
|
325
|
+
const { repo, prNumber, commitSha } = getClaimBody(body);
|
|
326
|
+
return heartbeat(repo, prNumber, commitSha);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (path.endsWith("/release")) {
|
|
330
|
+
const { repo, prNumber } = getReleaseBody(body);
|
|
331
|
+
return release(repo, prNumber);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return badRequest("Unsupported route");
|
|
335
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import * as cloudfont from "aws-cdk-lib/aws-cloudfront";
|
|
4
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
5
|
+
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
6
|
+
import * as apigateway from "aws-cdk-lib/aws-apigateway";
|
|
4
7
|
export interface DomainConfig {
|
|
5
8
|
/** The root domain name (e.g., example.com).
|
|
6
9
|
* There must be an associated hosted zone in Route 53 for this domain.
|
|
@@ -27,11 +30,47 @@ export interface WebsiteProps {
|
|
|
27
30
|
domainConfig?: DomainConfig;
|
|
28
31
|
/** Optional path to a custom 404 page. If not specified, the error file will be used. */
|
|
29
32
|
notFoundResponsePagePath?: string;
|
|
33
|
+
/** Optional configuration for pull request preview environments. */
|
|
34
|
+
previewConfig?: PreviewConfig;
|
|
35
|
+
}
|
|
36
|
+
export interface PreviewConfig {
|
|
37
|
+
/** Prefix used to name preview buckets. Buckets are created as `${prefix}-0`, `${prefix}-1`, ... */
|
|
38
|
+
bucketPrefix: string;
|
|
39
|
+
/** Number of preview buckets to create.
|
|
40
|
+
* @default 2
|
|
41
|
+
*/
|
|
42
|
+
bucketCount?: number;
|
|
43
|
+
/** If true, creates one CloudFront distribution per preview bucket.
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
createDistributions?: boolean;
|
|
47
|
+
/** Maximum lease lifetime in hours before a slot is considered expired.
|
|
48
|
+
* @default 24
|
|
49
|
+
*/
|
|
50
|
+
maxLeaseHours?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface PreviewEnvironmentProps extends PreviewConfig {
|
|
53
|
+
/** Index document for preview buckets. */
|
|
54
|
+
indexFile: string;
|
|
55
|
+
/** Error document for preview buckets. */
|
|
56
|
+
errorFile: string;
|
|
30
57
|
}
|
|
31
58
|
export declare class Website extends Construct {
|
|
32
59
|
readonly bucket: s3.Bucket;
|
|
33
60
|
readonly distribution: cloudfont.Distribution;
|
|
61
|
+
readonly previewEnvironment?: PreviewEnvironment;
|
|
34
62
|
constructor(scope: Construct, id: string, props: WebsiteProps);
|
|
35
63
|
private _getFullDomainName;
|
|
36
64
|
private _getCertificate;
|
|
37
65
|
}
|
|
66
|
+
export declare class PreviewEnvironment extends Construct {
|
|
67
|
+
readonly buckets: s3.Bucket[];
|
|
68
|
+
readonly distributions: cloudfont.Distribution[];
|
|
69
|
+
readonly leaseTable: dynamodb.Table;
|
|
70
|
+
readonly api: apigateway.RestApi;
|
|
71
|
+
readonly claimEndpoint: string;
|
|
72
|
+
readonly heartbeatEndpoint: string;
|
|
73
|
+
readonly releaseEndpoint: string;
|
|
74
|
+
constructor(scope: Construct, id: string, props: PreviewEnvironmentProps);
|
|
75
|
+
grantDeploymentAccess(grantee: iam.IGrantable): void;
|
|
76
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Website = void 0;
|
|
36
|
+
exports.PreviewEnvironment = exports.Website = void 0;
|
|
37
37
|
const constructs_1 = require("constructs");
|
|
38
38
|
const s3 = __importStar(require("aws-cdk-lib/aws-s3"));
|
|
39
39
|
const cloudfont = __importStar(require("aws-cdk-lib/aws-cloudfront"));
|
|
@@ -42,9 +42,14 @@ const certificatemanager = __importStar(require("aws-cdk-lib/aws-certificatemana
|
|
|
42
42
|
const cdk = __importStar(require("aws-cdk-lib"));
|
|
43
43
|
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
|
44
44
|
const route53 = __importStar(require("aws-cdk-lib/aws-route53"));
|
|
45
|
+
const dynamodb = __importStar(require("aws-cdk-lib/aws-dynamodb"));
|
|
46
|
+
const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
|
|
47
|
+
const apigateway = __importStar(require("aws-cdk-lib/aws-apigateway"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
45
49
|
class Website extends constructs_1.Construct {
|
|
46
50
|
bucket;
|
|
47
51
|
distribution;
|
|
52
|
+
previewEnvironment;
|
|
48
53
|
constructor(scope, id, props) {
|
|
49
54
|
super(scope, id);
|
|
50
55
|
this.bucket = new s3.Bucket(this, props.bucketName, {
|
|
@@ -126,6 +131,13 @@ class Website extends constructs_1.Construct {
|
|
|
126
131
|
description: "Website URL",
|
|
127
132
|
});
|
|
128
133
|
}
|
|
134
|
+
if (props.previewConfig) {
|
|
135
|
+
this.previewEnvironment = new PreviewEnvironment(this, "PreviewEnvironment", {
|
|
136
|
+
...props.previewConfig,
|
|
137
|
+
indexFile: props.indexFile,
|
|
138
|
+
errorFile: props.errorFile,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
129
141
|
}
|
|
130
142
|
_getFullDomainName(domainConfig) {
|
|
131
143
|
return domainConfig.subdomainName
|
|
@@ -137,4 +149,129 @@ class Website extends constructs_1.Construct {
|
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
151
|
exports.Website = Website;
|
|
140
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwyQ0FBdUM7QUFDdkMsdURBQXlDO0FBQ3pDLHNFQUF3RDtBQUN4RCw0RUFBOEQ7QUFDOUQsdUZBQXlFO0FBQ3pFLGlEQUFtQztBQUNuQyx5REFBMkM7QUFDM0MsaUVBQW1EO0FBbUNuRCxNQUFhLE9BQVEsU0FBUSxzQkFBUztJQUNwQixNQUFNLENBQVk7SUFDbEIsWUFBWSxDQUF5QjtJQUVyRCxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQW1CO1FBQzNELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDakIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxVQUFVLEVBQUU7WUFDbEQsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLFNBQVM7WUFDckMsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLFNBQVM7WUFDckMsZ0JBQWdCLEVBQUUsSUFBSTtZQUN0QixhQUFhLEVBQUUsR0FBRyxDQUFDLGFBQWEsQ0FBQyxPQUFPO1lBQ3hDLGlCQUFpQixFQUFFLEVBQUUsQ0FBQyxpQkFBaUIsQ0FBQyxlQUFlO1lBQ3ZELGFBQWEsRUFBRSxFQUFFLENBQUMsbUJBQW1CLENBQUMseUJBQXlCO1lBQy9ELFVBQVUsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsVUFBVTtTQUMzQyxDQUFDLENBQUM7UUFDSCxNQUFNLEdBQUcsR0FBRyxJQUFJLFNBQVMsQ0FBQyxvQkFBb0IsQ0FDNUMsSUFBSSxFQUNKLEdBQUcsS0FBSyxDQUFDLFVBQVUsTUFBTSxDQUMxQixDQUFDO1FBQ0YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FDN0IsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDO1lBQ3RCLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQztZQUN6QixTQUFTLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUMzQyxVQUFVLEVBQUU7Z0JBQ1YsSUFBSSxHQUFHLENBQUMsc0JBQXNCLENBQzVCLEdBQUcsQ0FBQywrQ0FBK0MsQ0FDcEQ7YUFDRjtTQUNGLENBQUMsQ0FDSCxDQUFDO1FBQ0YsTUFBTSxXQUFXLEdBQWEsRUFBRSxDQUFDO1FBQ2pDLElBQUksS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3ZCLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDO1lBQzlELElBQ0UsS0FBSyxDQUFDLFlBQVksQ0FBQyxpQkFBaUI7Z0JBQ3BDLEtBQUssQ0FBQyxZQUFZLENBQUMsYUFBYSxFQUNoQyxDQUFDO2dCQUNELFdBQVcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUNsRCxDQUFDO1FBQ0gsQ0FBQztRQUVELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxTQUFTLENBQUMsWUFBWSxDQUM1QyxJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxlQUFlLEVBQ2xDO1lBQ0UsZUFBZSxFQUFFO2dCQUNmLE1BQU0sRUFBRSxJQUFJLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO2dCQUN0RCxvQkFBb0IsRUFDbEIsU0FBUyxDQUFDLG9CQUFvQixDQUFDLGlCQUFpQjthQUNuRDtZQUNELGNBQWMsRUFBRTtnQkFDZDtvQkFDRSxVQUFVLEVBQUUsR0FBRztvQkFDZixrQkFBa0IsRUFBRSxHQUFHO29CQUN2QixnQkFBZ0IsRUFBRSxLQUFLLENBQUMsd0JBQXdCLElBQUksV0FBVztvQkFDL0QsR0FBRyxFQUFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztpQkFDOUI7YUFDRjtZQUNELFVBQVUsRUFBRSxTQUFTLENBQUMsVUFBVSxDQUFDLGVBQWU7WUFDaEQsR0FBRyxDQUFDLEtBQUssQ0FBQyxZQUFZO2dCQUNwQixDQUFDLENBQUM7b0JBQ0UsV0FBVyxFQUFFLFdBQVc7b0JBQ3hCLFdBQVcsRUFBRSxJQUFJLENBQUMsZUFBZSxDQUMvQixLQUFLLENBQUMsWUFBWSxDQUFDLGNBQWMsQ0FDbEM7aUJBQ0Y7Z0JBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUNSLENBQ0YsQ0FBQztRQUVGLElBQUksS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxZQUFZLEVBQUU7Z0JBQ25FLFVBQVUsRUFBRSxLQUFLLENBQUMsWUFBWSxDQUFDLFVBQVU7YUFDMUMsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxhQUFhLEdBQUcsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxlQUFlLEVBQUU7Z0JBQy9ELElBQUksRUFBRSxVQUFVO2dCQUNoQixVQUFVLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUM7Z0JBQ3ZELE1BQU0sRUFBRSxHQUFHLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxTQUFTLENBQzVDLElBQUksR0FBRyxDQUFDLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FDaEU7YUFDRixDQUFDLENBQUM7WUFDSCxhQUFhLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7WUFFcEQsSUFDRSxLQUFLLENBQUMsWUFBWSxDQUFDLGlCQUFpQjtnQkFDcEMsS0FBSyxDQUFDLFlBQVksQ0FBQyxhQUFhLEVBQ2hDLENBQUM7Z0JBQ0QsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxtQkFBbUIsRUFBRTtvQkFDN0MsSUFBSSxFQUFFLFVBQVU7b0JBQ2hCLFVBQVUsRUFBRSxLQUFLLENBQUMsWUFBWSxDQUFDLFVBQVU7b0JBQ3pDLE1BQU0sRUFBRSxHQUFHLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxTQUFTLENBQzVDLElBQUksR0FBRyxDQUFDLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FDaEU7aUJBQ0YsQ0FBQyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzNDLENBQUM7UUFDSCxDQUFDO1FBRUQsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSx3QkFBd0IsRUFBRTtZQUNoRCxLQUFLLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxzQkFBc0I7WUFDL0MsV0FBVyxFQUFFLHFDQUFxQztTQUNuRCxDQUFDLENBQUM7UUFFSCxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLGdCQUFnQixFQUFFO1lBQ3hDLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLGdCQUFnQjtZQUNuQyxXQUFXLEVBQUUsdUJBQXVCO1NBQ3JDLENBQUMsQ0FBQztRQUVILElBQUksS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3ZCLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsYUFBYSxFQUFFO2dCQUNyQyxLQUFLLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxVQUFVO2dCQUNuQyxXQUFXLEVBQUUsYUFBYTthQUMzQixDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUVPLGtCQUFrQixDQUFDLFlBQTBCO1FBQ25ELE9BQU8sWUFBWSxDQUFDLGFBQWE7WUFDL0IsQ0FBQyxDQUFDLEdBQUcsWUFBWSxDQUFDLGFBQWEsSUFBSSxZQUFZLENBQUMsVUFBVSxFQUFFO1lBQzVELENBQUMsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDO0lBQzlCLENBQUM7SUFFTyxlQUFlLENBQUMsR0FBVztRQUNqQyxPQUFPLGtCQUFrQixDQUFDLFdBQVcsQ0FBQyxrQkFBa0IsQ0FDdEQsSUFBSSxFQUNKLGNBQWMsRUFDZCxHQUFHLENBQ0osQ0FBQztJQUNKLENBQUM7Q0FDRjtBQWhJRCwwQkFnSUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0ICogYXMgczMgZnJvbSBcImF3cy1jZGstbGliL2F3cy1zM1wiO1xuaW1wb3J0ICogYXMgY2xvdWRmb250IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2xvdWRmcm9udFwiO1xuaW1wb3J0ICogYXMgb3JpZ2lucyBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWNsb3VkZnJvbnQtb3JpZ2luc1wiO1xuaW1wb3J0ICogYXMgY2VydGlmaWNhdGVtYW5hZ2VyIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2VydGlmaWNhdGVtYW5hZ2VyXCI7XG5pbXBvcnQgKiBhcyBjZGsgZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgKiBhcyBpYW0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1pYW1cIjtcbmltcG9ydCAqIGFzIHJvdXRlNTMgZnJvbSBcImF3cy1jZGstbGliL2F3cy1yb3V0ZTUzXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRG9tYWluQ29uZmlnIHtcbiAgLyoqIFRoZSByb290IGRvbWFpbiBuYW1lIChlLmcuLCBleGFtcGxlLmNvbSkuXG4gICAqIFRoZXJlIG11c3QgYmUgYW4gYXNzb2NpYXRlZCBob3N0ZWQgem9uZSBpbiBSb3V0ZSA1MyBmb3IgdGhpcyBkb21haW4uXG4gICAqL1xuICBkb21haW5OYW1lOiBzdHJpbmc7XG4gIC8qKiBUaGUgc3ViZG9tYWluIG5hbWUgKi9cbiAgc3ViZG9tYWluTmFtZTogc3RyaW5nO1xuICAvKiogVGhlIEFSTiBvZiB0aGUgU1NMIGNlcnRpZmljYXRlIHRvIHVzZSBmb3IgdGhlIGRvbWFpbi4gKi9cbiAgY2VydGlmaWNhdGVBcm46IHN0cmluZztcbiAgLyoqXG4gICAqIElmIHRydWUsIGNyZWF0ZXMgYW4gYWRkaXRpb25hbCBSb3V0ZSA1MyByZWNvcmQgZm9yIHRoZSByb290IGRvbWFpbiBwb2ludGluZyB0byB0aGUgQ2xvdWRGcm9udCBkaXN0cmlidXRpb24uXG4gICAqIEBkZWZhdWx0IGZhbHNlXG4gICAqL1xuICBpbmNsdWRlUm9vdERvbWFpbj86IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgV2Vic2l0ZVByb3BzIHtcbiAgLyoqIFRoZSBuYW1lIG9mIHRoZSBTMyBidWNrZXQgdGhhdCB3aWxsIGhvc3QgdGhlIHdlYnNpdGUgY29udGVudC4gKi9cbiAgYnVja2V0TmFtZTogc3RyaW5nO1xuXG4gIC8qKiBUaGUgcGF0aCB0byB0aGUgaW5kZXggZG9jdW1lbnQgdGhhdCB3aWxsIGJlIHNlcnZlZCBhcyB0aGUgZGVmYXVsdCBwYWdlLiAqL1xuICBpbmRleEZpbGU6IHN0cmluZztcblxuICAvKiogVGhlIHBhdGggdG8gdGhlIGVycm9yIGRvY3VtZW50IHRoYXQgd2lsbCBiZSBzZXJ2ZWQgd2hlbiBhbiBlcnJvciBvY2N1cnMuICovXG4gIGVycm9yRmlsZTogc3RyaW5nO1xuXG4gIC8qKiBPcHRpb25hbCBjb25maWd1cmF0aW9uIGZvciBjdXN0b20gZG9tYWluIHNldHVwLiAqL1xuICBkb21haW5Db25maWc/OiBEb21haW5Db25maWc7XG5cbiAgLyoqIE9wdGlvbmFsIHBhdGggdG8gYSBjdXN0b20gNDA0IHBhZ2UuIElmIG5vdCBzcGVjaWZpZWQsIHRoZSBlcnJvciBmaWxlIHdpbGwgYmUgdXNlZC4gKi9cbiAgbm90Rm91bmRSZXNwb25zZVBhZ2VQYXRoPzogc3RyaW5nO1xufVxuXG5leHBvcnQgY2xhc3MgV2Vic2l0ZSBleHRlbmRzIENvbnN0cnVjdCB7XG4gIHB1YmxpYyByZWFkb25seSBidWNrZXQ6IHMzLkJ1Y2tldDtcbiAgcHVibGljIHJlYWRvbmx5IGRpc3RyaWJ1dGlvbjogY2xvdWRmb250LkRpc3RyaWJ1dGlvbjtcblxuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogV2Vic2l0ZVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcbiAgICB0aGlzLmJ1Y2tldCA9IG5ldyBzMy5CdWNrZXQodGhpcywgcHJvcHMuYnVja2V0TmFtZSwge1xuICAgICAgd2Vic2l0ZUluZGV4RG9jdW1lbnQ6IHByb3BzLmluZGV4RmlsZSxcbiAgICAgIHdlYnNpdGVFcnJvckRvY3VtZW50OiBwcm9wcy5lcnJvckZpbGUsXG4gICAgICBwdWJsaWNSZWFkQWNjZXNzOiB0cnVlLFxuICAgICAgcmVtb3ZhbFBvbGljeTogY2RrLlJlbW92YWxQb2xpY3kuREVTVFJPWSxcbiAgICAgIGJsb2NrUHVibGljQWNjZXNzOiBzMy5CbG9ja1B1YmxpY0FjY2Vzcy5CTE9DS19BQ0xTX09OTFksXG4gICAgICBhY2Nlc3NDb250cm9sOiBzMy5CdWNrZXRBY2Nlc3NDb250cm9sLkJVQ0tFVF9PV05FUl9GVUxMX0NPTlRST0wsXG4gICAgICBlbmNyeXB0aW9uOiBzMy5CdWNrZXRFbmNyeXB0aW9uLlMzX01BTkFHRUQsXG4gICAgfSk7XG4gICAgY29uc3Qgb2FpID0gbmV3IGNsb3VkZm9udC5PcmlnaW5BY2Nlc3NJZGVudGl0eShcbiAgICAgIHRoaXMsXG4gICAgICBgJHtwcm9wcy5idWNrZXROYW1lfS1PQUlgLFxuICAgICk7XG4gICAgdGhpcy5idWNrZXQuYWRkVG9SZXNvdXJjZVBvbGljeShcbiAgICAgIG5ldyBpYW0uUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgYWN0aW9uczogW1wiczM6R2V0T2JqZWN0XCJdLFxuICAgICAgICByZXNvdXJjZXM6IFt0aGlzLmJ1Y2tldC5hcm5Gb3JPYmplY3RzKFwiKlwiKV0sXG4gICAgICAgIHByaW5jaXBhbHM6IFtcbiAgICAgICAgICBuZXcgaWFtLkNhbm9uaWNhbFVzZXJQcmluY2lwYWwoXG4gICAgICAgICAgICBvYWkuY2xvdWRGcm9udE9yaWdpbkFjY2Vzc0lkZW50aXR5UzNDYW5vbmljYWxVc2VySWQsXG4gICAgICAgICAgKSxcbiAgICAgICAgXSxcbiAgICAgIH0pLFxuICAgICk7XG4gICAgY29uc3QgZG9tYWluTmFtZXM6IHN0cmluZ1tdID0gW107XG4gICAgaWYgKHByb3BzLmRvbWFpbkNvbmZpZykge1xuICAgICAgZG9tYWluTmFtZXMucHVzaCh0aGlzLl9nZXRGdWxsRG9tYWluTmFtZShwcm9wcy5kb21haW5Db25maWcpKTtcbiAgICAgIGlmIChcbiAgICAgICAgcHJvcHMuZG9tYWluQ29uZmlnLmluY2x1ZGVSb290RG9tYWluICYmXG4gICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5zdWJkb21haW5OYW1lXG4gICAgICApIHtcbiAgICAgICAgZG9tYWluTmFtZXMucHVzaChwcm9wcy5kb21haW5Db25maWcuZG9tYWluTmFtZSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5kaXN0cmlidXRpb24gPSBuZXcgY2xvdWRmb250LkRpc3RyaWJ1dGlvbihcbiAgICAgIHRoaXMsXG4gICAgICBgJHtwcm9wcy5idWNrZXROYW1lfS1kaXN0cmlidXRpb25gLFxuICAgICAge1xuICAgICAgICBkZWZhdWx0QmVoYXZpb3I6IHtcbiAgICAgICAgICBvcmlnaW46IG5ldyBvcmlnaW5zLlMzU3RhdGljV2Vic2l0ZU9yaWdpbih0aGlzLmJ1Y2tldCksXG4gICAgICAgICAgdmlld2VyUHJvdG9jb2xQb2xpY3k6XG4gICAgICAgICAgICBjbG91ZGZvbnQuVmlld2VyUHJvdG9jb2xQb2xpY3kuUkVESVJFQ1RfVE9fSFRUUFMsXG4gICAgICAgIH0sXG4gICAgICAgIGVycm9yUmVzcG9uc2VzOiBbXG4gICAgICAgICAge1xuICAgICAgICAgICAgaHR0cFN0YXR1czogNDA0LFxuICAgICAgICAgICAgcmVzcG9uc2VIdHRwU3RhdHVzOiA0MDQsXG4gICAgICAgICAgICByZXNwb25zZVBhZ2VQYXRoOiBwcm9wcy5ub3RGb3VuZFJlc3BvbnNlUGFnZVBhdGggfHwgYC80MDQuaHRtbGAsXG4gICAgICAgICAgICB0dGw6IGNkay5EdXJhdGlvbi5taW51dGVzKDMwKSxcbiAgICAgICAgICB9LFxuICAgICAgICBdLFxuICAgICAgICBwcmljZUNsYXNzOiBjbG91ZGZvbnQuUHJpY2VDbGFzcy5QUklDRV9DTEFTU18xMDAsXG4gICAgICAgIC4uLihwcm9wcy5kb21haW5Db25maWdcbiAgICAgICAgICA/IHtcbiAgICAgICAgICAgICAgZG9tYWluTmFtZXM6IGRvbWFpbk5hbWVzLFxuICAgICAgICAgICAgICBjZXJ0aWZpY2F0ZTogdGhpcy5fZ2V0Q2VydGlmaWNhdGUoXG4gICAgICAgICAgICAgICAgcHJvcHMuZG9tYWluQ29uZmlnLmNlcnRpZmljYXRlQXJuLFxuICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgfVxuICAgICAgICAgIDoge30pLFxuICAgICAgfSxcbiAgICApO1xuXG4gICAgaWYgKHByb3BzLmRvbWFpbkNvbmZpZykge1xuICAgICAgY29uc3QgaG9zdGVkWm9uZSA9IHJvdXRlNTMuSG9zdGVkWm9uZS5mcm9tTG9va3VwKHRoaXMsIFwiSG9zdGVkWm9uZVwiLCB7XG4gICAgICAgIGRvbWFpbk5hbWU6IHByb3BzLmRvbWFpbkNvbmZpZy5kb21haW5OYW1lLFxuICAgICAgfSk7XG4gICAgICBjb25zdCBkb21haW5BUmVjb3JkID0gbmV3IHJvdXRlNTMuQVJlY29yZCh0aGlzLCBcIkRvbWFpbkFSZWNvcmRcIiwge1xuICAgICAgICB6b25lOiBob3N0ZWRab25lLFxuICAgICAgICByZWNvcmROYW1lOiB0aGlzLl9nZXRGdWxsRG9tYWluTmFtZShwcm9wcy5kb21haW5Db25maWcpLFxuICAgICAgICB0YXJnZXQ6IGNkay5hd3Nfcm91dGU1My5SZWNvcmRUYXJnZXQuZnJvbUFsaWFzKFxuICAgICAgICAgIG5ldyBjZGsuYXdzX3JvdXRlNTNfdGFyZ2V0cy5DbG91ZEZyb250VGFyZ2V0KHRoaXMuZGlzdHJpYnV0aW9uKSxcbiAgICAgICAgKSxcbiAgICAgIH0pO1xuICAgICAgZG9tYWluQVJlY29yZC5ub2RlLmFkZERlcGVuZGVuY3kodGhpcy5kaXN0cmlidXRpb24pO1xuXG4gICAgICBpZiAoXG4gICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5pbmNsdWRlUm9vdERvbWFpbiAmJlxuICAgICAgICBwcm9wcy5kb21haW5Db25maWcuc3ViZG9tYWluTmFtZVxuICAgICAgKSB7XG4gICAgICAgIG5ldyByb3V0ZTUzLkFSZWNvcmQodGhpcywgXCJSb290RG9tYWluQVJlY29yZFwiLCB7XG4gICAgICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgICAgICByZWNvcmROYW1lOiBwcm9wcy5kb21haW5Db25maWcuZG9tYWluTmFtZSxcbiAgICAgICAgICB0YXJnZXQ6IGNkay5hd3Nfcm91dGU1My5SZWNvcmRUYXJnZXQuZnJvbUFsaWFzKFxuICAgICAgICAgICAgbmV3IGNkay5hd3Nfcm91dGU1M190YXJnZXRzLkNsb3VkRnJvbnRUYXJnZXQodGhpcy5kaXN0cmlidXRpb24pLFxuICAgICAgICAgICksXG4gICAgICAgIH0pLm5vZGUuYWRkRGVwZW5kZW5jeSh0aGlzLmRpc3RyaWJ1dGlvbik7XG4gICAgICB9XG4gICAgfVxuXG4gICAgbmV3IGNkay5DZm5PdXRwdXQodGhpcywgXCJjbG91ZGZyb250LXdlYnNpdGUtdXJsXCIsIHtcbiAgICAgIHZhbHVlOiB0aGlzLmRpc3RyaWJ1dGlvbi5kaXN0cmlidXRpb25Eb21haW5OYW1lLFxuICAgICAgZGVzY3JpcHRpb246IFwiQ2xvdWRGcm9udCBEaXN0cmlidXRpb24gRG9tYWluIE5hbWVcIixcbiAgICB9KTtcblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwiczMtd2Vic2l0ZS11cmxcIiwge1xuICAgICAgdmFsdWU6IHRoaXMuYnVja2V0LmJ1Y2tldFdlYnNpdGVVcmwsXG4gICAgICBkZXNjcmlwdGlvbjogXCJTMyBCdWNrZXQgV2Vic2l0ZSBVUkxcIixcbiAgICB9KTtcblxuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwid2Vic2l0ZS11cmxcIiwge1xuICAgICAgICB2YWx1ZTogdGhpcy5kaXN0cmlidXRpb24uZG9tYWluTmFtZSxcbiAgICAgICAgZGVzY3JpcHRpb246IFwiV2Vic2l0ZSBVUkxcIixcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgX2dldEZ1bGxEb21haW5OYW1lKGRvbWFpbkNvbmZpZzogRG9tYWluQ29uZmlnKTogc3RyaW5nIHtcbiAgICByZXR1cm4gZG9tYWluQ29uZmlnLnN1YmRvbWFpbk5hbWVcbiAgICAgID8gYCR7ZG9tYWluQ29uZmlnLnN1YmRvbWFpbk5hbWV9LiR7ZG9tYWluQ29uZmlnLmRvbWFpbk5hbWV9YFxuICAgICAgOiBkb21haW5Db25maWcuZG9tYWluTmFtZTtcbiAgfVxuXG4gIHByaXZhdGUgX2dldENlcnRpZmljYXRlKGFybjogc3RyaW5nKTogY2VydGlmaWNhdGVtYW5hZ2VyLklDZXJ0aWZpY2F0ZSB7XG4gICAgcmV0dXJuIGNlcnRpZmljYXRlbWFuYWdlci5DZXJ0aWZpY2F0ZS5mcm9tQ2VydGlmaWNhdGVBcm4oXG4gICAgICB0aGlzLFxuICAgICAgYHdlYnNpdGUtY2VydGAsXG4gICAgICBhcm4sXG4gICAgKTtcbiAgfVxufVxuIl19
|
|
152
|
+
class PreviewEnvironment extends constructs_1.Construct {
|
|
153
|
+
buckets;
|
|
154
|
+
distributions;
|
|
155
|
+
leaseTable;
|
|
156
|
+
api;
|
|
157
|
+
claimEndpoint;
|
|
158
|
+
heartbeatEndpoint;
|
|
159
|
+
releaseEndpoint;
|
|
160
|
+
constructor(scope, id, props) {
|
|
161
|
+
super(scope, id);
|
|
162
|
+
const bucketCount = props.bucketCount ?? 2;
|
|
163
|
+
if (bucketCount < 1) {
|
|
164
|
+
throw new Error("bucketCount must be greater than or equal to 1");
|
|
165
|
+
}
|
|
166
|
+
const indexFile = props.indexFile;
|
|
167
|
+
const errorFile = props.errorFile;
|
|
168
|
+
const createDistributions = props.createDistributions ?? true;
|
|
169
|
+
const maxLeaseHours = props.maxLeaseHours ?? 24;
|
|
170
|
+
const maxLeaseMs = cdk.Duration.hours(maxLeaseHours).toMilliseconds();
|
|
171
|
+
this.buckets = Array.from({ length: bucketCount }, (_, slotId) => {
|
|
172
|
+
const bucketName = `${props.bucketPrefix}-${slotId}`;
|
|
173
|
+
return new s3.Bucket(this, `PreviewBucket${slotId}`, {
|
|
174
|
+
bucketName,
|
|
175
|
+
websiteIndexDocument: indexFile,
|
|
176
|
+
websiteErrorDocument: errorFile,
|
|
177
|
+
publicReadAccess: true,
|
|
178
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
179
|
+
autoDeleteObjects: true,
|
|
180
|
+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS_ONLY,
|
|
181
|
+
accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
|
|
182
|
+
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
this.distributions = createDistributions
|
|
186
|
+
? this.buckets.map((bucket, slotId) => {
|
|
187
|
+
const oai = new cloudfont.OriginAccessIdentity(this, `PreviewOAI${slotId}`);
|
|
188
|
+
bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
189
|
+
actions: ["s3:GetObject"],
|
|
190
|
+
resources: [bucket.arnForObjects("*")],
|
|
191
|
+
principals: [
|
|
192
|
+
new iam.CanonicalUserPrincipal(oai.cloudFrontOriginAccessIdentityS3CanonicalUserId),
|
|
193
|
+
],
|
|
194
|
+
}));
|
|
195
|
+
return new cloudfont.Distribution(this, `PreviewDistribution${slotId}`, {
|
|
196
|
+
defaultBehavior: {
|
|
197
|
+
origin: new origins.S3StaticWebsiteOrigin(bucket),
|
|
198
|
+
viewerProtocolPolicy: cloudfont.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
199
|
+
},
|
|
200
|
+
priceClass: cloudfont.PriceClass.PRICE_CLASS_100,
|
|
201
|
+
});
|
|
202
|
+
})
|
|
203
|
+
: [];
|
|
204
|
+
this.leaseTable = new dynamodb.Table(this, "PreviewLeases", {
|
|
205
|
+
partitionKey: {
|
|
206
|
+
name: "slotId",
|
|
207
|
+
type: dynamodb.AttributeType.STRING,
|
|
208
|
+
},
|
|
209
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
210
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
211
|
+
timeToLiveAttribute: "ttlEpochSeconds",
|
|
212
|
+
});
|
|
213
|
+
this.leaseTable.addGlobalSecondaryIndex({
|
|
214
|
+
indexName: "RepoPrKeyIndex",
|
|
215
|
+
partitionKey: {
|
|
216
|
+
name: "repoPrKey",
|
|
217
|
+
type: dynamodb.AttributeType.STRING,
|
|
218
|
+
},
|
|
219
|
+
projectionType: dynamodb.ProjectionType.ALL,
|
|
220
|
+
});
|
|
221
|
+
const slotDefinitions = this.buckets.map((bucket, slotId) => ({
|
|
222
|
+
slotId,
|
|
223
|
+
bucketName: bucket.bucketName,
|
|
224
|
+
previewUrl: this.distributions[slotId]
|
|
225
|
+
? `https://${this.distributions[slotId].distributionDomainName}`
|
|
226
|
+
: bucket.bucketWebsiteUrl,
|
|
227
|
+
}));
|
|
228
|
+
const leaseApiHandler = new lambda.Function(this, "PreviewLeaseApiHandler", {
|
|
229
|
+
runtime: lambda.Runtime.NODEJS_20_X,
|
|
230
|
+
handler: "index.handler",
|
|
231
|
+
timeout: cdk.Duration.seconds(15),
|
|
232
|
+
code: lambda.Code.fromAsset(path.join(__dirname, "..", "lambda")),
|
|
233
|
+
environment: {
|
|
234
|
+
TABLE_NAME: this.leaseTable.tableName,
|
|
235
|
+
SLOT_DEFINITIONS: JSON.stringify(slotDefinitions),
|
|
236
|
+
MAX_LEASE_MS: String(maxLeaseMs),
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
this.leaseTable.grantReadWriteData(leaseApiHandler);
|
|
240
|
+
this.api = new apigateway.RestApi(this, "PreviewLeaseApi", {
|
|
241
|
+
restApiName: `${cdk.Names.uniqueId(this)}-preview-lease-api`,
|
|
242
|
+
description: "API for claiming/releasing preview slots",
|
|
243
|
+
});
|
|
244
|
+
const claimResource = this.api.root.addResource("claim");
|
|
245
|
+
claimResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
|
|
246
|
+
const heartbeatResource = this.api.root.addResource("heartbeat");
|
|
247
|
+
heartbeatResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
|
|
248
|
+
const releaseResource = this.api.root.addResource("release");
|
|
249
|
+
releaseResource.addMethod("POST", new apigateway.LambdaIntegration(leaseApiHandler));
|
|
250
|
+
this.claimEndpoint = `${this.api.url}claim`;
|
|
251
|
+
this.heartbeatEndpoint = `${this.api.url}heartbeat`;
|
|
252
|
+
this.releaseEndpoint = `${this.api.url}release`;
|
|
253
|
+
new cdk.CfnOutput(this, "preview-claim-endpoint", {
|
|
254
|
+
value: this.claimEndpoint,
|
|
255
|
+
description: "POST endpoint used to claim a preview slot",
|
|
256
|
+
});
|
|
257
|
+
new cdk.CfnOutput(this, "preview-heartbeat-endpoint", {
|
|
258
|
+
value: this.heartbeatEndpoint,
|
|
259
|
+
description: "POST endpoint used to refresh a preview slot lease",
|
|
260
|
+
});
|
|
261
|
+
new cdk.CfnOutput(this, "preview-release-endpoint", {
|
|
262
|
+
value: this.releaseEndpoint,
|
|
263
|
+
description: "POST endpoint used to release a preview slot",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
grantDeploymentAccess(grantee) {
|
|
267
|
+
this.buckets.forEach((bucket) => bucket.grantReadWrite(grantee));
|
|
268
|
+
this.distributions.forEach((distribution) => {
|
|
269
|
+
grantee.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({
|
|
270
|
+
actions: ["cloudfront:CreateInvalidation"],
|
|
271
|
+
resources: [distribution.distributionArn],
|
|
272
|
+
}));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.PreviewEnvironment = PreviewEnvironment;
|
|
277
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwyQ0FBdUM7QUFDdkMsdURBQXlDO0FBQ3pDLHNFQUF3RDtBQUN4RCw0RUFBOEQ7QUFDOUQsdUZBQXlFO0FBQ3pFLGlEQUFtQztBQUNuQyx5REFBMkM7QUFDM0MsaUVBQW1EO0FBQ25ELG1FQUFxRDtBQUNyRCwrREFBaUQ7QUFDakQsdUVBQXlEO0FBQ3pELDJDQUE2QjtBQWtFN0IsTUFBYSxPQUFRLFNBQVEsc0JBQVM7SUFDcEIsTUFBTSxDQUFZO0lBQ2xCLFlBQVksQ0FBeUI7SUFDckMsa0JBQWtCLENBQXNCO0lBRXhELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBbUI7UUFDM0QsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLFVBQVUsRUFBRTtZQUNsRCxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxvQkFBb0IsRUFBRSxLQUFLLENBQUMsU0FBUztZQUNyQyxnQkFBZ0IsRUFBRSxJQUFJO1lBQ3RCLGFBQWEsRUFBRSxHQUFHLENBQUMsYUFBYSxDQUFDLE9BQU87WUFDeEMsaUJBQWlCLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLGVBQWU7WUFDdkQsYUFBYSxFQUFFLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyx5QkFBeUI7WUFDL0QsVUFBVSxFQUFFLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVO1NBQzNDLENBQUMsQ0FBQztRQUNILE1BQU0sR0FBRyxHQUFHLElBQUksU0FBUyxDQUFDLG9CQUFvQixDQUM1QyxJQUFJLEVBQ0osR0FBRyxLQUFLLENBQUMsVUFBVSxNQUFNLENBQzFCLENBQUM7UUFD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LENBQUMsYUFBYTtZQUN6QixXQUFXLEVBQUUsNENBQTRDO1NBQzFELENBQUMsQ0FBQztRQUNILElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsNEJBQTRCLEVBQUU7WUFDcEQsS0FBSyxFQUFFLElBQUksQ0FBQyxpQkFBaUI7WUFDN0IsV0FBVyxFQUFFLG9EQUFvRDtTQUNsRSxDQUFDLENBQUM7UUFDSCxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLDBCQUEwQixFQUFFO1lBQ2xELEtBQUssRUFBRSxJQUFJLENBQUMsZUFBZTtZQUMzQixXQUFXLEVBQUUsOENBQThDO1NBQzVELENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTSxxQkFBcUIsQ0FBQyxPQUF1QjtRQUNsRCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQ2pFLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7WUFDMUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxvQkFBb0IsQ0FDekMsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDO2dCQUN0QixPQUFPLEVBQUUsQ0FBQywrQkFBK0IsQ0FBQztnQkFDMUMsU0FBUyxFQUFFLENBQUMsWUFBWSxDQUFDLGVBQWUsQ0FBQzthQUMxQyxDQUFDLENBQ0gsQ0FBQztRQUNKLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBcEtELGdEQW9LQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCI7XG5pbXBvcnQgKiBhcyBzMyBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXMzXCI7XG5pbXBvcnQgKiBhcyBjbG91ZGZvbnQgZnJvbSBcImF3cy1jZGstbGliL2F3cy1jbG91ZGZyb250XCI7XG5pbXBvcnQgKiBhcyBvcmlnaW5zIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtY2xvdWRmcm9udC1vcmlnaW5zXCI7XG5pbXBvcnQgKiBhcyBjZXJ0aWZpY2F0ZW1hbmFnZXIgZnJvbSBcImF3cy1jZGstbGliL2F3cy1jZXJ0aWZpY2F0ZW1hbmFnZXJcIjtcbmltcG9ydCAqIGFzIGNkayBmcm9tIFwiYXdzLWNkay1saWJcIjtcbmltcG9ydCAqIGFzIGlhbSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWlhbVwiO1xuaW1wb3J0ICogYXMgcm91dGU1MyBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcbmltcG9ydCAqIGFzIGR5bmFtb2RiIGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtZHluYW1vZGJcIjtcbmltcG9ydCAqIGFzIGxhbWJkYSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWxhbWJkYVwiO1xuaW1wb3J0ICogYXMgYXBpZ2F0ZXdheSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLWFwaWdhdGV3YXlcIjtcbmltcG9ydCAqIGFzIHBhdGggZnJvbSBcInBhdGhcIjtcblxuZXhwb3J0IGludGVyZmFjZSBEb21haW5Db25maWcge1xuICAvKiogVGhlIHJvb3QgZG9tYWluIG5hbWUgKGUuZy4sIGV4YW1wbGUuY29tKS5cbiAgICogVGhlcmUgbXVzdCBiZSBhbiBhc3NvY2lhdGVkIGhvc3RlZCB6b25lIGluIFJvdXRlIDUzIGZvciB0aGlzIGRvbWFpbi5cbiAgICovXG4gIGRvbWFpbk5hbWU6IHN0cmluZztcbiAgLyoqIFRoZSBzdWJkb21haW4gbmFtZSAqL1xuICBzdWJkb21haW5OYW1lOiBzdHJpbmc7XG4gIC8qKiBUaGUgQVJOIG9mIHRoZSBTU0wgY2VydGlmaWNhdGUgdG8gdXNlIGZvciB0aGUgZG9tYWluLiAqL1xuICBjZXJ0aWZpY2F0ZUFybjogc3RyaW5nO1xuICAvKipcbiAgICogSWYgdHJ1ZSwgY3JlYXRlcyBhbiBhZGRpdGlvbmFsIFJvdXRlIDUzIHJlY29yZCBmb3IgdGhlIHJvb3QgZG9tYWluIHBvaW50aW5nIHRvIHRoZSBDbG91ZEZyb250IGRpc3RyaWJ1dGlvbi5cbiAgICogQGRlZmF1bHQgZmFsc2VcbiAgICovXG4gIGluY2x1ZGVSb290RG9tYWluPzogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBXZWJzaXRlUHJvcHMge1xuICAvKiogVGhlIG5hbWUgb2YgdGhlIFMzIGJ1Y2tldCB0aGF0IHdpbGwgaG9zdCB0aGUgd2Vic2l0ZSBjb250ZW50LiAqL1xuICBidWNrZXROYW1lOiBzdHJpbmc7XG5cbiAgLyoqIFRoZSBwYXRoIHRvIHRoZSBpbmRleCBkb2N1bWVudCB0aGF0IHdpbGwgYmUgc2VydmVkIGFzIHRoZSBkZWZhdWx0IHBhZ2UuICovXG4gIGluZGV4RmlsZTogc3RyaW5nO1xuXG4gIC8qKiBUaGUgcGF0aCB0byB0aGUgZXJyb3IgZG9jdW1lbnQgdGhhdCB3aWxsIGJlIHNlcnZlZCB3aGVuIGFuIGVycm9yIG9jY3Vycy4gKi9cbiAgZXJyb3JGaWxlOiBzdHJpbmc7XG5cbiAgLyoqIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gZm9yIGN1c3RvbSBkb21haW4gc2V0dXAuICovXG4gIGRvbWFpbkNvbmZpZz86IERvbWFpbkNvbmZpZztcblxuICAvKiogT3B0aW9uYWwgcGF0aCB0byBhIGN1c3RvbSA0MDQgcGFnZS4gSWYgbm90IHNwZWNpZmllZCwgdGhlIGVycm9yIGZpbGUgd2lsbCBiZSB1c2VkLiAqL1xuICBub3RGb3VuZFJlc3BvbnNlUGFnZVBhdGg/OiBzdHJpbmc7XG5cbiAgLyoqIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gZm9yIHB1bGwgcmVxdWVzdCBwcmV2aWV3IGVudmlyb25tZW50cy4gKi9cbiAgcHJldmlld0NvbmZpZz86IFByZXZpZXdDb25maWc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld0NvbmZpZyB7XG4gIC8qKiBQcmVmaXggdXNlZCB0byBuYW1lIHByZXZpZXcgYnVja2V0cy4gQnVja2V0cyBhcmUgY3JlYXRlZCBhcyBgJHtwcmVmaXh9LTBgLCBgJHtwcmVmaXh9LTFgLCAuLi4gKi9cbiAgYnVja2V0UHJlZml4OiBzdHJpbmc7XG5cbiAgLyoqIE51bWJlciBvZiBwcmV2aWV3IGJ1Y2tldHMgdG8gY3JlYXRlLlxuICAgKiBAZGVmYXVsdCAyXG4gICAqL1xuICBidWNrZXRDb3VudD86IG51bWJlcjtcblxuICAvKiogSWYgdHJ1ZSwgY3JlYXRlcyBvbmUgQ2xvdWRGcm9udCBkaXN0cmlidXRpb24gcGVyIHByZXZpZXcgYnVja2V0LlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICBjcmVhdGVEaXN0cmlidXRpb25zPzogYm9vbGVhbjtcblxuICAvKiogTWF4aW11bSBsZWFzZSBsaWZldGltZSBpbiBob3VycyBiZWZvcmUgYSBzbG90IGlzIGNvbnNpZGVyZWQgZXhwaXJlZC5cbiAgICogQGRlZmF1bHQgMjRcbiAgICovXG4gIG1heExlYXNlSG91cnM/OiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld0Vudmlyb25tZW50UHJvcHMgZXh0ZW5kcyBQcmV2aWV3Q29uZmlnIHtcbiAgLyoqIEluZGV4IGRvY3VtZW50IGZvciBwcmV2aWV3IGJ1Y2tldHMuICovXG4gIGluZGV4RmlsZTogc3RyaW5nO1xuXG4gIC8qKiBFcnJvciBkb2N1bWVudCBmb3IgcHJldmlldyBidWNrZXRzLiAqL1xuICBlcnJvckZpbGU6IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIFdlYnNpdGUgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0OiBzMy5CdWNrZXQ7XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb246IGNsb3VkZm9udC5EaXN0cmlidXRpb247XG4gIHB1YmxpYyByZWFkb25seSBwcmV2aWV3RW52aXJvbm1lbnQ/OiBQcmV2aWV3RW52aXJvbm1lbnQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFdlYnNpdGVQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG4gICAgdGhpcy5idWNrZXQgPSBuZXcgczMuQnVja2V0KHRoaXMsIHByb3BzLmJ1Y2tldE5hbWUsIHtcbiAgICAgIHdlYnNpdGVJbmRleERvY3VtZW50OiBwcm9wcy5pbmRleEZpbGUsXG4gICAgICB3ZWJzaXRlRXJyb3JEb2N1bWVudDogcHJvcHMuZXJyb3JGaWxlLFxuICAgICAgcHVibGljUmVhZEFjY2VzczogdHJ1ZSxcbiAgICAgIHJlbW92YWxQb2xpY3k6IGNkay5SZW1vdmFsUG9saWN5LkRFU1RST1ksXG4gICAgICBibG9ja1B1YmxpY0FjY2VzczogczMuQmxvY2tQdWJsaWNBY2Nlc3MuQkxPQ0tfQUNMU19PTkxZLFxuICAgICAgYWNjZXNzQ29udHJvbDogczMuQnVja2V0QWNjZXNzQ29udHJvbC5CVUNLRVRfT1dORVJfRlVMTF9DT05UUk9MLFxuICAgICAgZW5jcnlwdGlvbjogczMuQnVja2V0RW5jcnlwdGlvbi5TM19NQU5BR0VELFxuICAgIH0pO1xuICAgIGNvbnN0IG9haSA9IG5ldyBjbG91ZGZvbnQuT3JpZ2luQWNjZXNzSWRlbnRpdHkoXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuYnVja2V0TmFtZX0tT0FJYCxcbiAgICApO1xuICAgIHRoaXMuYnVja2V0LmFkZFRvUmVzb3VyY2VQb2xpY3koXG4gICAgICBuZXcgaWFtLlBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgIGFjdGlvbnM6IFtcInMzOkdldE9iamVjdFwiXSxcbiAgICAgICAgcmVzb3VyY2VzOiBbdGhpcy5idWNrZXQuYXJuRm9yT2JqZWN0cyhcIipcIildLFxuICAgICAgICBwcmluY2lwYWxzOiBbXG4gICAgICAgICAgbmV3IGlhbS5DYW5vbmljYWxVc2VyUHJpbmNpcGFsKFxuICAgICAgICAgICAgb2FpLmNsb3VkRnJvbnRPcmlnaW5BY2Nlc3NJZGVudGl0eVMzQ2Fub25pY2FsVXNlcklkLFxuICAgICAgICAgICksXG4gICAgICAgIF0sXG4gICAgICB9KSxcbiAgICApO1xuICAgIGNvbnN0IGRvbWFpbk5hbWVzOiBzdHJpbmdbXSA9IFtdO1xuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIGRvbWFpbk5hbWVzLnB1c2godGhpcy5fZ2V0RnVsbERvbWFpbk5hbWUocHJvcHMuZG9tYWluQ29uZmlnKSk7XG4gICAgICBpZiAoXG4gICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5pbmNsdWRlUm9vdERvbWFpbiAmJlxuICAgICAgICBwcm9wcy5kb21haW5Db25maWcuc3ViZG9tYWluTmFtZVxuICAgICAgKSB7XG4gICAgICAgIGRvbWFpbk5hbWVzLnB1c2gocHJvcHMuZG9tYWluQ29uZmlnLmRvbWFpbk5hbWUpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHRoaXMuZGlzdHJpYnV0aW9uID0gbmV3IGNsb3VkZm9udC5EaXN0cmlidXRpb24oXG4gICAgICB0aGlzLFxuICAgICAgYCR7cHJvcHMuYnVja2V0TmFtZX0tZGlzdHJpYnV0aW9uYCxcbiAgICAgIHtcbiAgICAgICAgZGVmYXVsdEJlaGF2aW9yOiB7XG4gICAgICAgICAgb3JpZ2luOiBuZXcgb3JpZ2lucy5TM1N0YXRpY1dlYnNpdGVPcmlnaW4odGhpcy5idWNrZXQpLFxuICAgICAgICAgIHZpZXdlclByb3RvY29sUG9saWN5OlxuICAgICAgICAgICAgY2xvdWRmb250LlZpZXdlclByb3RvY29sUG9saWN5LlJFRElSRUNUX1RPX0hUVFBTLFxuICAgICAgICB9LFxuICAgICAgICBlcnJvclJlc3BvbnNlczogW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIGh0dHBTdGF0dXM6IDQwNCxcbiAgICAgICAgICAgIHJlc3BvbnNlSHR0cFN0YXR1czogNDA0LFxuICAgICAgICAgICAgcmVzcG9uc2VQYWdlUGF0aDogcHJvcHMubm90Rm91bmRSZXNwb25zZVBhZ2VQYXRoIHx8IGAvNDA0Lmh0bWxgLFxuICAgICAgICAgICAgdHRsOiBjZGsuRHVyYXRpb24ubWludXRlcygzMCksXG4gICAgICAgICAgfSxcbiAgICAgICAgXSxcbiAgICAgICAgcHJpY2VDbGFzczogY2xvdWRmb250LlByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgICAuLi4ocHJvcHMuZG9tYWluQ29uZmlnXG4gICAgICAgICAgPyB7XG4gICAgICAgICAgICAgIGRvbWFpbk5hbWVzOiBkb21haW5OYW1lcyxcbiAgICAgICAgICAgICAgY2VydGlmaWNhdGU6IHRoaXMuX2dldENlcnRpZmljYXRlKFxuICAgICAgICAgICAgICAgIHByb3BzLmRvbWFpbkNvbmZpZy5jZXJ0aWZpY2F0ZUFybixcbiAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgIH1cbiAgICAgICAgICA6IHt9KSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIGlmIChwcm9wcy5kb21haW5Db25maWcpIHtcbiAgICAgIGNvbnN0IGhvc3RlZFpvbmUgPSByb3V0ZTUzLkhvc3RlZFpvbmUuZnJvbUxvb2t1cCh0aGlzLCBcIkhvc3RlZFpvbmVcIiwge1xuICAgICAgICBkb21haW5OYW1lOiBwcm9wcy5kb21haW5Db25maWcuZG9tYWluTmFtZSxcbiAgICAgIH0pO1xuICAgICAgY29uc3QgZG9tYWluQVJlY29yZCA9IG5ldyByb3V0ZTUzLkFSZWNvcmQodGhpcywgXCJEb21haW5BUmVjb3JkXCIsIHtcbiAgICAgICAgem9uZTogaG9zdGVkWm9uZSxcbiAgICAgICAgcmVjb3JkTmFtZTogdGhpcy5fZ2V0RnVsbERvbWFpbk5hbWUocHJvcHMuZG9tYWluQ29uZmlnKSxcbiAgICAgICAgdGFyZ2V0OiBjZGsuYXdzX3JvdXRlNTMuUmVjb3JkVGFyZ2V0LmZyb21BbGlhcyhcbiAgICAgICAgICBuZXcgY2RrLmF3c19yb3V0ZTUzX3RhcmdldHMuQ2xvdWRGcm9udFRhcmdldCh0aGlzLmRpc3RyaWJ1dGlvbiksXG4gICAgICAgICksXG4gICAgICB9KTtcbiAgICAgIGRvbWFpbkFSZWNvcmQubm9kZS5hZGREZXBlbmRlbmN5KHRoaXMuZGlzdHJpYnV0aW9uKTtcblxuICAgICAgaWYgKFxuICAgICAgICBwcm9wcy5kb21haW5Db25maWcuaW5jbHVkZVJvb3REb21haW4gJiZcbiAgICAgICAgcHJvcHMuZG9tYWluQ29uZmlnLnN1YmRvbWFpbk5hbWVcbiAgICAgICkge1xuICAgICAgICBuZXcgcm91dGU1My5BUmVjb3JkKHRoaXMsIFwiUm9vdERvbWFpbkFSZWNvcmRcIiwge1xuICAgICAgICAgIHpvbmU6IGhvc3RlZFpvbmUsXG4gICAgICAgICAgcmVjb3JkTmFtZTogcHJvcHMuZG9tYWluQ29uZmlnLmRvbWFpbk5hbWUsXG4gICAgICAgICAgdGFyZ2V0OiBjZGsuYXdzX3JvdXRlNTMuUmVjb3JkVGFyZ2V0LmZyb21BbGlhcyhcbiAgICAgICAgICAgIG5ldyBjZGsuYXdzX3JvdXRlNTNfdGFyZ2V0cy5DbG91ZEZyb250VGFyZ2V0KHRoaXMuZGlzdHJpYnV0aW9uKSxcbiAgICAgICAgICApLFxuICAgICAgICB9KS5ub2RlLmFkZERlcGVuZGVuY3kodGhpcy5kaXN0cmlidXRpb24pO1xuICAgICAgfVxuICAgIH1cblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwiY2xvdWRmcm9udC13ZWJzaXRlLXVybFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5kaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZSxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIkNsb3VkRnJvbnQgRGlzdHJpYnV0aW9uIERvbWFpbiBOYW1lXCIsXG4gICAgfSk7XG5cbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcInMzLXdlYnNpdGUtdXJsXCIsIHtcbiAgICAgIHZhbHVlOiB0aGlzLmJ1Y2tldC5idWNrZXRXZWJzaXRlVXJsLFxuICAgICAgZGVzY3JpcHRpb246IFwiUzMgQnVja2V0IFdlYnNpdGUgVVJMXCIsXG4gICAgfSk7XG5cbiAgICBpZiAocHJvcHMuZG9tYWluQ29uZmlnKSB7XG4gICAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCBcIndlYnNpdGUtdXJsXCIsIHtcbiAgICAgICAgdmFsdWU6IHRoaXMuZGlzdHJpYnV0aW9uLmRvbWFpbk5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBcIldlYnNpdGUgVVJMXCIsXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBpZiAocHJvcHMucHJldmlld0NvbmZpZykge1xuICAgICAgdGhpcy5wcmV2aWV3RW52aXJvbm1lbnQgPSBuZXcgUHJldmlld0Vudmlyb25tZW50KFxuICAgICAgICB0aGlzLFxuICAgICAgICBcIlByZXZpZXdFbnZpcm9ubWVudFwiLFxuICAgICAgICB7XG4gICAgICAgICAgLi4ucHJvcHMucHJldmlld0NvbmZpZyxcbiAgICAgICAgICBpbmRleEZpbGU6IHByb3BzLmluZGV4RmlsZSxcbiAgICAgICAgICBlcnJvckZpbGU6IHByb3BzLmVycm9yRmlsZSxcbiAgICAgICAgfSxcbiAgICAgICk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBfZ2V0RnVsbERvbWFpbk5hbWUoZG9tYWluQ29uZmlnOiBEb21haW5Db25maWcpOiBzdHJpbmcge1xuICAgIHJldHVybiBkb21haW5Db25maWcuc3ViZG9tYWluTmFtZVxuICAgICAgPyBgJHtkb21haW5Db25maWcuc3ViZG9tYWluTmFtZX0uJHtkb21haW5Db25maWcuZG9tYWluTmFtZX1gXG4gICAgICA6IGRvbWFpbkNvbmZpZy5kb21haW5OYW1lO1xuICB9XG5cbiAgcHJpdmF0ZSBfZ2V0Q2VydGlmaWNhdGUoYXJuOiBzdHJpbmcpOiBjZXJ0aWZpY2F0ZW1hbmFnZXIuSUNlcnRpZmljYXRlIHtcbiAgICByZXR1cm4gY2VydGlmaWNhdGVtYW5hZ2VyLkNlcnRpZmljYXRlLmZyb21DZXJ0aWZpY2F0ZUFybihcbiAgICAgIHRoaXMsXG4gICAgICBgd2Vic2l0ZS1jZXJ0YCxcbiAgICAgIGFybixcbiAgICApO1xuICB9XG59XG5cbmV4cG9ydCBjbGFzcyBQcmV2aWV3RW52aXJvbm1lbnQgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwdWJsaWMgcmVhZG9ubHkgYnVja2V0czogczMuQnVja2V0W107XG4gIHB1YmxpYyByZWFkb25seSBkaXN0cmlidXRpb25zOiBjbG91ZGZvbnQuRGlzdHJpYnV0aW9uW107XG4gIHB1YmxpYyByZWFkb25seSBsZWFzZVRhYmxlOiBkeW5hbW9kYi5UYWJsZTtcbiAgcHVibGljIHJlYWRvbmx5IGFwaTogYXBpZ2F0ZXdheS5SZXN0QXBpO1xuICBwdWJsaWMgcmVhZG9ubHkgY2xhaW1FbmRwb2ludDogc3RyaW5nO1xuICBwdWJsaWMgcmVhZG9ubHkgaGVhcnRiZWF0RW5kcG9pbnQ6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IHJlbGVhc2VFbmRwb2ludDogc3RyaW5nO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBQcmV2aWV3RW52aXJvbm1lbnRQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICBjb25zdCBidWNrZXRDb3VudCA9IHByb3BzLmJ1Y2tldENvdW50ID8/IDI7XG4gICAgaWYgKGJ1Y2tldENvdW50IDwgMSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiYnVja2V0Q291bnQgbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMVwiKTtcbiAgICB9XG5cbiAgICBjb25zdCBpbmRleEZpbGUgPSBwcm9wcy5pbmRleEZpbGU7XG4gICAgY29uc3QgZXJyb3JGaWxlID0gcHJvcHMuZXJyb3JGaWxlO1xuICAgIGNvbnN0IGNyZWF0ZURpc3RyaWJ1dGlvbnMgPSBwcm9wcy5jcmVhdGVEaXN0cmlidXRpb25zID8/IHRydWU7XG4gICAgY29uc3QgbWF4TGVhc2VIb3VycyA9IHByb3BzLm1heExlYXNlSG91cnMgPz8gMjQ7XG4gICAgY29uc3QgbWF4TGVhc2VNcyA9IGNkay5EdXJhdGlvbi5ob3VycyhtYXhMZWFzZUhvdXJzKS50b01pbGxpc2Vjb25kcygpO1xuXG4gICAgdGhpcy5idWNrZXRzID0gQXJyYXkuZnJvbSh7IGxlbmd0aDogYnVja2V0Q291bnQgfSwgKF8sIHNsb3RJZCkgPT4ge1xuICAgICAgY29uc3QgYnVja2V0TmFtZSA9IGAke3Byb3BzLmJ1Y2tldFByZWZpeH0tJHtzbG90SWR9YDtcbiAgICAgIHJldHVybiBuZXcgczMuQnVja2V0KHRoaXMsIGBQcmV2aWV3QnVja2V0JHtzbG90SWR9YCwge1xuICAgICAgICBidWNrZXROYW1lLFxuICAgICAgICB3ZWJzaXRlSW5kZXhEb2N1bWVudDogaW5kZXhGaWxlLFxuICAgICAgICB3ZWJzaXRlRXJyb3JEb2N1bWVudDogZXJyb3JGaWxlLFxuICAgICAgICBwdWJsaWNSZWFkQWNjZXNzOiB0cnVlLFxuICAgICAgICByZW1vdmFsUG9saWN5OiBjZGsuUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgICBhdXRvRGVsZXRlT2JqZWN0czogdHJ1ZSxcbiAgICAgICAgYmxvY2tQdWJsaWNBY2Nlc3M6IHMzLkJsb2NrUHVibGljQWNjZXNzLkJMT0NLX0FDTFNfT05MWSxcbiAgICAgICAgYWNjZXNzQ29udHJvbDogczMuQnVja2V0QWNjZXNzQ29udHJvbC5CVUNLRVRfT1dORVJfRlVMTF9DT05UUk9MLFxuICAgICAgICBlbmNyeXB0aW9uOiBzMy5CdWNrZXRFbmNyeXB0aW9uLlMzX01BTkFHRUQsXG4gICAgICB9KTtcbiAgICB9KTtcblxuICAgIHRoaXMuZGlzdHJpYnV0aW9ucyA9IGNyZWF0ZURpc3RyaWJ1dGlvbnNcbiAgICAgID8gdGhpcy5idWNrZXRzLm1hcCgoYnVja2V0LCBzbG90SWQpID0+IHtcbiAgICAgICAgICBjb25zdCBvYWkgPSBuZXcgY2xvdWRmb250Lk9yaWdpbkFjY2Vzc0lkZW50aXR5KFxuICAgICAgICAgICAgdGhpcyxcbiAgICAgICAgICAgIGBQcmV2aWV3T0FJJHtzbG90SWR9YCxcbiAgICAgICAgICApO1xuICAgICAgICAgIGJ1Y2tldC5hZGRUb1Jlc291cmNlUG9saWN5KFxuICAgICAgICAgICAgbmV3IGlhbS5Qb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgICAgICBhY3Rpb25zOiBbXCJzMzpHZXRPYmplY3RcIl0sXG4gICAgICAgICAgICAgIHJlc291cmNlczogW2J1Y2tldC5hcm5Gb3JPYmplY3RzKFwiKlwiKV0sXG4gICAgICAgICAgICAgIHByaW5jaXBhbHM6IFtcbiAgICAgICAgICAgICAgICBuZXcgaWFtLkNhbm9uaWNhbFVzZXJQcmluY2lwYWwoXG4gICAgICAgICAgICAgICAgICBvYWkuY2xvdWRGcm9udE9yaWdpbkFjY2Vzc0lkZW50aXR5UzNDYW5vbmljYWxVc2VySWQsXG4gICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgIH0pLFxuICAgICAgICAgICk7XG4gICAgICAgICAgcmV0dXJuIG5ldyBjbG91ZGZvbnQuRGlzdHJpYnV0aW9uKFxuICAgICAgICAgICAgdGhpcyxcbiAgICAgICAgICAgIGBQcmV2aWV3RGlzdHJpYnV0aW9uJHtzbG90SWR9YCxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgZGVmYXVsdEJlaGF2aW9yOiB7XG4gICAgICAgICAgICAgICAgb3JpZ2luOiBuZXcgb3JpZ2lucy5TM1N0YXRpY1dlYnNpdGVPcmlnaW4oYnVja2V0KSxcbiAgICAgICAgICAgICAgICB2aWV3ZXJQcm90b2NvbFBvbGljeTpcbiAgICAgICAgICAgICAgICAgIGNsb3VkZm9udC5WaWV3ZXJQcm90b2NvbFBvbGljeS5SRURJUkVDVF9UT19IVFRQUyxcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgcHJpY2VDbGFzczogY2xvdWRmb250LlByaWNlQ2xhc3MuUFJJQ0VfQ0xBU1NfMTAwLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICApO1xuICAgICAgICB9KVxuICAgICAgOiBbXTtcblxuICAgIHRoaXMubGVhc2VUYWJsZSA9IG5ldyBkeW5hbW9kYi5UYWJsZSh0aGlzLCBcIlByZXZpZXdMZWFzZXNcIiwge1xuICAgICAgcGFydGl0aW9uS2V5OiB7XG4gICAgICAgIG5hbWU6IFwic2xvdElkXCIsXG4gICAgICAgIHR5cGU6IGR5bmFtb2RiLkF0dHJpYnV0ZVR5cGUuU1RSSU5HLFxuICAgICAgfSxcbiAgICAgIGJpbGxpbmdNb2RlOiBkeW5hbW9kYi5CaWxsaW5nTW9kZS5QQVlfUEVSX1JFUVVFU1QsXG4gICAgICByZW1vdmFsUG9saWN5OiBjZGsuUmVtb3ZhbFBvbGljeS5ERVNUUk9ZLFxuICAgICAgdGltZVRvTGl2ZUF0dHJpYnV0ZTogXCJ0dGxFcG9jaFNlY29uZHNcIixcbiAgICB9KTtcbiAgICB0aGlzLmxlYXNlVGFibGUuYWRkR2xvYmFsU2Vjb25kYXJ5SW5kZXgoe1xuICAgICAgaW5kZXhOYW1lOiBcIlJlcG9QcktleUluZGV4XCIsXG4gICAgICBwYXJ0aXRpb25LZXk6IHtcbiAgICAgICAgbmFtZTogXCJyZXBvUHJLZXlcIixcbiAgICAgICAgdHlwZTogZHluYW1vZGIuQXR0cmlidXRlVHlwZS5TVFJJTkcsXG4gICAgICB9LFxuICAgICAgcHJvamVjdGlvblR5cGU6IGR5bmFtb2RiLlByb2plY3Rpb25UeXBlLkFMTCxcbiAgICB9KTtcblxuICAgIGNvbnN0IHNsb3REZWZpbml0aW9ucyA9IHRoaXMuYnVja2V0cy5tYXAoKGJ1Y2tldCwgc2xvdElkKSA9PiAoe1xuICAgICAgc2xvdElkLFxuICAgICAgYnVja2V0TmFtZTogYnVja2V0LmJ1Y2tldE5hbWUsXG4gICAgICBwcmV2aWV3VXJsOiB0aGlzLmRpc3RyaWJ1dGlvbnNbc2xvdElkXVxuICAgICAgICA/IGBodHRwczovLyR7dGhpcy5kaXN0cmlidXRpb25zW3Nsb3RJZF0uZGlzdHJpYnV0aW9uRG9tYWluTmFtZX1gXG4gICAgICAgIDogYnVja2V0LmJ1Y2tldFdlYnNpdGVVcmwsXG4gICAgfSkpO1xuXG4gICAgY29uc3QgbGVhc2VBcGlIYW5kbGVyID0gbmV3IGxhbWJkYS5GdW5jdGlvbihcbiAgICAgIHRoaXMsXG4gICAgICBcIlByZXZpZXdMZWFzZUFwaUhhbmRsZXJcIixcbiAgICAgIHtcbiAgICAgICAgcnVudGltZTogbGFtYmRhLlJ1bnRpbWUuTk9ERUpTXzIwX1gsXG4gICAgICAgIGhhbmRsZXI6IFwiaW5kZXguaGFuZGxlclwiLFxuICAgICAgICB0aW1lb3V0OiBjZGsuRHVyYXRpb24uc2Vjb25kcygxNSksXG4gICAgICAgIGNvZGU6IGxhbWJkYS5Db2RlLmZyb21Bc3NldChwYXRoLmpvaW4oX19kaXJuYW1lLCBcIi4uXCIsIFwibGFtYmRhXCIpKSxcbiAgICAgICAgZW52aXJvbm1lbnQ6IHtcbiAgICAgICAgICBUQUJMRV9OQU1FOiB0aGlzLmxlYXNlVGFibGUudGFibGVOYW1lLFxuICAgICAgICAgIFNMT1RfREVGSU5JVElPTlM6IEpTT04uc3RyaW5naWZ5KHNsb3REZWZpbml0aW9ucyksXG4gICAgICAgICAgTUFYX0xFQVNFX01TOiBTdHJpbmcobWF4TGVhc2VNcyksXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICk7XG5cbiAgICB0aGlzLmxlYXNlVGFibGUuZ3JhbnRSZWFkV3JpdGVEYXRhKGxlYXNlQXBpSGFuZGxlcik7XG5cbiAgICB0aGlzLmFwaSA9IG5ldyBhcGlnYXRld2F5LlJlc3RBcGkodGhpcywgXCJQcmV2aWV3TGVhc2VBcGlcIiwge1xuICAgICAgcmVzdEFwaU5hbWU6IGAke2Nkay5OYW1lcy51bmlxdWVJZCh0aGlzKX0tcHJldmlldy1sZWFzZS1hcGlgLFxuICAgICAgZGVzY3JpcHRpb246IFwiQVBJIGZvciBjbGFpbWluZy9yZWxlYXNpbmcgcHJldmlldyBzbG90c1wiLFxuICAgIH0pO1xuXG4gICAgY29uc3QgY2xhaW1SZXNvdXJjZSA9IHRoaXMuYXBpLnJvb3QuYWRkUmVzb3VyY2UoXCJjbGFpbVwiKTtcbiAgICBjbGFpbVJlc291cmNlLmFkZE1ldGhvZChcbiAgICAgIFwiUE9TVFwiLFxuICAgICAgbmV3IGFwaWdhdGV3YXkuTGFtYmRhSW50ZWdyYXRpb24obGVhc2VBcGlIYW5kbGVyKSxcbiAgICApO1xuICAgIGNvbnN0IGhlYXJ0YmVhdFJlc291cmNlID0gdGhpcy5hcGkucm9vdC5hZGRSZXNvdXJjZShcImhlYXJ0YmVhdFwiKTtcbiAgICBoZWFydGJlYXRSZXNvdXJjZS5hZGRNZXRob2QoXG4gICAgICBcIlBPU1RcIixcbiAgICAgIG5ldyBhcGlnYXRld2F5LkxhbWJkYUludGVncmF0aW9uKGxlYXNlQXBpSGFuZGxlciksXG4gICAgKTtcbiAgICBjb25zdCByZWxlYXNlUmVzb3VyY2UgPSB0aGlzLmFwaS5yb290LmFkZFJlc291cmNlKFwicmVsZWFzZVwiKTtcbiAgICByZWxlYXNlUmVzb3VyY2UuYWRkTWV0aG9kKFxuICAgICAgXCJQT1NUXCIsXG4gICAgICBuZXcgYXBpZ2F0ZXdheS5MYW1iZGFJbnRlZ3JhdGlvbihsZWFzZUFwaUhhbmRsZXIpLFxuICAgICk7XG5cbiAgICB0aGlzLmNsYWltRW5kcG9pbnQgPSBgJHt0aGlzLmFwaS51cmx9Y2xhaW1gO1xuICAgIHRoaXMuaGVhcnRiZWF0RW5kcG9pbnQgPSBgJHt0aGlzLmFwaS51cmx9aGVhcnRiZWF0YDtcbiAgICB0aGlzLnJlbGVhc2VFbmRwb2ludCA9IGAke3RoaXMuYXBpLnVybH1yZWxlYXNlYDtcblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsIFwicHJldmlldy1jbGFpbS1lbmRwb2ludFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5jbGFpbUVuZHBvaW50LFxuICAgICAgZGVzY3JpcHRpb246IFwiUE9TVCBlbmRwb2ludCB1c2VkIHRvIGNsYWltIGEgcHJldmlldyBzbG90XCIsXG4gICAgfSk7XG4gICAgbmV3IGNkay5DZm5PdXRwdXQodGhpcywgXCJwcmV2aWV3LWhlYXJ0YmVhdC1lbmRwb2ludFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5oZWFydGJlYXRFbmRwb2ludCxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIlBPU1QgZW5kcG9pbnQgdXNlZCB0byByZWZyZXNoIGEgcHJldmlldyBzbG90IGxlYXNlXCIsXG4gICAgfSk7XG4gICAgbmV3IGNkay5DZm5PdXRwdXQodGhpcywgXCJwcmV2aWV3LXJlbGVhc2UtZW5kcG9pbnRcIiwge1xuICAgICAgdmFsdWU6IHRoaXMucmVsZWFzZUVuZHBvaW50LFxuICAgICAgZGVzY3JpcHRpb246IFwiUE9TVCBlbmRwb2ludCB1c2VkIHRvIHJlbGVhc2UgYSBwcmV2aWV3IHNsb3RcIixcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBncmFudERlcGxveW1lbnRBY2Nlc3MoZ3JhbnRlZTogaWFtLklHcmFudGFibGUpOiB2b2lkIHtcbiAgICB0aGlzLmJ1Y2tldHMuZm9yRWFjaCgoYnVja2V0KSA9PiBidWNrZXQuZ3JhbnRSZWFkV3JpdGUoZ3JhbnRlZSkpO1xuICAgIHRoaXMuZGlzdHJpYnV0aW9ucy5mb3JFYWNoKChkaXN0cmlidXRpb24pID0+IHtcbiAgICAgIGdyYW50ZWUuZ3JhbnRQcmluY2lwYWwuYWRkVG9QcmluY2lwYWxQb2xpY3koXG4gICAgICAgIG5ldyBpYW0uUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbXCJjbG91ZGZyb250OkNyZWF0ZUludmFsaWRhdGlvblwiXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFtkaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uQXJuXSxcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH0pO1xuICB9XG59XG4iXX0=
|