@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.
185 lines • 25.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyBody = verifyBody;
exports.handler = handler;
const crypto = require("crypto");
const client_sfn_1 = require("@aws-sdk/client-sfn");
const lambda_github_1 = require("./lambda-github");
const lambda_helpers_1 = require("./lambda-helpers");
const sf = new client_sfn_1.SFNClient();
// TODO use @octokit/webhooks?
function getHeader(event, header) {
// API Gateway doesn't lowercase headers (V1 event) but Lambda URLs do (V2 event) :(
for (const headerName of Object.keys(event.headers)) {
if (headerName.toLowerCase() === header.toLowerCase()) {
return event.headers[headerName];
}
}
return undefined;
}
/**
* Exported for unit testing.
* @internal
*/
function verifyBody(event, secret) {
const sig = Buffer.from(getHeader(event, 'x-hub-signature-256') || '', 'utf8');
if (!event.body) {
throw new Error('No body');
}
let body;
if (event.isBase64Encoded) {
body = Buffer.from(event.body, 'base64');
}
else {
body = Buffer.from(event.body || '', 'utf8');
}
const hmac = crypto.createHmac('sha256', secret);
hmac.update(body);
const expectedSig = Buffer.from(`sha256=${hmac.digest('hex')}`, 'utf8');
console.log('Calculated signature: ', expectedSig.toString());
if (sig.length !== expectedSig.length || !crypto.timingSafeEqual(sig, expectedSig)) {
throw new Error(`Signature mismatch. Expected ${expectedSig.toString()} but got ${sig.toString()}`);
}
return body.toString();
}
async function isDeploymentPending(payload) {
const statusesUrl = payload.deployment?.statuses_url;
if (statusesUrl === undefined) {
return false;
}
try {
const { octokit } = await (0, lambda_github_1.getOctokit)(payload.installation?.id);
const statuses = await octokit.request(statusesUrl);
return statuses.data[0]?.state === 'waiting';
}
catch (e) {
console.error('Unable to check deployment. Try adding deployment read permission.', e);
return false;
}
}
function matchLabelsToProvider(labels) {
const jobLabelSet = labels.map((label) => label.toLowerCase());
const supportedLabels = JSON.parse(process.env.SUPPORTED_LABELS);
// is every label the job requires available in the runner provider?
for (const supportedLabelSet of supportedLabels) {
const lowerCasedSupportedLabelSet = supportedLabelSet.labels.map((label) => label.toLowerCase());
if (jobLabelSet.every(label => label == 'self-hosted' || lowerCasedSupportedLabelSet.includes(label))) {
return supportedLabelSet.provider;
}
}
return undefined;
}
async function handler(event) {
if (!process.env.WEBHOOK_SECRET_ARN || !process.env.STEP_FUNCTION_ARN || !process.env.SUPPORTED_LABELS || !process.env.REQUIRE_SELF_HOSTED_LABEL) {
throw new Error('Missing environment variables');
}
const webhookSecret = (await (0, lambda_helpers_1.getSecretJsonValue)(process.env.WEBHOOK_SECRET_ARN)).webhookSecret;
let body;
try {
body = verifyBody(event, webhookSecret);
}
catch (e) {
console.error(e);
return {
statusCode: 403,
body: 'Bad signature',
};
}
if (getHeader(event, 'content-type') !== 'application/json') {
console.error(`This webhook only accepts JSON payloads, got ${getHeader(event, 'content-type')}`);
return {
statusCode: 400,
body: 'Expecting JSON payload',
};
}
if (getHeader(event, 'x-github-event') === 'ping') {
return {
statusCode: 200,
body: 'Pong',
};
}
// if (getHeader(event, 'x-github-event') !== 'workflow_job' && getHeader(event, 'x-github-event') !== 'workflow_run') {
// console.error(`This webhook only accepts workflow_job and workflow_run, got ${getHeader(event, 'x-github-event')}`);
if (getHeader(event, 'x-github-event') !== 'workflow_job') {
console.error(`This webhook only accepts workflow_job, got ${getHeader(event, 'x-github-event')}`);
return {
statusCode: 400,
body: 'Expecting workflow_job',
};
}
const payload = JSON.parse(body);
if (payload.action !== 'queued') {
console.log({
notice: `Ignoring action "${payload.action}", expecting "queued"`,
job: payload.workflow_job,
});
return {
statusCode: 200,
body: 'OK. No runner started (action is not "queued").',
};
}
if (process.env.REQUIRE_SELF_HOSTED_LABEL === '1' && !payload.workflow_job.labels.includes('self-hosted')) {
console.log({
notice: `Ignoring labels "${payload.workflow_job.labels}", expecting "self-hosted"`,
job: payload.workflow_job,
});
return {
statusCode: 200,
body: 'OK. No runner started (no "self-hosted" label).',
};
}
// don't start step function unless labels match a runner provider
const provider = matchLabelsToProvider(payload.workflow_job.labels);
if (!provider) {
console.log({
notice: `Ignoring labels "${payload.workflow_job.labels}", as they don't match a supported runner provider`,
job: payload.workflow_job,
});
return {
statusCode: 200,
body: 'OK. No runner started (no provider with matching labels).',
};
}
// don't start runners for a deployment that's still pending as GitHub will send another event when it's ready
if (await isDeploymentPending(payload)) {
console.log({
notice: 'Ignoring job as its deployment is still pending',
job: payload.workflow_job,
});
return {
statusCode: 200,
body: 'OK. No runner started (deployment pending).',
};
}
// set execution name which is also used as runner name which are limited to 64 characters
let executionName = payload.repository.full_name.replace('/', '-').slice(0, 50);
let deliveryId = getHeader(event, 'x-github-delivery') ?? `${Math.random()}`;
executionName = `${executionName}-${deliveryId.slice(0, 63 - executionName.length)}`;
// start execution
const input = {
owner: payload.repository.owner.login,
repo: payload.repository.name,
jobId: payload.workflow_job.id,
jobUrl: payload.workflow_job.html_url,
installationId: payload.installation?.id ?? -1, // always pass value because step function can't handle missing input
labels: payload.workflow_job.labels.join(','),
provider: provider,
};
const execution = await sf.send(new client_sfn_1.StartExecutionCommand({
stateMachineArn: process.env.STEP_FUNCTION_ARN,
input: JSON.stringify(input),
// name is not random so multiple execution of this webhook won't cause multiple builders to start
name: executionName,
}));
console.log({
notice: 'Started orchestrator',
execution: execution.executionArn,
sfnInput: input,
job: payload.workflow_job,
});
return {
statusCode: 202,
body: executionName,
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2ViaG9vay1oYW5kbGVyLmxhbWJkYS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy93ZWJob29rLWhhbmRsZXIubGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBMEJBLGdDQXlCQztBQWtDRCwwQkE0SEM7QUFqTkQsaUNBQWlDO0FBQ2pDLG9EQUF1RTtBQUV2RSxtREFBNkM7QUFDN0MscURBQXNEO0FBR3RELE1BQU0sRUFBRSxHQUFHLElBQUksc0JBQVMsRUFBRSxDQUFDO0FBRTNCLDhCQUE4QjtBQUU5QixTQUFTLFNBQVMsQ0FBQyxLQUF1QyxFQUFFLE1BQWM7SUFDeEUsb0ZBQW9GO0lBQ3BGLEtBQUssTUFBTSxVQUFVLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNwRCxJQUFJLFVBQVUsQ0FBQyxXQUFXLEVBQUUsS0FBSyxNQUFNLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQztZQUN0RCxPQUFPLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDbkMsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0IsVUFBVSxDQUFDLEtBQXVDLEVBQUUsTUFBVztJQUM3RSxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUscUJBQXFCLENBQUMsSUFBSSxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFFL0UsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFFRCxJQUFJLElBQVksQ0FBQztJQUNqQixJQUFJLEtBQUssQ0FBQyxlQUFlLEVBQUUsQ0FBQztRQUMxQixJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFL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TyxDQUFDLFVBQVUsQ0FBQyxJQUFJO1FBQzdCLEtBQUssRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUU7UUFDOUIsTUFBTSxFQUFFLE9BQU8sQ0FBQyxZQUFZLENBQUMsUUFBUTtRQUNyQyxjQUFjLEVBQUUsT0FBTyxDQUFDLFlBQVksRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDLEVBQUUscUVBQXFFO1FBQ3JILE1BQU0sRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO1FBQzdDLFFBQVEsRUFBRSxRQUFRO0tBQ25CLENBQUM7SUFDRixNQUFNLFNBQVMsR0FBRyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxrQ0FBcUIsQ0FBQztRQUN4RCxlQUFlLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUI7UUFDOUMsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDO1FBQzVCLGtHQUFrRztRQUNsRyxJQUFJLEVBQUUsYUFBYTtLQUNwQixDQUFDLENBQUMsQ0FBQztJQUVKLE9BQU8sQ0FBQyxHQUFHLENBQUM7UUFDVixNQUFNLEVBQUUsc0JBQXNCO1FBQzlCLFNBQVMsRUFBRSxTQUFTLENBQUMsWUFBWTtRQUNqQyxRQUFRLEVBQUUsS0FBSztRQUNmLEdBQUcsRUFBRSxPQUFPLENBQUMsWUFBWTtLQUMxQixDQUFDLENBQUM7SUFFSCxPQUFPO1FBQ0wsVUFBVSxFQUFFLEdBQUc7UUFDZixJQUFJLEVBQUUsYUFBYTtLQUNwQixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGNyeXB0byBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgU0ZOQ2xpZW50LCBTdGFydEV4ZWN1dGlvbkNvbW1hbmQgfSBmcm9tICdAYXdzLXNkay9jbGllbnQtc2ZuJztcbmltcG9ydCAqIGFzIEFXU0xhbWJkYSBmcm9tICdhd3MtbGFtYmRhJztcbmltcG9ydCB7IGdldE9jdG9raXQgfSBmcm9tICcuL2xhbWJkYS1naXRodWInO1xuaW1wb3J0IHsgZ2V0U2VjcmV0SnNvblZhbHVlIH0gZnJvbSAnLi9sYW1iZGEtaGVscGVycyc7XG5pbXBvcnQgeyBTdXBwb3J0ZWRMYWJlbHMgfSBmcm9tICcuL3dlYmhvb2snO1xuXG5jb25zdCBzZiA9IG5ldyBTRk5DbGllbnQoKTtcblxuLy8gVE9ETyB1c2UgQG9jdG9raXQvd2ViaG9va3M/XG5cbmZ1bmN0aW9uIGdldEhlYWRlcihldmVudDogQVdTTGFtYmRhLkFQSUdhdGV3YXlQcm94eUV2ZW50VjIsIGhlYWRlcjogc3RyaW5nKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgLy8gQVBJIEdhdGV3YXkgZG9lc24ndCBsb3dlcmNhc2UgaGVhZGVycyAoVjEgZXZlbnQpIGJ1dCBMYW1iZGEgVVJMcyBkbyAoVjIgZXZlbnQpIDooXG4gIGZvciAoY29uc3QgaGVhZGVyTmFtZSBvZiBPYmplY3Qua2V5cyhldmVudC5oZWFkZXJzKSkge1xuICAgIGlmIChoZWFkZXJOYW1lLnRvTG93ZXJDYXNlKCkgPT09IGhlYWRlci50b0xvd2VyQ2FzZSgpKSB7XG4gICAgICByZXR1cm4gZXZlbnQuaGVhZGVyc1toZWFkZXJOYW1lXTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIEV4cG9ydGVkIGZvciB1bml0IHRlc3RpbmcuXG4gKiBAaW50ZXJuYWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHZlcmlmeUJvZHkoZXZlbnQ6IEFXU0xhbWJkYS5BUElHYXRld2F5UHJveHlFdmVudFYyLCBzZWNyZXQ6IGFueSk6IHN0cmluZyB7XG4gIGNvbnN0IHNpZyA9IEJ1ZmZlci5mcm9tKGdldEhlYWRlcihldmVudCwgJ3gtaHViLXNpZ25hdHVyZS0yNTYnKSB8fCAnJywgJ3V0ZjgnKTtcblxuICBpZiAoIWV2ZW50LmJvZHkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ05vIGJvZHknKTtcbiAgfVxuXG4gIGxldCBib2R5OiBCdWZmZXI7XG4gIGlmIChldmVudC5pc0Jhc2U2NEVuY29kZWQpIHtcbiAgICBib2R5ID0gQnVmZmVyLmZyb20oZXZlbnQuYm9keSwgJ2Jhc2U2NCcpO1xuICB9IGVsc2Uge1xuICAgIGJvZHkgPSBCdWZmZXIuZnJvbShldmVudC5ib2R5IHx8ICcnLCAndXRmOCcpO1xuICB9XG5cbiAgY29uc3QgaG1hYyA9IGNyeXB0by5jcmVhdGVIbWFjKCdzaGEyNTYnLCBzZWNyZXQpO1xuICBobWFjLnVwZGF0ZShib2R5KTtcbiAgY29uc3QgZXhwZWN0ZWRTaWcgPSBCdWZmZXIuZnJvbShgc2hhMjU2PSR7aG1hYy5kaWdlc3QoJ2hleCcpfWAsICd1dGY4Jyk7XG5cbiAgY29uc29sZS5sb2coJ0NhbGN1bGF0ZWQgc2lnbmF0dXJlOiAnLCBleHBlY3RlZFNpZy50b1N0cmluZygpKTtcblxuICBpZiAoc2lnLmxlbmd0aCAhPT0gZXhwZWN0ZWRTaWcubGVuZ3RoIHx8ICFjcnlwdG8udGltaW5nU2FmZUVxdWFsKHNpZywgZXhwZWN0ZWRTaWcpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBTaWduYXR1cmUgbWlzbWF0Y2guIEV4cGVjdGVkICR7ZXhwZWN0ZWRTaWcudG9TdHJpbmcoKX0gYnV0IGdvdCAke3NpZy50b1N0cmluZygpfWApO1xuICB9XG5cbiAgcmV0dXJuIGJvZHkudG9TdHJpbmcoKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gaXNEZXBsb3ltZW50UGVuZGluZyhwYXlsb2FkOiBhbnkpIHtcbiAgY29uc3Qgc3RhdHVzZXNVcmwgPSBwYXlsb2FkLmRlcGxveW1lbnQ/LnN0YXR1c2VzX3VybDtcbiAgaWYgKHN0YXR1c2VzVXJsID09PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICB0cnkge1xuICAgIGNvbnN0IHsgb2N0b2tpdCB9ID0gYXdhaXQgZ2V0T2N0b2tpdChwYXlsb2FkLmluc3RhbGxhdGlvbj8uaWQpO1xuICAgIGNvbnN0IHN0YXR1c2VzID0gYXdhaXQgb2N0b2tpdC5yZXF1ZXN0KHN0YXR1c2VzVXJsKTtcblxuICAgIHJldHVybiBzdGF0dXNlcy5kYXRhWzBdPy5zdGF0ZSA9PT0gJ3dhaXRpbmcnO1xuICB9IGNhdGNoIChlKSB7XG4gICAgY29uc29sZS5lcnJvcignVW5hYmxlIHRvIGNoZWNrIGRlcGxveW1lbnQuIFRyeSBhZGRpbmcgZGVwbG95bWVudCByZWFkIHBlcm1pc3Npb24uJywgZSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbmZ1bmN0aW9uIG1hdGNoTGFiZWxzVG9Qcm92aWRlcihsYWJlbHM6IHN0cmluZ1tdKSB7XG4gIGNvbnN0IGpvYkxhYmVsU2V0ID0gbGFiZWxzLm1hcCgobGFiZWwpID0+IGxhYmVsLnRvTG93ZXJDYXNlKCkpO1xuICBjb25zdCBzdXBwb3J0ZWRMYWJlbHM6IFN1cHBvcnRlZExhYmVsc1tdID0gSlNPTi5wYXJzZShwcm9jZXNzLmVudi5TVVBQT1JURURfTEFCRUxTISk7XG5cbiAgLy8gaXMgZXZlcnkgbGFiZWwgdGhlIGpvYiByZXF1aXJlcyBhdmFpbGFibGUgaW4gdGhlIHJ1bm5lciBwcm92aWRlcj9cbiAgZm9yIChjb25zdCBzdXBwb3J0ZWRMYWJlbFNldCBvZiBzdXBwb3J0ZWRMYWJlbHMpIHtcbiAgICBjb25zdCBsb3dlckNhc2VkU3VwcG9ydGVkTGFiZWxTZXQgPSBzdXBwb3J0ZWRMYWJlbFNldC5sYWJlbHMubWFwKChsYWJlbCkgPT4gbGFiZWwudG9Mb3dlckNhc2UoKSk7XG4gICAgaWYgKGpvYkxhYmVsU2V0LmV2ZXJ5KGxhYmVsID0+IGxhYmVsID09ICdzZWxmLWhvc3RlZCcgfHwgbG93ZXJDYXNlZFN1cHBvcnRlZExhYmVsU2V0LmluY2x1ZGVzKGxhYmVsKSkpIHtcbiAgICAgIHJldHVybiBzdXBwb3J0ZWRMYWJlbFNldC5wcm92aWRlcjtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaGFuZGxlcihldmVudDogQVdTTGFtYmRhLkFQSUdhdGV3YXlQcm94eUV2ZW50VjIpOiBQcm9taXNlPEFXU0xhbWJkYS5BUElHYXRld2F5UHJveHlSZXN1bHRWMj4ge1xuICBpZiAoIXByb2Nlc3MuZW52LldFQkhPT0tfU0VDUkVUX0FSTiB8fCAhcHJvY2Vzcy5lbnYuU1RFUF9GVU5DVElPTl9BUk4gfHwgIXByb2Nlc3MuZW52LlNVUFBPUlRFRF9MQUJFTFMgfHwgIXByb2Nlc3MuZW52LlJFUVVJUkVfU0VMRl9IT1NURURfTEFCRUwpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgZW52aXJvbm1lbnQgdmFyaWFibGVzJyk7XG4gIH1cblxuICBjb25zdCB3ZWJob29rU2VjcmV0ID0gKGF3YWl0IGdldFNlY3JldEpzb25WYWx1ZShwcm9jZXNzLmVudi5XRUJIT09LX1NFQ1JFVF9BUk4pKS53ZWJob29rU2VjcmV0O1xuXG4gIGxldCBib2R5O1xuICB0cnkge1xuICAgIGJvZHkgPSB2ZXJpZnlCb2R5KGV2ZW50LCB3ZWJob29rU2VjcmV0KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnNvbGUuZXJyb3IoZSk7XG4gICAgcmV0dXJuIHtcbiAgICAgIHN0YXR1c0NvZGU6IDQwMyxcbiAgICAgIGJvZHk6ICdCYWQgc2lnbmF0dXJlJyxcbiAgICB9O1xuICB9XG5cbiAgaWYgKGdldEhlYWRlcihldmVudCwgJ2NvbnRlbnQtdHlwZScpICE9PSAnYXBwbGljYXRpb24vanNvbicpIHtcbiAgICBjb25zb2xlLmVycm9yKGBUaGlzIHdlYmhvb2sgb25seSBhY2NlcHRzIEpTT04gcGF5bG9hZHMsIGdvdCAke2dldEhlYWRlcihldmVudCwgJ2NvbnRlbnQtdHlwZScpfWApO1xuICAgIHJldHVybiB7XG4gICAgICBzdGF0dXNDb2RlOiA0MDAsXG4gICAgICBib2R5OiAnRXhwZWN0aW5nIEpTT04gcGF5bG9hZCcsXG4gICAgfTtcbiAgfVxuXG4gIGlmIChnZXRIZWFkZXIoZXZlbnQsICd4LWdpdGh1Yi1ldmVudCcpID09PSAncGluZycpIHtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdHVzQ29kZTogMjAwLFxuICAgICAgYm9keTogJ1BvbmcnLFxuICAgIH07XG4gIH1cblxuICAvLyBpZiAoZ2V0SGVhZGVyKGV2ZW50LCAneC1naXRodWItZXZlbnQnKSAhPT0gJ3dvcmtmbG93X2pvYicgJiYgZ2V0SGVhZGVyKGV2ZW50LCAneC1naXRodWItZXZlbnQnKSAhPT0gJ3dvcmtmbG93X3J1bicpIHtcbiAgLy8gICAgIGNvbnNvbGUuZXJyb3IoYFRoaXMgd2ViaG9vayBvbmx5IGFjY2VwdHMgd29ya2Zsb3dfam9iIGFuZCB3b3JrZmxvd19ydW4sIGdvdCAke2dldEhlYWRlcihldmVudCwgJ3gtZ2l0aHViLWV2ZW50Jyl9YCk7XG4gIGlmIChnZXRIZWFkZXIoZXZlbnQsICd4LWdpdGh1Yi1ldmVudCcpICE9PSAnd29ya2Zsb3dfam9iJykge1xuICAgIGNvbnNvbGUuZXJyb3IoYFRoaXMgd2ViaG9vayBvbmx5IGFjY2VwdHMgd29ya2Zsb3dfam9iLCBnb3QgJHtnZXRIZWFkZXIoZXZlbnQsICd4LWdpdGh1Yi1ldmVudCcpfWApO1xuICAgIHJldHVybiB7XG4gICAgICBzdGF0dXNDb2RlOiA0MDAsXG4gICAgICBib2R5OiAnRXhwZWN0aW5nIHdvcmtmbG93X2pvYicsXG4gICAgfTtcbiAgfVxuXG4gIGNvbnN0IHBheWxvYWQgPSBKU09OLnBhcnNlKGJvZHkpO1xuXG4gIGlmIChwYXlsb2FkLmFjdGlvbiAhPT0gJ3F1ZXVlZCcpIHtcbiAgICBjb25zb2xlLmxvZyh7XG4gICAgICBub3RpY2U6IGBJZ25vcmluZyBhY3Rpb24gXCIke3BheWxvYWQuYWN0aW9ufVwiLCBleHBlY3RpbmcgXCJxdWV1ZWRcImAsXG4gICAgICBqb2I6IHBheWxvYWQud29ya2Zsb3dfam9iLFxuICAgIH0pO1xuICAgIHJldHVybiB7XG4gICAgICBzdGF0dXNDb2RlOiAyMDAsXG4gICAgICBib2R5OiAnT0suIE5vIHJ1bm5lciBzdGFydGVkIChhY3Rpb24gaXMgbm90IFwicXVldWVkXCIpLicsXG4gICAgfTtcbiAgfVxuXG4gIGlmIChwcm9jZXNzLmVudi5SRVFVSVJFX1NFTEZfSE9TVEVEX0xBQkVMID09PSAnMScgJiYgIXBheWxvYWQud29ya2Zsb3dfam9iLmxhYmVscy5pbmNsdWRlcygnc2VsZi1ob3N0ZWQnKSkge1xuICAgIGNvbnNvbGUubG9nKHtcbiAgICAgIG5vdGljZTogYElnbm9yaW5nIGxhYmVscyBcIiR7cGF5bG9hZC53b3JrZmxvd19qb2IubGFiZWxzfVwiLCBleHBlY3RpbmcgXCJzZWxmLWhvc3RlZFwiYCxcbiAgICAgIGpvYjogcGF5bG9hZC53b3JrZmxvd19qb2IsXG4gICAgfSk7XG4gICAgcmV0dXJuIHtcbiAgICAgIHN0YXR1c0NvZGU6IDIwMCxcbiAgICAgIGJvZHk6ICdPSy4gTm8gcnVubmVyIHN0YXJ0ZWQgKG5vIFwic2VsZi1ob3N0ZWRcIiBsYWJlbCkuJyxcbiAgICB9O1xuICB9XG5cbiAgLy8gZG9uJ3Qgc3RhcnQgc3RlcCBmdW5jdGlvbiB1bmxlc3MgbGFiZWxzIG1hdGNoIGEgcnVubmVyIHByb3ZpZGVyXG4gIGNvbnN0IHByb3ZpZGVyID0gbWF0Y2hMYWJlbHNUb1Byb3ZpZGVyKHBheWxvYWQud29ya2Zsb3dfam9iLmxhYmVscyk7XG4gIGlmICghcHJvdmlkZXIpIHtcbiAgICBjb25zb2xlLmxvZyh7XG4gICAgICBub3RpY2U6IGBJZ25vcmluZyBsYWJlbHMgXCIke3BheWxvYWQud29ya2Zsb3dfam9iLmxhYmVsc31cIiwgYXMgdGhleSBkb24ndCBtYXRjaCBhIHN1cHBvcnRlZCBydW5uZXIgcHJvdmlkZXJgLFxuICAgICAgam9iOiBwYXlsb2FkLndvcmtmbG93X2pvYixcbiAgICB9KTtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdHVzQ29kZTogMjAwLFxuICAgICAgYm9keTogJ09LLiBObyBydW5uZXIgc3RhcnRlZCAobm8gcHJvdmlkZXIgd2l0aCBtYXRjaGluZyBsYWJlbHMpLicsXG4gICAgfTtcbiAgfVxuXG4gIC8vIGRvbid0IHN0YXJ0IHJ1bm5lcnMgZm9yIGEgZGVwbG95bWVudCB0aGF0J3Mgc3RpbGwgcGVuZGluZyBhcyBHaXRIdWIgd2lsbCBzZW5kIGFub3RoZXIgZXZlbnQgd2hlbiBpdCdzIHJlYWR5XG4gIGlmIChhd2FpdCBpc0RlcGxveW1lbnRQZW5kaW5nKHBheWxvYWQpKSB7XG4gICAgY29uc29sZS5sb2coe1xuICAgICAgbm90aWNlOiAnSWdub3Jpbmcgam9iIGFzIGl0cyBkZXBsb3ltZW50IGlzIHN0aWxsIHBlbmRpbmcnLFxuICAgICAgam9iOiBwYXlsb2FkLndvcmtmbG93X2pvYixcbiAgICB9KTtcbiAgICByZXR1cm4ge1xuICAgICAgc3RhdHVzQ29kZTogMjAwLFxuICAgICAgYm9keTogJ09LLiBObyBydW5uZXIgc3RhcnRlZCAoZGVwbG95bWVudCBwZW5kaW5nKS4nLFxuICAgIH07XG4gIH1cblxuICAvLyBzZXQgZXhlY3V0aW9uIG5hbWUgd2hpY2ggaXMgYWxzbyB1c2VkIGFzIHJ1bm5lciBuYW1lIHdoaWNoIGFyZSBsaW1pdGVkIHRvIDY0IGNoYXJhY3RlcnNcbiAgbGV0IGV4ZWN1dGlvbk5hbWUgPSBwYXlsb2FkLnJlcG9zaXRvcnkuZnVsbF9uYW1lLnJlcGxhY2UoJy8nLCAnLScpLnNsaWNlKDAsIDUwKTtcbiAgbGV0IGRlbGl2ZXJ5SWQgPSBnZXRIZWFkZXIoZXZlbnQsICd4LWdpdGh1Yi1kZWxpdmVyeScpID8/IGAke01hdGgucmFuZG9tKCl9YDtcbiAgZXhlY3V0aW9uTmFtZSA9IGAke2V4ZWN1dGlvbk5hbWV9LSR7ZGVsaXZlcnlJZC5zbGljZSgwLCA2My1leGVjdXRpb25OYW1lLmxlbmd0aCl9YDtcbiAgLy8gc3RhcnQgZXhlY3V0aW9uXG4gIGNvbnN0IGlucHV0ID0ge1xuICAgIG93bmVyOiBwYXlsb2FkLnJlcG9zaXRvcnkub3duZXIubG9naW4sXG4gICAgcmVwbzogcGF5bG9hZC5yZXBvc2l0b3J5Lm5hbWUsXG4gICAgam9iSWQ6IHBheWxvYWQud29ya2Zsb3dfam9iLmlkLFxuICAgIGpvYlVybDogcGF5bG9hZC53b3JrZmxvd19qb2IuaHRtbF91cmwsXG4gICAgaW5zdGFsbGF0aW9uSWQ6IHBheWxvYWQuaW5zdGFsbGF0aW9uPy5pZCA/PyAtMSwgLy8gYWx3YXlzIHBhc3MgdmFsdWUgYmVjYXVzZSBzdGVwIGZ1bmN0aW9uIGNhbid0IGhhbmRsZSBtaXNzaW5nIGlucHV0XG4gICAgbGFiZWxzOiBwYXlsb2FkLndvcmtmbG93X2pvYi5sYWJlbHMuam9pbignLCcpLFxuICAgIHByb3ZpZGVyOiBwcm92aWRlcixcbiAgfTtcbiAgY29uc3QgZXhlY3V0aW9uID0gYXdhaXQgc2Yuc2VuZChuZXcgU3RhcnRFeGVjdXRpb25Db21tYW5kKHtcbiAgICBzdGF0ZU1hY2hpbmVBcm46IHByb2Nlc3MuZW52LlNURVBfRlVOQ1RJT05fQVJOLFxuICAgIGlucHV0OiBKU09OLnN0cmluZ2lmeShpbnB1dCksXG4gICAgLy8gbmFtZSBpcyBub3QgcmFuZG9tIHNvIG11bHRpcGxlIGV4ZWN1dGlvbiBvZiB0aGlzIHdlYmhvb2sgd29uJ3QgY2F1c2UgbXVsdGlwbGUgYnVpbGRlcnMgdG8gc3RhcnRcbiAgICBuYW1lOiBleGVjdXRpb25OYW1lLFxuICB9KSk7XG5cbiAgY29uc29sZS5sb2coe1xuICAgIG5vdGljZTogJ1N0YXJ0ZWQgb3JjaGVzdHJhdG9yJyxcbiAgICBleGVjdXRpb246IGV4ZWN1dGlvbi5leGVjdXRpb25Bcm4sXG4gICAgc2ZuSW5wdXQ6IGlucHV0LFxuICAgIGpvYjogcGF5bG9hZC53b3JrZmxvd19qb2IsXG4gIH0pO1xuXG4gIHJldHVybiB7XG4gICAgc3RhdHVzQ29kZTogMjAyLFxuICAgIGJvZHk6IGV4ZWN1dGlvbk5hbWUsXG4gIH07XG59XG4iXX0=