@cloudsnorkel/cdk-github-runners
Version:
CDK construct to create GitHub Actions self-hosted runners. Creates ephemeral runners on demand. Easy to deploy and highly customizable.
487 lines • 67.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handler = handler;
/**
* Warm Runner Manager Lambda
*
* Maintains a pool of pre-provisioned ("warm") GitHub self-hosted runners so jobs
* start with near-zero latency instead of waiting for a fresh runner to provision.
*
* ## Lifecycle
*
* 1. **Fill** — An EventBridge cron rule (midnight UTC for always-on, or window
* start for scheduled) sends a fill payload to the shared SQS queue. For
* AlwaysOnWarmRunner only, a CloudFormation custom resource (on deploy)
* invokes this Lambda directly to fill immediately; the deadline is set to
* the next midnight UTC (not full 24h) so runners last until the next cron
* fill. ScheduledWarmRunner has no deployment-fill — first fill is at the
* next schedule occurrence.
*
* 2. **Keeper** — Each keeper message tracks one runner. The SQS queue delivers the
* message, this Lambda inspects the runner, and one of the following happens:
* - Runner is past its deadline → the keeper stops the Step Function and deletes
* the runner. No replacement is created.
* - Runner is idle and within deadline → message is returned to the queue
* (via batch item failure) to be checked again after the visibility timeout.
* - Runner is busy or its Step Function finished → a replacement runner is
* started with the same deadline, and a new keeper message is enqueued.
* - Config hash mismatch (config was changed/removed since this runner was
* created) → the runner is stopped and deleted. No replacement is created.
* This is how old runners are cleaned up quickly on config changes.
*
* 3. **Shutdown mechanisms** — The keeper is the primary mechanism for enforcing the
* absolute deadline: when a runner is past its deadline, the keeper stops the
* Step Function and deletes the runner. Fallbacks:
* - **Idle reaper**: Measures idle time from runner *registration* (cdkghr:started
* label), not step function start. So it fires at deadline + provisioning delay.
* It's a fallback if the keeper misses a message; it does not enforce the
* absolute deadline precisely.
* - **Step Function idle timeout**: Each runner is started with `maxIdleSeconds`
* matching the deadline. If both keeper and idle reaper miss it, the Step
* Function will self-terminate when its idle timeout fires.
*
* ## Config hash
*
* Each warm runner config (provider, count, labels, owner, repo, duration) is
* hashed at CDK synth time. All current hashes are stored in the WARM_CONFIG_HASHES
* environment variable. Fill payloads and keeper messages carry the hash. When the
* keeper processes a message whose hash is not in the current set, it knows the
* config was changed or removed and stops the runner immediately. This helps quickly
* get rid of stale runners while keeping over-provisioning to a minimum.
*
* ## Gotchas
*
* - Keeper messages rely on SQS redelivery (batch item failure) for periodic
* checking. The visibility timeout (1 min) determines how often runners are
* polled. Failed messages are retried until they succeed or the runner
* self-terminates at its idle timeout.
* - Each fill unconditionally starts `count` runners — it does not check how many
* are already running. On cron fire, this creates a brief overlap with the
* previous cycle's runners (which are near their deadline).
* - Removing all warm runners configurations may result in warm runners staying
* around until they expire. To remove all warm runners quickly, set count to 0
* and deploy. Only once all the warm runners are stopped, you can remove all
* configurations and deploy again.
* - **Gaps in coverage**: The Step Function that provisions each warm runner uses
* increasing timeouts between retries for provider failures (CodeBuild timeout,
* Lambda timeout, capacity errors, etc.). While a warm runner slot is retrying,
* that slot has no runner. This may create gaps in coverage. An idle warm runner
* that fails to provision (or whose replacement fails) will be unavailable until
* the retry succeeds. Current retry mechanism has built-in back-off rate and can
* be tweaked using `retryOptions`. This will be improved in the future.
*/
const crypto = require("crypto");
const client_sfn_1 = require("@aws-sdk/client-sfn");
const client_sqs_1 = require("@aws-sdk/client-sqs");
const lambda_github_1 = require("./lambda-github");
const lambda_helpers_1 = require("./lambda-helpers");
const sfn = new client_sfn_1.SFNClient();
const sqs = new client_sqs_1.SQSClient();
const SFN_EXECUTION_NAME_MAX_LENGTH = 80;
function isSqsEvent(event) {
return Array.isArray(event.Records);
}
function isFillInput(event) {
return typeof event === 'object' && event !== null && event.action === 'fill';
}
function isCustomResourceEvent(event) {
const e = event;
return typeof e?.RequestType === 'string' && typeof e?.ResponseURL === 'string';
}
function requireEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing environment variable ${name}`);
}
return value;
}
/**
* Deterministic execution name for idempotent fills. Same seed → same name, so retries
* (custom resource, SQS redelivery) don't create duplicate runners.
*/
function deterministicExecutionName(providerPath, seed) {
const pathWithoutStack = providerPath.split('/').slice(1).join('/') || providerPath;
const sanitized = `warm-${pathWithoutStack.replace(/[^a-zA-Z0-9-]/g, '-')}`;
const hash = crypto.createHash('sha256').update(seed).digest('hex').slice(0, 16);
const maxPrefixLen = SFN_EXECUTION_NAME_MAX_LENGTH - hash.length - 1;
return `${sanitized.slice(0, maxPrefixLen)}-${hash}`;
}
/** Returns Unix ms of the next midnight UTC. Used for AlwaysOn deployment-fill deadline. */
function getNextMidnightUtcMs() {
const now = new Date();
const nextMidnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0, 0));
return nextMidnight.getTime();
}
/** Find installation id for our app. Normal code path gets this from the webhook payload, but we schedule these ourselves. */
async function resolveInstallationId(owner, repo) {
const appOctokit = await (0, lambda_github_1.getAppOctokit)();
if (!appOctokit) {
return undefined; // PAT authentication
}
if (repo) {
const { data } = await appOctokit.rest.apps.getRepoInstallation({ owner, repo });
return data.id;
}
else {
const { data } = await appOctokit.rest.apps.getOrgInstallation({ org: owner });
return data.id;
}
}
/** Start a warm runner and enqueue a keeper message using SQS. */
async function startWarmRunnerAndEnqueueKeeper(input) {
const stepFunctionArn = requireEnv('STEP_FUNCTION_ARN');
const queueUrl = requireEnv('WARM_RUNNER_QUEUE_URL');
const remainingSeconds = Math.floor((input.absoluteDeadline - Date.now()) / 1000);
if (remainingSeconds <= 0) {
console.log({
notice: 'Absolute deadline already passed; not starting replacement',
configHash: input.configHash,
runnerName: input.executionName,
input,
});
return;
}
let executionArn;
try {
const result = await sfn.send(new client_sfn_1.StartExecutionCommand({
stateMachineArn: stepFunctionArn,
name: input.executionName,
input: JSON.stringify({
owner: input.owner,
repo: input.repo || '',
jobId: -1,
jobUrl: '',
installationId: input.installationId ?? -1,
jobLabels: input.providerLabels.join(','),
provider: input.providerPath,
labels: [...input.providerLabels, 'cdkghr:warm'].join(','),
maxIdleSeconds: remainingSeconds,
}),
}));
executionArn = result.executionArn;
}
catch (e) {
if (e instanceof client_sfn_1.ExecutionAlreadyExists) {
// Idempotent retry: execution was already started (e.g. Lambda timed out mid-fill).
// Don't enqueue. The first attempt already did, so duplicate keeper messages would cause duplicate replacements.
console.log({
notice: 'ExecutionAlreadyExists — idempotent retry, skipping enqueue',
configHash: input.configHash,
slot: input.slot,
runnerName: input.executionName,
});
return;
}
else {
throw e;
}
}
const message = {
executionArn,
runnerName: input.executionName,
owner: input.owner,
repo: input.repo,
installationId: input.installationId,
providerPath: input.providerPath,
providerLabels: input.providerLabels,
absoluteDeadline: input.absoluteDeadline,
configHash: input.configHash,
};
await sqs.send(new client_sqs_1.SendMessageCommand({
QueueUrl: queueUrl,
MessageBody: JSON.stringify(message),
}));
console.log({
notice: 'Started warm runner and enqueued keeper message',
configHash: input.configHash,
slot: input.slot,
runnerName: input.executionName,
executionArn,
remainingSeconds,
});
}
/**
* Unconditionally starts `count` warm runners for the given config and enqueues keeper messages.
*
* @param input the fill payload
* @param getNameForSlot a function to generate a unique and stable execution name for each slot
* @param source the source of the fill for debugging purposes
* @param absoluteDeadlineOverride if provided, use this instead of now + duration (for AlwaysOn deployment-fill)
*/
async function runFiller(input, getNameForSlot, source, absoluteDeadlineOverride) {
const installationId = await resolveInstallationId(input.owner, input.repo);
const absoluteDeadline = absoluteDeadlineOverride ?? Date.now() + input.duration * 1000;
for (let i = 0; i < input.count; i++) {
await startWarmRunnerAndEnqueueKeeper({
providerPath: input.providerPath,
providerLabels: input.providerLabels,
owner: input.owner,
repo: input.repo,
installationId,
absoluteDeadline,
configHash: input.configHash,
executionName: getNameForSlot(i),
slot: i,
});
}
console.log({
notice: 'Fill complete - started warm runners',
source,
configHash: input.configHash,
providerPath: input.providerPath,
started: input.count,
});
}
/** Stop the step function and delete the runner from GitHub. */
async function stopAndDeleteRunner(input, octokit, secrets, reason) {
try {
await sfn.send(new client_sfn_1.StopExecutionCommand({
executionArn: input.executionArn,
error: reason,
cause: 'Warm runner stopped by keeper',
}));
}
catch (e) {
console.error({
notice: 'Failed to stop step function',
configHash: input.configHash,
runnerName: input.runnerName,
executionArn: input.executionArn,
error: e,
input,
});
}
const runner = await (0, lambda_github_1.getRunner)(octokit, secrets.runnerLevel, input.owner, input.repo, input.runnerName);
if (runner) {
try {
await (0, lambda_github_1.deleteRunner)(octokit, secrets.runnerLevel, input.owner, input.repo, runner.id);
}
catch (e) {
console.error({
notice: 'Failed to delete runner',
configHash: input.configHash,
runnerName: input.runnerName,
runnerId: runner.id,
error: e,
input,
});
}
}
}
/**
* Warm runner manager Lambda - handles three invocation modes:
*
* 1. CloudFormation Custom Resource - triggered on stack deploy (Create/Update) for AlwaysOnWarmRunner only. Runs
* runFiller with deadline = next midnight UTC so runners last until the next cron fill. Delete is a no-op.
* 2. SQS messages - fill or keeper
* - Fill - from EventBridge cron. Uses messageId for deterministic execution names (idempotent on redelivery).
* - Keeper - tracks one runner. Uses SQS message cycling for periodic checks.
* Each message tracks one warm runner. The keeper checks:
* - Past deadline - stop the Step Function and delete the runner.
* - Config hash - if the message's `configHash` doesn't match the current `WARM_CONFIG_HASHES` env var, the runner is from a stale config - stop it and discard the message without replacement.
* - Busy/finished - if the Step Function ended or the GitHub runner is busy (took a job), start a replacement runner (inheriting the same deadline and config hash).
* - Not found yet (runner/infrastructure still starting) - retry later (message goes back to queue).
* - Still idle - retry later to check again.
*/
async function handler(event) {
if (isCustomResourceEvent(event)) {
const physicalId = ('PhysicalResourceId' in event ? event.PhysicalResourceId : undefined) ?? event.LogicalResourceId;
try {
const props = event.ResourceProperties;
console.log({
notice: 'Custom resource fill',
requestType: event.RequestType,
logicalResourceId: event.LogicalResourceId,
configHash: props.configHash,
providerPath: props.providerPath,
count: props.count,
});
if (event.RequestType === 'Create' || event.RequestType === 'Update') {
const getNameForSlot = (slot) => deterministicExecutionName(props.providerPath, `${event.LogicalResourceId}:${event.RequestType}:${props.configHash}:${slot}`);
const deadline = getNextMidnightUtcMs();
await runFiller(props, getNameForSlot, 'customResource', deadline);
}
await (0, lambda_helpers_1.customResourceRespond)(event, 'SUCCESS', 'OK', physicalId, {});
}
catch (e) {
console.error({ notice: 'Custom resource handler failed', error: e });
await (0, lambda_helpers_1.customResourceRespond)(event, 'FAILED', e.message || 'Internal Error', physicalId, {});
}
return;
}
if (!isSqsEvent(event)) {
console.error({ notice: 'Unknown event type; ignoring', event });
return;
}
const validHashes = new Set((process.env.WARM_CONFIG_HASHES ?? '').split(',').filter(Boolean));
const result = { batchItemFailures: [] };
const octokitCache = new Map();
for (const record of event.Records) {
let body;
try {
body = JSON.parse(record.body);
}
catch (e) {
console.error({
notice: 'Failed to parse message body',
requestId: record.messageId,
error: e,
});
continue;
}
const retryLater = () => result.batchItemFailures.push({ itemIdentifier: record.messageId });
const isFill = isFillInput(body);
const configHash = body.configHash;
const runnerName = isFill ? undefined : body.runnerName;
console.log({
notice: 'Processing SQS message',
messageId: record.messageId,
configHash,
runnerName,
});
// scheduled fill from EventBridge via SQS
if (isFill) {
const fillPayload = body;
try {
console.log({
notice: 'Scheduled fill',
configHash,
providerPath: fillPayload.providerPath,
count: fillPayload.count,
});
const getNameForSlot = (slot) => deterministicExecutionName(fillPayload.providerPath, `${record.messageId}:${slot}`);
await runFiller(fillPayload, getNameForSlot, 'scheduled', undefined);
}
catch (e) {
console.error({
notice: 'Fill failed',
messageId: record.messageId,
configHash: fillPayload.configHash,
error: e,
});
retryLater();
}
continue;
}
// keeper message
const input = body;
console.log({
notice: 'Checking warm runner',
configHash: input.configHash,
runnerName: input.runnerName,
});
// get github access (cached per installationId to avoid re-reading the secrets manager and Github API every time)
let octokit;
let secrets;
const cached = octokitCache.get(input.installationId);
if (cached) {
octokit = cached.octokit;
secrets = cached.secrets;
}
else {
const got = await (0, lambda_github_1.getOctokit)(input.installationId);
octokit = got.octokit;
secrets = got.githubSecrets;
octokitCache.set(input.installationId, { octokit, secrets });
}
// stale config - best-effort stop, then discard message (runner will self-terminate at its idle timeout)
if (!validHashes.has(input.configHash)) {
console.log({
notice: 'Config hash mismatch (new CDK deployment, old runner) - stopping stale warm runner',
configHash: input.configHash,
runnerName: input.runnerName,
validHashes: validHashes,
});
try {
await stopAndDeleteRunner(input, octokit, secrets, 'StaleWarmRunner');
}
catch (e) {
console.error({
notice: 'Best-effort cleanup of stale warm runner failed; it will self-terminate at idle timeout',
configHash: input.configHash,
runnerName: input.runnerName,
error: e,
});
}
continue;
}
// past deadline - keeper must stop and delete the runner
if (Date.now() >= input.absoluteDeadline) {
console.log({
notice: 'Warm runner past deadline, stopping and deleting',
configHash: input.configHash,
runnerName: input.runnerName,
});
try {
await stopAndDeleteRunner(input, octokit, secrets, 'WarmRunnerExpired');
}
catch (e) {
console.error({
notice: 'Failed to stop expired warm runner',
configHash: input.configHash,
runnerName: input.runnerName,
error: e,
});
// don't retry to not accidentally create a new runner
// idle reaper will take care of it soon enough
}
continue;
}
// check if step function is still running
const execution = await sfn.send(new client_sfn_1.DescribeExecutionCommand({ executionArn: input.executionArn }));
const stillRunning = execution.status === 'RUNNING';
// find runner
const runner = await (0, lambda_github_1.getRunner)(octokit, secrets.runnerLevel, input.owner, input.repo, input.runnerName);
// need replacement: step function finished (not running) or runner took a job (busy)
if (!stillRunning || runner?.busy) {
console.log({
notice: 'Warm runner finished or busy; starting replacement',
configHash: input.configHash,
runnerName: input.runnerName,
stillRunning,
runnerBusy: runner?.busy ?? false,
});
try {
await startWarmRunnerAndEnqueueKeeper({
providerPath: input.providerPath,
providerLabels: input.providerLabels,
owner: input.owner,
repo: input.repo,
installationId: input.installationId,
absoluteDeadline: input.absoluteDeadline,
configHash: input.configHash,
executionName: deterministicExecutionName(input.providerPath, record.messageId),
});
}
catch (e) {
console.error({
notice: 'Failed to start replacement warm runner',
configHash: input.configHash,
runnerName: input.runnerName,
error: e,
});
retryLater();
}
continue;
}
// step function still running but runner not found yet
if (!runner) {
console.log({
notice: 'Runner not running yet',
configHash: input.configHash,
runnerName: input.runnerName,
});
retryLater();
continue;
}
// still idle - check again later
console.log({
notice: 'Runner still idle - will check again later',
configHash: input.configHash,
runnerName: input.runnerName,
});
retryLater();
}
return result;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2FybS1ydW5uZXItbWFuYWdlci5sYW1iZGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvd2FybS1ydW5uZXItbWFuYWdlci5sYW1iZGEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF5VkEsMEJBb05DO0FBN2lCRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvRUc7QUFDSCxpQ0FBaUM7QUFDakMsb0RBTTZCO0FBQzdCLG9EQUFvRTtBQUdwRSxtREFBb0c7QUFDcEcscURBQXlEO0FBRXpELE1BQU0sR0FBRyxHQUFHLElBQUksc0JBQVMsRUFBRSxDQUFDO0FBQzVCLE1BQU0sR0FBRyxHQUFHLElBQUksc0JBQVMsRUFBRSxDQUFDO0FBRTVCLE1BQU0sNkJBQTZCLEdBQUcsRUFBRSxDQUFDO0FBNEJ6QyxTQUFTLFVBQVUsQ0FBQyxLQUFjO0lBQ2hDLE9BQU8sS0FBSyxDQUFDLE9BQU8sQ0FBRSxLQUE0QixDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxLQUFjO0lBQ2pDLE9BQU8sT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUssS0FBK0IsQ0FBQyxNQUFNLEtBQUssTUFBTSxDQUFDO0FBQzNHLENBQUM7QUFFRCxTQUFTLHFCQUFxQixDQUFDLEtBQWM7SUFDM0MsTUFBTSxDQUFDLEdBQUcsS0FBb0QsQ0FBQztJQUMvRCxPQUFPLE9BQU8sQ0FBQyxFQUFFLFdBQVcsS0FBSyxRQUFRLElBQUksT0FBTyxDQUFDLEVBQUUsV0FBVyxLQUFLLFFBQVEsQ0FBQztBQUNsRixDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsSUFBWTtJQUM5QixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2hDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztR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QUFVO29CQUM1QixhQUFhLEVBQUUsMEJBQTBCLENBQUMsS0FBSyxDQUFDLFlBQVksRUFBRSxNQUFNLENBQUMsU0FBUyxDQUFDO2lCQUNoRixDQUFDLENBQUM7WUFDTCxDQUFDO1lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztnQkFDWCxPQUFPLENBQUMsS0FBSyxDQUFDO29CQUNaLE1BQU0sRUFBRSx5Q0FBeUM7b0JBQ2pELFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtvQkFDNUIsVUFBVSxFQUFFLEtBQUssQ0FBQyxVQUFVO29CQUM1QixLQUFLLEVBQUUsQ0FBQztpQkFDVCxDQUFDLENBQUM7Z0JBQ0gsVUFBVSxFQUFFLENBQUM7WUFDZixDQUFDO1lBQ0QsU0FBUztRQUNYLENBQUM7UUFFRCx1REFBdUQ7UUFDdkQsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ1osT0FBTyxDQUFDLEdBQUcsQ0FBQztnQkFDVixNQUFNLEVBQUUsd0JBQXdCO2dCQUNoQyxVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVU7Z0JBQzVCLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTthQUM3QixDQUFDLENBQUM7WUFDSCxVQUFVLEVBQUUsQ0FBQztZQUNiLFNBQVM7UUFDWCxDQUFDO1FBRUQsaUNBQWlDO1FBQ2pDLE9BQU8sQ0FBQyxHQUFHLENBQUM7WUFDVixNQUFNLEVBQUUsNENBQTRDO1lBQ3BELFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtZQUM1QixVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVU7U0FDN0IsQ0FBQyxDQUFDO1FBQ0gsVUFBVSxFQUFFLENBQUM7SUFDZixDQUFDO0lBRUQsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogV2FybSBSdW5uZXIgTWFuYWdlciBMYW1iZGFcbiAqXG4gKiBNYWludGFpbnMgYSBwb29sIG9mIHByZS1wcm92aXNpb25lZCAoXCJ3YXJtXCIpIEdpdEh1YiBzZWxmLWhvc3RlZCBydW5uZXJzIHNvIGpvYnNcbiAqIHN0YXJ0IHdpdGggbmVhci16ZXJvIGxhdGVuY3kgaW5zdGVhZCBvZiB3YWl0aW5nIGZvciBhIGZyZXNoIHJ1bm5lciB0byBwcm92aXNpb24uXG4gKlxuICogIyMgTGlmZWN5Y2xlXG4gKlxuICogMS4gKipGaWxsKiog4oCUIEFuIEV2ZW50QnJpZGdlIGNyb24gcnVsZSAobWlkbmlnaHQgVVRDIGZvciBhbHdheXMtb24sIG9yIHdpbmRvd1xuICogICAgc3RhcnQgZm9yIHNjaGVkdWxlZCkgc2VuZHMgYSBmaWxsIHBheWxvYWQgdG8gdGhlIHNoYXJlZCBTUVMgcXVldWUuIEZvclxuICogICAgQWx3YXlzT25XYXJtUnVubmVyIG9ubHksIGEgQ2xvdWRGb3JtYXRpb24gY3VzdG9tIHJlc291cmNlIChvbiBkZXBsb3kpXG4gKiAgICBpbnZva2VzIHRoaXMgTGFtYmRhIGRpcmVjdGx5IHRvIGZpbGwgaW1tZWRpYXRlbHk7IHRoZSBkZWFkbGluZSBpcyBzZXQgdG9cbiAqICAgIHRoZSBuZXh0IG1pZG5pZ2h0IFVUQyAobm90IGZ1bGwgMjRoKSBzbyBydW5uZXJzIGxhc3QgdW50aWwgdGhlIG5leHQgY3JvblxuICogICAgZmlsbC4gU2NoZWR1bGVkV2FybVJ1bm5lciBoYXMgbm8gZGVwbG95bWVudC1maWxsIOKAlCBmaXJzdCBmaWxsIGlzIGF0IHRoZVxuICogICAgbmV4dCBzY2hlZHVsZSBvY2N1cnJlbmNlLlxuICpcbiAqIDIuICoqS2VlcGVyKiog4oCUIEVhY2gga2VlcGVyIG1lc3NhZ2UgdHJhY2tzIG9uZSBydW5uZXIuIFRoZSBTUVMgcXVldWUgZGVsaXZlcnMgdGhlXG4gKiAgICBtZXNzYWdlLCB0aGlzIExhbWJkYSBpbnNwZWN0cyB0aGUgcnVubmVyLCBhbmQgb25lIG9mIHRoZSBmb2xsb3dpbmcgaGFwcGVuczpcbiAqICAgIC0gUnVubmVyIGlzIHBhc3QgaXRzIGRlYWRsaW5lIOKGkiB0aGUga2VlcGVyIHN0b3BzIHRoZSBTdGVwIEZ1bmN0aW9uIGFuZCBkZWxldGVzXG4gKiAgICAgIHRoZSBydW5uZXIuIE5vIHJlcGxhY2VtZW50IGlzIGNyZWF0ZWQuXG4gKiAgICAtIFJ1bm5lciBpcyBpZGxlIGFuZCB3aXRoaW4gZGVhZGxpbmUg4oaSIG1lc3NhZ2UgaXMgcmV0dXJuZWQgdG8gdGhlIHF1ZXVlXG4gKiAgICAgICh2aWEgYmF0Y2ggaXRlbSBmYWlsdXJlKSB0byBiZSBjaGVja2VkIGFnYWluIGFmdGVyIHRoZSB2aXNpYmlsaXR5IHRpbWVvdXQuXG4gKiAgICAtIFJ1bm5lciBpcyBidXN5IG9yIGl0cyBTdGVwIEZ1bmN0aW9uIGZpbmlzaGVkIOKGkiBhIHJlcGxhY2VtZW50IHJ1bm5lciBpc1xuICogICAgICBzdGFydGVkIHdpdGggdGhlIHNhbWUgZGVhZGxpbmUsIGFuZCBhIG5ldyBrZWVwZXIgbWVzc2FnZSBpcyBlbnF1ZXVlZC5cbiAqICAgIC0gQ29uZmlnIGhhc2ggbWlzbWF0Y2ggKGNvbmZpZyB3YXMgY2hhbmdlZC9yZW1vdmVkIHNpbmNlIHRoaXMgcnVubmVyIHdhc1xuICogICAgICBjcmVhdGVkKSDihpIgdGhlIHJ1bm5lciBpcyBzdG9wcGVkIGFuZCBkZWxldGVkLiBObyByZXBsYWNlbWVudCBpcyBjcmVhdGVkLlxuICogICAgICBUaGlzIGlzIGhvdyBvbGQgcnVubmVycyBhcmUgY2xlYW5lZCB1cCBxdWlja2x5IG9uIGNvbmZpZyBjaGFuZ2VzLlxuICpcbiAqIDMuICoqU2h1dGRvd24gbWVjaGFuaXNtcyoqIOKAlCBUaGUga2VlcGVyIGlzIHRoZSBwcmltYXJ5IG1lY2hhbmlzbSBmb3IgZW5mb3JjaW5nIHRoZVxuICogICAgYWJzb2x1dGUgZGVhZGxpbmU6IHdoZW4gYSBydW5uZXIgaXMgcGFzdCBpdHMgZGVhZGxpbmUsIHRoZSBrZWVwZXIgc3RvcHMgdGhlXG4gKiAgICBTdGVwIEZ1bmN0aW9uIGFuZCBkZWxldGVzIHRoZSBydW5uZXIuIEZhbGxiYWNrczpcbiAqICAgIC0gKipJZGxlIHJlYXBlcioqOiBNZWFzdXJlcyBpZGxlIHRpbWUgZnJvbSBydW5uZXIgKnJlZ2lzdHJhdGlvbiogKGNka2docjpzdGFydGVkXG4gKiAgICAgIGxhYmVsKSwgbm90IHN0ZXAgZnVuY3Rpb24gc3RhcnQuIFNvIGl0IGZpcmVzIGF0IGRlYWRsaW5lICsgcHJvdmlzaW9uaW5nIGRlbGF5LlxuICogICAgICBJdCdzIGEgZmFsbGJhY2sgaWYgdGhlIGtlZXBlciBtaXNzZXMgYSBtZXNzYWdlOyBpdCBkb2VzIG5vdCBlbmZvcmNlIHRoZVxuICogICAgICBhYnNvbHV0ZSBkZWFkbGluZSBwcmVjaXNlbHkuXG4gKiAgICAtICoqU3RlcCBGdW5jdGlvbiBpZGxlIHRpbWVvdXQqKjogRWFjaCBydW5uZXIgaXMgc3RhcnRlZCB3aXRoIGBtYXhJZGxlU2Vjb25kc2BcbiAqICAgICAgbWF0Y2hpbmcgdGhlIGRlYWRsaW5lLiBJZiBib3RoIGtlZXBlciBhbmQgaWRsZSByZWFwZXIgbWlzcyBpdCwgdGhlIFN0ZXBcbiAqICAgICAgRnVuY3Rpb24gd2lsbCBzZWxmLXRlcm1pbmF0ZSB3aGVuIGl0cyBpZGxlIHRpbWVvdXQgZmlyZXMuXG4gKlxuICogIyMgQ29uZmlnIGhhc2hcbiAqXG4gKiBFYWNoIHdhcm0gcnVubmVyIGNvbmZpZyAocHJvdmlkZXIsIGNvdW50LCBsYWJlbHMsIG93bmVyLCByZXBvLCBkdXJhdGlvbikgaXNcbiAqIGhhc2hlZCBhdCBDREsgc3ludGggdGltZS4gQWxsIGN1cnJlbnQgaGFzaGVzIGFyZSBzdG9yZWQgaW4gdGhlIFdBUk1fQ09ORklHX0hBU0hFU1xuICogZW52aXJvbm1lbnQgdmFyaWFibGUuIEZpbGwgcGF5bG9hZHMgYW5kIGtlZXBlciBtZXNzYWdlcyBjYXJyeSB0aGUgaGFzaC4gV2hlbiB0aGVcbiAqIGtlZXBlciBwcm9jZXNzZXMgYSBtZXNzYWdlIHdob3NlIGhhc2ggaXMgbm90IGluIHRoZSBjdXJyZW50IHNldCwgaXQga25vd3MgdGhlXG4gKiBjb25maWcgd2FzIGNoYW5nZWQgb3IgcmVtb3ZlZCBhbmQgc3RvcHMgdGhlIHJ1bm5lciBpbW1lZGlhdGVseS4gVGhpcyBoZWxwcyBxdWlja2x5XG4gKiBnZXQgcmlkIG9mIHN0YWxlIHJ1bm5lcnMgd2hpbGUga2VlcGluZyBvdmVyLXByb3Zpc2lvbmluZyB0byBhIG1pbmltdW0uXG4gKlxuICogIyMgR290Y2hhc1xuICpcbiAqIC0gS2VlcGVyIG1lc3NhZ2VzIHJlbHkgb24gU1FTIHJlZGVsaXZlcnkgKGJhdGNoIGl0ZW0gZmFpbHVyZSkgZm9yIHBlcmlvZGljXG4gKiAgIGNoZWNraW5nLiBUaGUgdmlzaWJpbGl0eSB0aW1lb3V0ICgxIG1pbikgZGV0ZXJtaW5lcyBob3cgb2Z0ZW4gcnVubmVycyBhcmVcbiAqICAgcG9sbGVkLiBGYWlsZWQgbWVzc2FnZXMgYXJlIHJldHJpZWQgdW50aWwgdGhleSBzdWNjZWVkIG9yIHRoZSBydW5uZXJcbiAqICAgc2VsZi10ZXJtaW5hdGVzIGF0IGl0cyBpZGxlIHRpbWVvdXQuXG4gKiAtIEVhY2ggZmlsbCB1bmNvbmRpdGlvbmFsbHkgc3RhcnRzIGBjb3VudGAgcnVubmVycyDigJQgaXQgZG9lcyBub3QgY2hlY2sgaG93IG1hbnlcbiAqICAgYXJlIGFscmVhZHkgcnVubmluZy4gT24gY3JvbiBmaXJlLCB0aGlzIGNyZWF0ZXMgYSBicmllZiBvdmVybGFwIHdpdGggdGhlXG4gKiAgIHByZXZpb3VzIGN5Y2xlJ3MgcnVubmVycyAod2hpY2ggYXJlIG5lYXIgdGhlaXIgZGVhZGxpbmUpLlxuICogLSBSZW1vdmluZyBhbGwgd2FybSBydW5uZXJzIGNvbmZpZ3VyYXRpb25zIG1heSByZXN1bHQgaW4gd2FybSBydW5uZXJzIHN0YXlpbmdcbiAqICAgYXJvdW5kIHVudGlsIHRoZXkgZXhwaXJlLiBUbyByZW1vdmUgYWxsIHdhcm0gcnVubmVycyBxdWlja2x5LCBzZXQgY291bnQgdG8gMFxuICogICBhbmQgZGVwbG95LiBPbmx5IG9uY2UgYWxsIHRoZSB3YXJtIHJ1bm5lcnMgYXJlIHN0b3BwZWQsIHlvdSBjYW4gcmVtb3ZlIGFsbFxuICogICBjb25maWd1cmF0aW9ucyBhbmQgZGVwbG95IGFnYWluLlxuICogLSAqKkdhcHMgaW4gY292ZXJhZ2UqKjogVGhlIFN0ZXAgRnVuY3Rpb24gdGhhdCBwcm92aXNpb25zIGVhY2ggd2FybSBydW5uZXIgdXNlc1xuICogICBpbmNyZWFzaW5nIHRpbWVvdXRzIGJldHdlZW4gcmV0cmllcyBmb3IgcHJvdmlkZXIgZmFpbHVyZXMgKENvZGVCdWlsZCB0aW1lb3V0LFxuICogICBMYW1iZGEgdGltZW91dCwgY2FwYWNpdHkgZXJyb3JzLCBldGMuKS4gV2hpbGUgYSB3YXJtIHJ1bm5lciBzbG90IGlzIHJldHJ5aW5nLFxuICogICB0aGF0IHNsb3QgaGFzIG5vIHJ1bm5lci4gVGhpcyBtYXkgY3JlYXRlIGdhcHMgaW4gY292ZXJhZ2UuIEFuIGlkbGUgd2FybSBydW5uZXJcbiAqICAgdGhhdCBmYWlscyB0byBwcm92aXNpb24gKG9yIHdob3NlIHJlcGxhY2VtZW50IGZhaWxzKSB3aWxsIGJlIHVuYXZhaWxhYmxlIHVudGlsXG4gKiAgIHRoZSByZXRyeSBzdWNjZWVkcy4gQ3VycmVudCByZXRyeSBtZWNoYW5pc20gaGFzIGJ1aWx0LWluIGJhY2stb2ZmIHJhdGUgYW5kIGNhblxuICogICBiZSB0d2Vha2VkIHVzaW5nIGByZXRyeU9wdGlvbnNgLiBUaGlzIHdpbGwgYmUgaW1wcm92ZWQgaW4gdGhlIGZ1dHVyZS5cbiAqL1xuaW1wb3J0ICogYXMgY3J5cHRvIGZyb20gJ2NyeXB0byc7XG5pbXBvcnQge1xuICBEZXNjcmliZUV4ZWN1dGlvbkNvbW1hbmQsXG4gIFNGTkNsaWVudCxcbiAgU3RhcnRFeGVjdXRpb25Db21tYW5kLFxuICBTdG9wRXhlY3V0aW9uQ29tbWFuZCxcbiAgRXhlY3V0aW9uQWxyZWFkeUV4aXN0cyxcbn0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LXNmbic7XG5pbXBvcnQgeyBTUVNDbGllbnQsIFNlbmRNZXNzYWdlQ29tbWFuZCB9IGZyb20gJ0Bhd3Mtc2RrL2NsaWVudC1zcXMnO1xuaW1wb3J0IHR5cGUgeyBPY3Rva2l0IH0gZnJvbSAnQG9jdG9raXQvcmVzdCc7XG5pbXBvcnQgKiBhcyBBV1NMYW1iZGEgZnJvbSAnYXdzLWxhbWJkYSc7XG5pbXBvcnQgeyBkZWxldGVSdW5uZXIsIGdldEFwcE9jdG9raXQsIGdldE9jdG9raXQsIGdldFJ1bm5lciwgR2l0SHViU2VjcmV0cyB9IGZyb20gJy4vbGFtYmRhLWdpdGh1Yic7XG5pbXBvcnQgeyBjdXN0b21SZXNvdXJjZVJlc3BvbmQgfSBmcm9tICcuL2xhbWJkYS1oZWxwZXJzJztcblxuY29uc3Qgc2ZuID0gbmV3IFNGTkNsaWVudCgpO1xuY29uc3Qgc3FzID0gbmV3IFNRU0NsaWVudCgpO1xuXG5jb25zdCBTRk5fRVhFQ1VUSU9OX05BTUVfTUFYX0xFTkdUSCA9IDgwO1xuXG5leHBvcnQgaW50ZXJmYWNlIFdhcm1SdW5uZXJLZWVwZXJNZXNzYWdlIHtcbiAgcmVhZG9ubHkgZXhlY3V0aW9uQXJuOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHJ1bm5lck5hbWU6IHN0cmluZztcbiAgcmVhZG9ubHkgb3duZXI6IHN0cmluZztcbiAgcmVhZG9ubHkgcmVwbzogc3RyaW5nO1xuICByZWFkb25seSBpbnN0YWxsYXRpb25JZD86IG51bWJlcjtcbiAgcmVhZG9ubHkgcHJvdmlkZXJQYXRoOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHByb3ZpZGVyTGFiZWxzOiBzdHJpbmdbXTtcbiAgcmVhZG9ubHkgYWJzb2x1dGVEZWFkbGluZTogbnVtYmVyOyAvLyBVbml4IG1zIOKAlCBpbmhlcml0ZWQgYnkgcmVwbGFjZW1lbnRzXG4gIHJlYWRvbmx5IGNvbmZpZ0hhc2g6IHN0cmluZztcbn1cblxuLyoqXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBXYXJtUnVubmVyRmlsbFBheWxvYWQge1xuICByZWFkb25seSBhY3Rpb246ICdmaWxsJztcbiAgcmVhZG9ubHkgcHJvdmlkZXJQYXRoOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHByb3ZpZGVyTGFiZWxzOiBzdHJpbmdbXTtcbiAgcmVhZG9ubHkgY291bnQ6IG51bWJlcjtcbiAgcmVhZG9ubHkgZHVyYXRpb246IG51bWJlcjtcbiAgcmVhZG9ubHkgb3duZXI6IHN0cmluZztcbiAgcmVhZG9ubHkgcmVwbzogc3RyaW5nO1xuICByZWFkb25seSBjb25maWdIYXNoOiBzdHJpbmc7XG59XG5cbmZ1bmN0aW9uIGlzU3FzRXZlbnQoZXZlbnQ6IHVua25vd24pOiBldmVudCBpcyBBV1NMYW1iZGEuU1FTRXZlbnQge1xuICByZXR1cm4gQXJyYXkuaXNBcnJheSgoZXZlbnQgYXMgQVdTTGFtYmRhLlNRU0V2ZW50KS5SZWNvcmRzKTtcbn1cblxuZnVuY3Rpb24gaXNGaWxsSW5wdXQoZXZlbnQ6IHVua25vd24pOiBldmVudCBpcyBXYXJtUnVubmVyRmlsbFBheWxvYWQge1xuICByZXR1cm4gdHlwZW9mIGV2ZW50ID09PSAnb2JqZWN0JyAmJiBldmVudCAhPT0gbnVsbCAmJiAoZXZlbnQgYXMgV2FybVJ1bm5lckZpbGxQYXlsb2FkKS5hY3Rpb24gPT09ICdmaWxsJztcbn1cblxuZnVuY3Rpb24gaXNDdXN0b21SZXNvdXJjZUV2ZW50KGV2ZW50OiB1bmtub3duKTogZXZlbnQgaXMgQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCB7XG4gIGNvbnN0IGUgPSBldmVudCBhcyBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50O1xuICByZXR1cm4gdHlwZW9mIGU/LlJlcXVlc3RUeXBlID09PSAnc3RyaW5nJyAmJiB0eXBlb2YgZT8uUmVzcG9uc2VVUkwgPT09ICdzdHJpbmcnO1xufVxuXG5mdW5jdGlvbiByZXF1aXJlRW52KG5hbWU6IHN0cmluZykge1xuICBjb25zdCB2YWx1ZSA9IHByb2Nlc3MuZW52W25hbWVdO1xuICBpZiAoIXZhbHVlKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBNaXNzaW5nIGVudmlyb25tZW50IHZhcmlhYmxlICR7bmFtZX1gKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbmludGVyZmFjZSBTdGFydFdhcm1SdW5uZXJJbnB1dCB7XG4gIHJlYWRvbmx5IHByb3ZpZGVyUGF0aDogc3RyaW5nO1xuICByZWFkb25seSBwcm92aWRlckxhYmVsczogc3RyaW5nW107XG4gIHJlYWRvbmx5IG93bmVyOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHJlcG86IHN0cmluZztcbiAgcmVhZG9ubHkgaW5zdGFsbGF0aW9uSWQ/OiBudW1iZXI7XG4gIHJlYWRvbmx5IGFic29sdXRlRGVhZGxpbmU6IG51bWJlcjsgLy8gVW5peCBtc1xuICByZWFkb25seSBjb25maWdIYXNoOiBzdHJpbmc7XG4gIHJlYWRvbmx5IGV4ZWN1dGlvbk5hbWU6IHN0cmluZztcbiAgcmVhZG9ubHkgc2xvdD86IG51bWJlcjsgLy8gMC1iYXNlZCBpbmRleCB3aGVuIGZpbGxpbmcgbXVsdGlwbGUgc2xvdHM7IGhlbHBzIGNvcnJlbGF0ZSBsb2dzXG59XG5cbi8qKlxuICogRGV0ZXJtaW5pc3RpYyBleGVjdXRpb24gbmFtZSBmb3IgaWRlbXBvdGVudCBmaWxscy4gU2FtZSBzZWVkIOKGkiBzYW1lIG5hbWUsIHNvIHJldHJpZXNcbiAqIChjdXN0b20gcmVzb3VyY2UsIFNRUyByZWRlbGl2ZXJ5KSBkb24ndCBjcmVhdGUgZHVwbGljYXRlIHJ1bm5lcnMuXG4gKi9cbmZ1bmN0aW9uIGRldGVybWluaXN0aWNFeGVjdXRpb25OYW1lKHByb3ZpZGVyUGF0aDogc3RyaW5nLCBzZWVkOiBzdHJpbmcpIHtcbiAgY29uc3QgcGF0aFdpdGhvdXRTdGFjayA9IHByb3ZpZGVyUGF0aC5zcGxpdCgnLycpLnNsaWNlKDEpLmpvaW4oJy8nKSB8fCBwcm92aWRlclBhdGg7XG4gIGNvbnN0IHNhbml0aXplZCA9IGB3YXJtLSR7cGF0aFdpdGhvdXRTdGFjay5yZXBsYWNlKC9bXmEtekEtWjAtOS1dL2csICctJyl9YDtcbiAgY29uc3QgaGFzaCA9IGNyeXB0by5jcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoc2VlZCkuZGlnZXN0KCdoZXgnKS5zbGljZSgwLCAxNik7XG4gIGNvbnN0IG1heFByZWZpeExlbiA9IFNGTl9FWEVDVVRJT05fTkFNRV9NQVhfTEVOR1RIIC0gaGFzaC5sZW5ndGggLSAxO1xuICByZXR1cm4gYCR7c2FuaXRpemVkLnNsaWNlKDAsIG1heFByZWZpeExlbil9LSR7aGFzaH1gO1xufVxuXG4vKiogUmV0dXJucyBVbml4IG1zIG9mIHRoZSBuZXh0IG1pZG5pZ2h0IFVUQy4gVXNlZCBmb3IgQWx3YXlzT24gZGVwbG95bWVudC1maWxsIGRlYWRsaW5lLiAqL1xuZnVuY3Rpb24gZ2V0TmV4dE1pZG5pZ2h0VXRjTXMoKTogbnVtYmVyIHtcbiAgY29uc3Qgbm93ID0gbmV3IERhdGUoKTtcbiAgY29uc3QgbmV4dE1pZG5pZ2h0ID0gbmV3IERhdGUoRGF0ZS5VVEMobm93LmdldFVUQ0Z1bGxZZWFyKCksIG5vdy5nZXRVVENNb250aCgpLCBub3cuZ2V0VVRDRGF0ZSgpICsgMSwgMCwgMCwgMCwgMCkpO1xuICByZXR1cm4gbmV4dE1pZG5pZ2h0LmdldFRpbWUoKTtcbn1cblxuLyoqIEZpbmQgaW5zdGFsbGF0aW9uIGlkIGZvciBvdXIgYXBwLiBOb3JtYWwgY29kZSBwYXRoIGdldHMgdGhpcyBmcm9tIHRoZSB3ZWJob29rIHBheWxvYWQsIGJ1dCB3ZSBzY2hlZHVsZSB0aGVzZSBvdXJzZWx2ZXMuICovXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlSW5zdGFsbGF0aW9uSWQob3duZXI6IHN0cmluZywgcmVwbzogc3RyaW5nKSB7XG4gIGNvbnN0IGFwcE9jdG9raXQgPSBhd2FpdCBnZXRBcHBPY3Rva2l0KCk7XG4gIGlmICghYXBwT2N0b2tpdCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7IC8vIFBBVCBhdXRoZW50aWNhdGlvblxuICB9XG5cbiAgaWYgKHJlcG8pIHtcbiAgICBjb25zdCB7IGRhdGEgfSA9IGF3YWl0IGFwcE9jdG9raXQucmVzdC5hcHBzLmdldFJlcG9JbnN0YWxsYXRpb24oeyBvd25lciwgcmVwbyB9KTtcbiAgICByZXR1cm4gZGF0YS5pZDtcbiAgfSBlbHNlIHtcbiAgICBjb25zdCB7IGRhdGEgfSA9IGF3YWl0IGFwcE9jdG9raXQucmVzdC5hcHBzLmdldE9yZ0luc3RhbGxhdGlvbih7IG9yZzogb3duZXIgfSk7XG4gICAgcmV0dXJuIGRhdGEuaWQ7XG4gIH1cbn1cblxuLyoqIFN0YXJ0IGEgd2FybSBydW5uZXIgYW5kIGVucXVldWUgYSBrZWVwZXIgbWVzc2FnZSB1c2luZyBTUVMuICovXG5hc3luYyBmdW5jdGlvbiBzdGFydFdhcm1SdW5uZXJBbmRFbnF1ZXVlS2VlcGVyKGlucHV0OiBTdGFydFdhcm1SdW5uZXJJbnB1dCkge1xuICBjb25zdCBzdGVwRnVuY3Rpb25Bcm4gPSByZXF1aXJlRW52KCdTVEVQX0ZVTkNUSU9OX0FSTicpO1xuICBjb25zdCBxdWV1ZVVybCA9IHJlcXVpcmVFbnYoJ1dBUk1fUlVOTkVSX1FVRVVFX1VSTCcpO1xuXG4gIGNvbnN0IHJlbWFpbmluZ1NlY29uZHMgPSBNYXRoLmZsb29yKChpbnB1dC5hYnNvbHV0ZURlYWRsaW5lIC0gRGF0ZS5ub3coKSkgLyAxMDAwKTtcbiAgaWYgKHJlbWFpbmluZ1NlY29uZHMgPD0gMCkge1xuICAgIGNvbnNvbGUubG9nKHtcbiAgICAgIG5vdGljZTogJ0Fic29sdXRlIGRlYWRsaW5lIGFscmVhZHkgcGFzc2VkOyBub3Qgc3RhcnRpbmcgcmV