token-injectable-docker-builder
Version:
The TokenInjectableDockerBuilder is a flexible AWS CDK construct that enables the usage of AWS CDK tokens in the building, pushing, and deployment of Docker images to Amazon Elastic Container Registry (ECR). It leverages AWS CodeBuild and Lambda custom re
126 lines • 18.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveExcludes = resolveExcludes;
exports.buildBuildSpec = buildBuildSpec;
const fs = require("fs");
const path = require("path");
/**
* Parse a .dockerignore file at `sourcePath` (if present) and combine it with
* a user-supplied exclude list. The Dockerfile itself is never excluded —
* `.dockerignore` patterns matching `dockerFile` are filtered out so the
* S3 asset always contains the Dockerfile.
*/
function resolveExcludes(sourcePath, dockerFile, exclude) {
let effective = exclude;
if (!effective) {
const dockerignorePath = path.join(sourcePath, '.dockerignore');
if (fs.existsSync(dockerignorePath)) {
const fileContent = fs.readFileSync(dockerignorePath, 'utf8');
effective = fileContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith('#'));
}
}
if (!effective)
return undefined;
return effective.filter((pattern) => {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`^${escaped.replace(/\*/g, '.*').replace(/\?/g, '.')}$`, 'i');
return !regex.test(dockerFile);
});
}
/**
* Assemble the plain buildspec object that the construct passes to
* `BuildSpec.fromObject`. Pure function — easy to unit test.
*/
function buildBuildSpec(opts) {
const { imageTag, dockerFile, buildArgs, dockerLoginSecretArn, installCommands, preBuildCommands, cacheDisabled, platform, } = opts;
const buildArgsString = buildArgs
? Object.entries(buildArgs)
.map(([k, v]) => `--build-arg ${k}=${v}`)
.join(' ')
: '';
const dockerFileFlag = dockerFile ? `-f $CODEBUILD_SRC_DIR/${dockerFile}` : '';
const dockerLoginCommands = dockerLoginSecretArn
? [
'echo "Retrieving Docker credentials..."',
'apt-get update -y && apt-get install -y jq',
`DOCKER_USERNAME=$(aws secretsmanager get-secret-value --secret-id ${dockerLoginSecretArn} --query SecretString --output text | jq -r .username)`,
`DOCKER_PASSWORD=$(aws secretsmanager get-secret-value --secret-id ${dockerLoginSecretArn} --query SecretString --output text | jq -r .password)`,
'echo "Logging in to Docker Hub..."',
'echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin',
]
: ['echo "No Docker credentials. Skipping Docker Hub login."'];
const buildxInstallCommands = cacheDisabled
? []
: [
'echo "Setting up Docker buildx for ECR layer cache..."',
'docker buildx create --driver docker-container --name ecr-cache-builder --use 2>/dev/null || docker buildx use ecr-cache-builder',
];
const platformFlag = `--platform ${platform}`;
// --provenance=false --sbom=false: Docker Buildx v0.10+ adds attestations by default,
// producing OCI image indexes that AWS Lambda does not support.
// $CACHE_FROM_FLAG is set in pre_build: empty on first build (when
// the `:cache` tag doesn't exist yet — buildx 0.20 still rejects this
// even with `ignore-error=true` at the importer-configure step),
// populated on subsequent builds so cache is reused. This avoids the
// `failed to configure registry cache importer: ...:cache: not found`
// / `invalid response status 404` crash on the first deploy.
const buildCommand = cacheDisabled
? `docker build ${platformFlag} ${dockerFileFlag} ${buildArgsString} -t $ECR_REPO_URI:${imageTag} $CODEBUILD_SRC_DIR`
: `docker buildx build --push ${platformFlag} --provenance=false --sbom=false $CACHE_FROM_FLAG --cache-to type=registry,ref=$ECR_REPO_URI:cache,mode=max,image-manifest=true ${dockerFileFlag} ${buildArgsString} -t $ECR_REPO_URI:${imageTag} $CODEBUILD_SRC_DIR`;
// Pre-build cache probe: only attach --cache-from when the :cache tag
// already exists in this repo. Skipped when caching is disabled entirely.
const cacheProbeCommands = cacheDisabled
? []
: [
'echo "Probing for existing :cache tag in ECR..."',
'export ECR_REPO_NAME=$(echo $ECR_REPO_URI | cut -d/ -f2-)',
'if aws ecr describe-images --repository-name $ECR_REPO_NAME --image-ids imageTag=cache --region $AWS_DEFAULT_REGION >/dev/null 2>&1; then '
+ 'export CACHE_FROM_FLAG="--cache-from type=registry,ref=$ECR_REPO_URI:cache"; '
+ 'echo "Cache tag exists, will pull cache from registry."; '
+ 'else '
+ 'export CACHE_FROM_FLAG=""; '
+ 'echo "No :cache tag yet (first build); skipping --cache-from."; '
+ 'fi',
];
return {
version: '0.2',
phases: {
install: {
commands: [
'echo "Beginning install phase..."',
...(installCommands ?? []),
...buildxInstallCommands,
],
},
pre_build: {
commands: [
...(preBuildCommands ?? []),
...dockerLoginCommands,
'echo "Retrieving AWS Account ID..."',
'export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)',
'echo "Logging into Amazon ECR..."',
'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
...cacheProbeCommands,
],
},
build: {
commands: [
`echo "Building Docker image with tag ${imageTag}..."`,
buildCommand,
],
},
...(cacheDisabled && {
post_build: {
commands: [
`echo "Pushing Docker image with tag ${imageTag}..."`,
`docker push $ECR_REPO_URI:${imageTag}`,
],
},
}),
},
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQtc3BlYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9idWlsZC1zcGVjLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBU0EsMENBd0JDO0FBaUJELHdDQXlHQztBQTNKRCx5QkFBeUI7QUFDekIsNkJBQTZCO0FBRTdCOzs7OztHQUtHO0FBQ0gsU0FBZ0IsZUFBZSxDQUM3QixVQUFrQixFQUNsQixVQUFrQixFQUNsQixPQUE2QjtJQUU3QixJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUM7SUFDeEIsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ2YsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUNoRSxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxDQUFDO1lBQ3BDLE1BQU0sV0FBVyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDOUQsU0FBUyxHQUFHLFdBQVc7aUJBQ3BCLEtBQUssQ0FBQyxJQUFJLENBQUM7aUJBQ1gsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7aUJBQzFCLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDaEUsQ0FBQztJQUNILENBQUM7SUFFRCxJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRWpDLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQ2xDLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDN0QsTUFB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a0JBQWtCO2lCQUN0QjthQUNGO1lBQ0QsS0FBSyxFQUFFO2dCQUNMLFFBQVEsRUFBRTtvQkFDUix3Q0FBd0MsUUFBUSxNQUFNO29CQUN0RCxZQUFZO2lCQUNiO2FBQ0Y7WUFDRCxHQUFHLENBQUMsYUFBYSxJQUFJO2dCQUNuQixVQUFVLEVBQUU7b0JBQ1YsUUFBUSxFQUFFO3dCQUNSLHVDQUF1QyxRQUFRLE1BQU07d0JBQ3JELDZCQUE2QixRQUFRLEVBQUU7cUJBQ3hDO2lCQUNGO2FBQ0YsQ0FBQztTQUNIO0tBQ0YsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBmcyBmcm9tICdmcyc7XG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuXG4vKipcbiAqIFBhcnNlIGEgLmRvY2tlcmlnbm9yZSBmaWxlIGF0IGBzb3VyY2VQYXRoYCAoaWYgcHJlc2VudCkgYW5kIGNvbWJpbmUgaXQgd2l0aFxuICogYSB1c2VyLXN1cHBsaWVkIGV4Y2x1ZGUgbGlzdC4gVGhlIERvY2tlcmZpbGUgaXRzZWxmIGlzIG5ldmVyIGV4Y2x1ZGVkIOKAlFxuICogYC5kb2NrZXJpZ25vcmVgIHBhdHRlcm5zIG1hdGNoaW5nIGBkb2NrZXJGaWxlYCBhcmUgZmlsdGVyZWQgb3V0IHNvIHRoZVxuICogUzMgYXNzZXQgYWx3YXlzIGNvbnRhaW5zIHRoZSBEb2NrZXJmaWxlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUV4Y2x1ZGVzKFxuICBzb3VyY2VQYXRoOiBzdHJpbmcsXG4gIGRvY2tlckZpbGU6IHN0cmluZyxcbiAgZXhjbHVkZTogc3RyaW5nW10gfCB1bmRlZmluZWQsXG4pOiBzdHJpbmdbXSB8IHVuZGVmaW5lZCB7XG4gIGxldCBlZmZlY3RpdmUgPSBleGNsdWRlO1xuICBpZiAoIWVmZmVjdGl2ZSkge1xuICAgIGNvbnN0IGRvY2tlcmlnbm9yZVBhdGggPSBwYXRoLmpvaW4oc291cmNlUGF0aCwgJy5kb2NrZXJpZ25vcmUnKTtcbiAgICBpZiAoZnMuZXhpc3RzU3luYyhkb2NrZXJpZ25vcmVQYXRoKSkge1xuICAgICAgY29uc3QgZmlsZUNvbnRlbnQgPSBmcy5yZWFkRmlsZVN5bmMoZG9ja2VyaWdub3JlUGF0aCwgJ3V0ZjgnKTtcbiAgICAgIGVmZmVjdGl2ZSA9IGZpbGVDb250ZW50XG4gICAgICAgIC5zcGxpdCgnXFxuJylcbiAgICAgICAgLm1hcCgobGluZSkgPT4gbGluZS50cmltKCkpXG4gICAgICAgIC5maWx0ZXIoKGxpbmUpID0+IGxpbmUubGVuZ3RoID4gMCAmJiAhbGluZS5zdGFydHNXaXRoKCcjJykpO1xuICAgIH1cbiAgfVxuXG4gIGlmICghZWZmZWN0aXZlKSByZXR1cm4gdW5kZWZpbmVkO1xuXG4gIHJldHVybiBlZmZlY3RpdmUuZmlsdGVyKChwYXR0ZXJuKSA9PiB7XG4gICAgY29uc3QgZXNjYXBlZCA9IHBhdHRlcm4ucmVwbGFjZSgvWy4rXiR7fSgpfFtcXF1cXFxcXS9nLCAnXFxcXCQmJyk7XG4gICAgY29uc3QgcmVnZXggPSBuZXcgUmVnRXhwKGBeJHtlc2NhcGVkLnJlcGxhY2UoL1xcKi9nLCAnLionKS5yZXBsYWNlKC9cXD8vZywgJy4nKX0kYCwgJ2knKTtcbiAgICByZXR1cm4gIXJlZ2V4LnRlc3QoZG9ja2VyRmlsZSk7XG4gIH0pO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEJ1aWxkU3BlY09wdGlvbnMge1xuICByZWFkb25seSBpbWFnZVRhZzogc3RyaW5nO1xuICByZWFkb25seSBkb2NrZXJGaWxlPzogc3RyaW5nO1xuICByZWFkb25seSBidWlsZEFyZ3M/OiB7IFtrZXk6IHN0cmluZ106IHN0cmluZyB9O1xuICByZWFkb25seSBkb2NrZXJMb2dpblNlY3JldEFybj86IHN0cmluZztcbiAgcmVhZG9ubHkgaW5zdGFsbENvbW1hbmRzPzogc3RyaW5nW107XG4gIHJlYWRvbmx5IHByZUJ1aWxkQ29tbWFuZHM/OiBzdHJpbmdbXTtcbiAgcmVhZG9ubHkgY2FjaGVEaXNhYmxlZDogYm9vbGVhbjtcbiAgcmVhZG9ubHkgcGxhdGZvcm06ICdsaW51eC9hbWQ2NCcgfCAnbGludXgvYXJtNjQnO1xufVxuXG4vKipcbiAqIEFzc2VtYmxlIHRoZSBwbGFpbiBidWlsZHNwZWMgb2JqZWN0IHRoYXQgdGhlIGNvbnN0cnVjdCBwYXNzZXMgdG9cbiAqIGBCdWlsZFNwZWMuZnJvbU9iamVjdGAuIFB1cmUgZnVuY3Rpb24g4oCUIGVhc3kgdG8gdW5pdCB0ZXN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRCdWlsZFNwZWMob3B0czogQnVpbGRTcGVjT3B0aW9ucyk6IFJlY29yZDxzdHJpbmcsIHVua25vd24+IHtcbiAgY29uc3Qge1xuICAgIGltYWdlVGFnLFxuICAgIGRvY2tlckZpbGUsXG4gICAgYnVpbGRBcmdzLFxuICAgIGRvY2tlckxvZ2luU2VjcmV0QXJuLFxuICAgIGluc3RhbGxDb21tYW5kcyxcbiAgICBwcmVCdWlsZENvbW1hbmRzLFxuICAgIGNhY2hlRGlzYWJsZWQsXG4gICAgcGxhdGZvcm0sXG4gIH0gPSBvcHRzO1xuXG4gIGNvbnN0IGJ1aWxkQXJnc1N0cmluZyA9IGJ1aWxkQXJnc1xuICAgID8gT2JqZWN0LmVudHJpZXMoYnVpbGRBcmdzKVxuICAgICAgLm1hcCgoW2ssIHZdKSA9PiBgLS1idWlsZC1hcmcgJHtrfT0ke3Z9YClcbiAgICAgIC5qb2luKCcgJylcbiAgICA6ICcnO1xuXG4gIGNvbnN0IGRvY2tlckZpbGVGbGFnID0gZG9ja2VyRmlsZSA/IGAtZiAkQ09ERUJVSUxEX1NSQ19ESVIvJHtkb2NrZXJGaWxlfWAgOiAnJztcblxuICBjb25zdCBkb2NrZXJMb2dpbkNvbW1hbmRzID0gZG9ja2VyTG9naW5TZWNyZXRBcm5cbiAgICA/IFtcbiAgICAgICdlY2hvIFwiUmV0cmlldmluZyBEb2NrZXIgY3JlZGVudGlhbHMuLi5cIicsXG4gICAgICAnYXB0LWdldCB1cGRhdGUgLXkgJiYgYXB0LWdldCBpbnN0YWxsIC15IGpxJyxcbiAgICAgIGBET0NLRVJfVVNFUk5BTUU9JChhd3Mgc2VjcmV0c21hbmFnZXIgZ2V0LXNlY3JldC12YWx1ZSAtLXNlY3JldC1pZCAke2RvY2tlckxvZ2luU2VjcmV0QXJufSAtLXF1ZXJ5IFNlY3JldFN0cmluZyAtLW91dHB1dCB0ZXh0IHwganEgLXIgLnVzZXJuYW1lKWAsXG4gICAgICBgRE9DS0VSX1BBU1NXT1JEPSQoYXdzIHNlY3JldHNtYW5hZ2VyIGdldC1zZWNyZXQtdmFsdWUgLS1zZWNyZXQtaWQgJHtkb2NrZXJMb2dpblNlY3JldEFybn0gLS1xdWVyeSBTZWNyZXRTdHJpbmcgLS1vdXRwdXQgdGV4dCB8IGpxIC1yIC5wYXNzd29yZClgLFxuICAgICAgJ2VjaG8gXCJMb2dnaW5nIGluIHRvIERvY2tlciBIdWIuLi5cIicsXG4gICAgICAnZWNobyAkRE9DS0VSX1BBU1NXT1JEIHwgZG9ja2VyIGxvZ2luIC0tdXNlcm5hbWUgJERPQ0tFUl9VU0VSTkFNRSAtLXBhc3N3b3JkLXN0ZGluJyxcbiAgICBdXG4gICAgOiBbJ2VjaG8gXCJObyBEb2NrZXIgY3JlZGVudGlhbHMuIFNraXBwaW5nIERvY2tlciBIdWIgbG9naW4uXCInXTtcblxuICBjb25zdCBidWlsZHhJbnN0YWxsQ29tbWFuZHMgPSBjYWNoZURpc2FibGVkXG4gICAgPyBbXVxuICAgIDogW1xuICAgICAgJ2VjaG8gXCJTZXR0aW5nIHVwIERvY2tlciBidWlsZHggZm9yIEVDUiBsYXllciBjYWNoZS4uLlwiJyxcbiAgICAgICdkb2NrZXIgYnVpbGR4IGNyZWF0ZSAtLWRyaXZlciBkb2NrZXItY29udGFpbmVyIC0tbmFtZSBlY3ItY2FjaGUtYnVpbGRlciAtLXVzZSAyPi9kZXYvbnVsbCB8fCBkb2NrZXIgYnVpbGR4IHVzZSBlY3ItY2FjaGUtYnVpbGRlcicsXG4gICAgXTtcblxuICBjb25zdCBwbGF0Zm9ybUZsYWcgPSBgLS1wbGF0Zm9ybSAke3BsYXRmb3JtfWA7XG5cbiAgLy8gLS1wcm92ZW5hbmNlPWZhbHNlIC0tc2JvbT1mYWxzZTogRG9ja2VyIEJ1aWxkeCB2MC4xMCsgYWRkcyBhdHRlc3RhdGlvbnMgYnkgZGVmYXVsdCxcbiAgLy8gcHJvZHVjaW5nIE9DSSBpbWFnZSBpbmRleGVzIHRoYXQgQVdTIExhbWJkYSBkb2VzIG5vdCBzdXBwb3J0LlxuICAvLyAkQ0FDSEVfRlJPTV9GTEFHIGlzIHNldCBpbiBwcmVfYnVpbGQ6IGVtcHR5IG9uIGZpcnN0IGJ1aWxkICh3aGVuXG4gIC8vIHRoZSBgOmNhY2hlYCB0YWcgZG9lc24ndCBleGlzdCB5ZXQg4oCUIGJ1aWxkeCAwLjIwIHN0aWxsIHJlamVjdHMgdGhpc1xuICAvLyBldmVuIHdpdGggYGlnbm9yZS1lcnJvcj10cnVlYCBhdCB0aGUgaW1wb3J0ZXItY29uZmlndXJlIHN0ZXApLFxuICAvLyBwb3B1bGF0ZWQgb24gc3Vic2VxdWVudCBidWlsZHMgc28gY2FjaGUgaXMgcmV1c2VkLiBUaGlzIGF2b2lkcyB0aGVcbiAgLy8gYGZhaWxlZCB0byBjb25maWd1cmUgcmVnaXN0cnkgY2FjaGUgaW1wb3J0ZXI6IC4uLjpjYWNoZTogbm90IGZvdW5kYFxuICAvLyAvIGBpbnZhbGlkIHJlc3BvbnNlIHN0YXR1cyA0MDRgIGNyYXNoIG9uIHRoZSBmaXJzdCBkZXBsb3kuXG4gIGNvbnN0IGJ1aWxkQ29tbWFuZCA9IGNhY2hlRGlzYWJsZWRcbiAgICA/IGBkb2NrZXIgYnVpbGQgJHtwbGF0Zm9ybUZsYWd9ICR7ZG9ja2VyRmlsZUZsYWd9ICR7YnVpbGRBcmdzU3RyaW5nfSAtdCAkRUNSX1JFUE9fVVJJOiR7aW1hZ2VUYWd9ICRDT0RFQlVJTERfU1JDX0RJUmBcbiAgICA6IGBkb2NrZXIgYnVpbGR4IGJ1aWxkIC0tcHVzaCAke3BsYXRmb3JtRmxhZ30gLS1wcm92ZW5hbmNlPWZhbHNlIC0tc2JvbT1mYWxzZSAkQ0FDSEVfRlJPTV9GTEFHIC0tY2FjaGUtdG8gdHlwZT1yZWdpc3RyeSxyZWY9JEVDUl9SRVBPX1VSSTpjYWNoZSxtb2RlPW1heCxpbWFnZS1tYW5pZmVzdD10cnVlICR7ZG9ja2VyRmlsZUZsYWd9ICR7YnVpbGRBcmdzU3RyaW5nfSAtdCAkRUNSX1JFUE9fVVJJOiR7aW1hZ2VUYWd9ICRDT0RFQlVJTERfU1JDX0RJUmA7XG5cbiAgLy8gUHJlLWJ1aWxkIGNhY2hlIHByb2JlOiBvbmx5IGF0dGFjaCAtLWNhY2hlLWZyb20gd2hlbiB0aGUgOmNhY2hlIHRhZ1xuICAvLyBhbHJlYWR5IGV4aXN0cyBpbiB0aGlzIHJlcG8uIFNraXBwZWQgd2hlbiBjYWNoaW5nIGlzIGRpc2FibGVkIGVudGlyZWx5LlxuICBjb25zdCBjYWNoZVByb2JlQ29tbWFuZHMgPSBjYWNoZURpc2FibGVkXG4gICAgPyBbXVxuICAgIDogW1xuICAgICAgJ2VjaG8gXCJQcm9iaW5nIGZvciBleGlzdGluZyA6Y2FjaGUgdGFnIGluIEVDUi4uLlwiJyxcbiAgICAgICdleHBvcnQgRUNSX1JFUE9fTkFNRT0kKGVjaG8gJEVDUl9SRVBPX1VSSSB8IGN1dCAtZC8gLWYyLSknLFxuICAgICAgJ2lmIGF3cyBlY3IgZGVzY3JpYmUtaW1hZ2VzIC0tcmVwb3NpdG9yeS1uYW1lICRFQ1JfUkVQT19OQU1FIC0taW1hZ2UtaWRzIGltYWdlVGFnPWNhY2hlIC0tcmVnaW9uICRBV1NfREVGQVVMVF9SRUdJT04gPi9kZXYvbnVsbCAyPiYxOyB0aGVuICdcbiAgICAgICAgKyAnZXhwb3J0IENBQ0hFX0ZST01fRkxBRz1cIi0tY2FjaGUtZnJvbSB0eXBlPXJlZ2lzdHJ5LHJlZj0kRUNSX1JFUE9fVVJJOmNhY2hlXCI7ICdcbiAgICAgICAgKyAnZWNobyBcIkNhY2hlIHRhZyBleGlzdHMsIHdpbGwgcHVsbCBjYWNoZSBmcm9tIHJlZ2lzdHJ5LlwiOyAnXG4gICAgICAgICsgJ2Vsc2UgJ1xuICAgICAgICArICdleHBvcnQgQ0FDSEVfRlJPTV9GTEFHPVwiXCI7ICdcbiAgICAgICAgKyAnZWNobyBcIk5vIDpjYWNoZSB0YWcgeWV0IChmaXJzdCBidWlsZCk7IHNraXBwaW5nIC0tY2FjaGUtZnJvbS5cIjsgJ1xuICAgICAgICArICdmaScsXG4gICAgXTtcblxuICByZXR1cm4ge1xuICAgIHZlcnNpb246ICcwLjInLFxuICAgIHBoYXNlczoge1xuICAgICAgaW5zdGFsbDoge1xuICAgICAgICBjb21tYW5kczogW1xuICAgICAgICAgICdlY2hvIFwiQmVnaW5uaW5nIGluc3RhbGwgcGhhc2UuLi5cIicsXG4gICAgICAgICAgLi4uKGluc3RhbGxDb21tYW5kcyA/PyBbXSksXG4gICAgICAgICAgLi4uYnVpbGR4SW5zdGFsbENvbW1hbmRzLFxuICAgICAgICBdLFxuICAgICAgfSxcbiAgICAgIHByZV9idWlsZDoge1xuICAgICAgICBjb21tYW5kczogW1xuICAgICAgICAgIC4uLihwcmVCdWlsZENvbW1hbmRzID8/IFtdKSxcbiAgICAgICAgICAuLi5kb2NrZXJMb2dpbkNvbW1hbmRzLFxuICAgICAgICAgICdlY2hvIFwiUmV0cmlldmluZyBBV1MgQWNjb3VudCBJRC4uLlwiJyxcbiAgICAgICAgICAnZXhwb3J0IEFDQ09VTlRfSUQ9JChhd3Mgc3RzIGdldC1jYWxsZXItaWRlbnRpdHkgLS1xdWVyeSBBY2NvdW50IC0tb3V0cHV0IHRleHQpJyxcbiAgICAgICAgICAnZWNobyBcIkxvZ2dpbmcgaW50byBBbWF6b24gRUNSLi4uXCInLFxuICAgICAgICAgICdhd3MgZWNyIGdldC1sb2dpbi1wYXNzd29yZCAtLXJlZ2lvbiAkQVdTX0RFRkFVTFRfUkVHSU9OIHwgZG9ja2VyIGxvZ2luIC0tdXNlcm5hbWUgQVdTIC0tcGFzc3dvcmQtc3RkaW4gJEFDQ09VTlRfSUQuZGtyLmVjci4kQVdTX0RFRkFVTFRfUkVHSU9OLmFtYXpvbmF3cy5jb20nLFxuICAgICAgICAgIC4uLmNhY2hlUHJvYmVDb21tYW5kcyxcbiAgICAgICAgXSxcbiAgICAgIH0sXG4gICAgICBidWlsZDoge1xuICAgICAgICBjb21tYW5kczogW1xuICAgICAgICAgIGBlY2hvIFwiQnVpbGRpbmcgRG9ja2VyIGltYWdlIHdpdGggdGFnICR7aW1hZ2VUYWd9Li4uXCJgLFxuICAgICAgICAgIGJ1aWxkQ29tbWFuZCxcbiAgICAgICAgXSxcbiAgICAgIH0sXG4gICAgICAuLi4oY2FjaGVEaXNhYmxlZCAmJiB7XG4gICAgICAgIHBvc3RfYnVpbGQ6IHtcbiAgICAgICAgICBjb21tYW5kczogW1xuICAgICAgICAgICAgYGVjaG8gXCJQdXNoaW5nIERvY2tlciBpbWFnZSB3aXRoIHRhZyAke2ltYWdlVGFnfS4uLlwiYCxcbiAgICAgICAgICAgIGBkb2NrZXIgcHVzaCAkRUNSX1JFUE9fVVJJOiR7aW1hZ2VUYWd9YCxcbiAgICAgICAgICBdLFxuICAgICAgICB9LFxuICAgICAgfSksXG4gICAgfSxcbiAgfTtcbn1cbiJdfQ==