aws-cdk
Version:
CDK Toolkit, the command line tool for CDK apps
584 lines • 78.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParameterValues = exports.TemplateParameters = exports.CloudFormationStack = void 0;
exports.waitForChangeSet = waitForChangeSet;
exports.createDiffChangeSet = createDiffChangeSet;
exports.uploadStackTemplateAssets = uploadStackTemplateAssets;
exports.cleanupOldChangeset = cleanupOldChangeset;
exports.changeSetHasNoChanges = changeSetHasNoChanges;
exports.waitForStackDelete = waitForStackDelete;
exports.waitForStackDeploy = waitForStackDeploy;
exports.stabilizeStack = stabilizeStack;
const cxapi = require("@aws-cdk/cx-api");
const cx_api_1 = require("@aws-cdk/cx-api");
const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
const cdk_assets_1 = require("cdk-assets");
const asset_manifest_builder_1 = require("./asset-manifest-builder");
const logging_1 = require("../../logging");
const serialize_1 = require("../../serialize");
const error_1 = require("../../toolkit/error");
const error_2 = require("../../util/error");
const stack_status_1 = require("../util/cloudformation/stack-status");
const template_body_parameter_1 = require("../util/template-body-parameter");
/**
* Represents an (existing) Stack in CloudFormation
*
* Bundle and cache some information that we need during deployment (so we don't have to make
* repeated calls to CloudFormation).
*/
class CloudFormationStack {
static async lookup(cfn, stackName, retrieveProcessedTemplate = false) {
try {
const response = await cfn.describeStacks({ StackName: stackName });
return new CloudFormationStack(cfn, stackName, response.Stacks && response.Stacks[0], retrieveProcessedTemplate);
}
catch (e) {
if (e.name === 'ValidationError' && (0, error_2.formatErrorMessage)(e) === `Stack with id ${stackName} does not exist`) {
return new CloudFormationStack(cfn, stackName, undefined);
}
throw e;
}
}
/**
* Return a copy of the given stack that does not exist
*
* It's a little silly that it needs arguments to do that, but there we go.
*/
static doesNotExist(cfn, stackName) {
return new CloudFormationStack(cfn, stackName);
}
/**
* From static information (for testing)
*/
static fromStaticInformation(cfn, stackName, stack) {
return new CloudFormationStack(cfn, stackName, stack);
}
constructor(cfn, stackName, stack, retrieveProcessedTemplate = false) {
this.cfn = cfn;
this.stackName = stackName;
this.stack = stack;
this.retrieveProcessedTemplate = retrieveProcessedTemplate;
}
/**
* Retrieve the stack's deployed template
*
* Cached, so will only be retrieved once. Will return an empty
* structure if the stack does not exist.
*/
async template() {
if (!this.exists) {
return {};
}
if (this._template === undefined) {
const response = await this.cfn.getTemplate({
StackName: this.stackName,
TemplateStage: this.retrieveProcessedTemplate ? 'Processed' : 'Original',
});
this._template = (response.TemplateBody && (0, serialize_1.deserializeStructure)(response.TemplateBody)) || {};
}
return this._template;
}
/**
* Whether the stack exists
*/
get exists() {
return this.stack !== undefined;
}
/**
* The stack's ID
*
* Throws if the stack doesn't exist.
*/
get stackId() {
this.assertExists();
return this.stack.StackId;
}
/**
* The stack's current outputs
*
* Empty object if the stack doesn't exist
*/
get outputs() {
if (!this.exists) {
return {};
}
const result = {};
(this.stack.Outputs || []).forEach((output) => {
result[output.OutputKey] = output.OutputValue;
});
return result;
}
/**
* The stack's status
*
* Special status NOT_FOUND if the stack does not exist.
*/
get stackStatus() {
if (!this.exists) {
return new stack_status_1.StackStatus('NOT_FOUND', 'Stack not found during lookup');
}
return stack_status_1.StackStatus.fromStackDescription(this.stack);
}
/**
* The stack's current tags
*
* Empty list if the stack does not exist
*/
get tags() {
return this.stack?.Tags || [];
}
/**
* SNS Topic ARNs that will receive stack events.
*
* Empty list if the stack does not exist
*/
get notificationArns() {
return this.stack?.NotificationARNs ?? [];
}
/**
* Return the names of all current parameters to the stack
*
* Empty list if the stack does not exist.
*/
get parameterNames() {
return Object.keys(this.parameters);
}
/**
* Return the names and values of all current parameters to the stack
*
* Empty object if the stack does not exist.
*/
get parameters() {
if (!this.exists) {
return {};
}
const ret = {};
for (const param of this.stack.Parameters ?? []) {
ret[param.ParameterKey] = param.ResolvedValue ?? param.ParameterValue;
}
return ret;
}
/**
* Return the termination protection of the stack
*/
get terminationProtection() {
return this.stack?.EnableTerminationProtection;
}
assertExists() {
if (!this.exists) {
throw new error_1.ToolkitError(`No stack named '${this.stackName}'`);
}
}
}
exports.CloudFormationStack = CloudFormationStack;
/**
* Describe a changeset in CloudFormation, regardless of its current state.
*
* @param cfn a CloudFormation client
* @param stackName the name of the Stack the ChangeSet belongs to
* @param changeSetName the name of the ChangeSet
* @param fetchAll if true, fetches all pages of the change set description.
*
* @returns CloudFormation information about the ChangeSet
*/
async function describeChangeSet(cfn, stackName, changeSetName, { fetchAll }) {
const response = await cfn.describeChangeSet({
StackName: stackName,
ChangeSetName: changeSetName,
});
// If fetchAll is true, traverse all pages from the change set description.
while (fetchAll && response.NextToken != null) {
const nextPage = await cfn.describeChangeSet({
StackName: stackName,
ChangeSetName: response.ChangeSetId ?? changeSetName,
NextToken: response.NextToken,
});
// Consolidate the changes
if (nextPage.Changes != null) {
response.Changes = response.Changes != null ? response.Changes.concat(nextPage.Changes) : nextPage.Changes;
}
// Forward the new NextToken
response.NextToken = nextPage.NextToken;
}
return response;
}
/**
* Waits for a function to return non-+undefined+ before returning.
*
* @param valueProvider a function that will return a value that is not +undefined+ once the wait should be over
* @param timeout the time to wait between two calls to +valueProvider+
*
* @returns the value that was returned by +valueProvider+
*/
async function waitFor(valueProvider, timeout = 5000) {
while (true) {
const result = await valueProvider();
if (result === null) {
return undefined;
}
else if (result !== undefined) {
return result;
}
await new Promise((cb) => setTimeout(cb, timeout));
}
}
/**
* Waits for a ChangeSet to be available for triggering a StackUpdate.
*
* Will return a changeset that is either ready to be executed or has no changes.
* Will throw in other cases.
*
* @param cfn a CloudFormation client
* @param stackName the name of the Stack that the ChangeSet belongs to
* @param changeSetName the name of the ChangeSet
* @param fetchAll if true, fetches all pages of the ChangeSet before returning.
*
* @returns the CloudFormation description of the ChangeSet
*/
async function waitForChangeSet(cfn, stackName, changeSetName, { fetchAll }) {
(0, logging_1.debug)('Waiting for changeset %s on stack %s to finish creating...', changeSetName, stackName);
const ret = await waitFor(async () => {
const description = await describeChangeSet(cfn, stackName, changeSetName, {
fetchAll,
});
// The following doesn't use a switch because tsc will not allow fall-through, UNLESS it is allows
// EVERYWHERE that uses this library directly or indirectly, which is undesirable.
if (description.Status === 'CREATE_PENDING' || description.Status === 'CREATE_IN_PROGRESS') {
(0, logging_1.debug)('Changeset %s on stack %s is still creating', changeSetName, stackName);
return undefined;
}
if (description.Status === client_cloudformation_1.ChangeSetStatus.CREATE_COMPLETE || changeSetHasNoChanges(description)) {
return description;
}
// eslint-disable-next-line max-len
throw new error_1.ToolkitError(`Failed to create ChangeSet ${changeSetName} on ${stackName}: ${description.Status || 'NO_STATUS'}, ${description.StatusReason || 'no reason provided'}`);
});
if (!ret) {
throw new error_1.ToolkitError('Change set took too long to be created; aborting');
}
return ret;
}
/**
* Create a changeset for a diff operation
*/
async function createDiffChangeSet(options) {
// `options.stack` has been modified to include any nested stack templates directly inline with its own template, under a special `NestedTemplate` property.
// Thus the parent template's Resources section contains the nested template's CDK metadata check, which uses Fn::Equals.
// This causes CreateChangeSet to fail with `Template Error: Fn::Equals cannot be partially collapsed`.
for (const resource of Object.values(options.stack.template.Resources ?? {})) {
if (resource.Type === 'AWS::CloudFormation::Stack') {
(0, logging_1.debug)('This stack contains one or more nested stacks, falling back to template-only diff...');
return undefined;
}
}
return uploadBodyParameterAndCreateChangeSet(options);
}
/**
* Returns all file entries from an AssetManifestArtifact that look like templates.
*
* This is used in the `uploadBodyParameterAndCreateChangeSet` function to find
* all template asset files to build and publish.
*
* Returns a tuple of [AssetManifest, FileManifestEntry[]]
*/
function templatesFromAssetManifestArtifact(artifact) {
const assets = [];
const fileName = artifact.file;
const assetManifest = cdk_assets_1.AssetManifest.fromFile(fileName);
assetManifest.entries.forEach((entry) => {
if (entry.type === 'file') {
const source = entry.source;
if (source.path && source.path.endsWith('.template.json')) {
assets.push(entry);
}
}
});
return [assetManifest, assets];
}
async function uploadBodyParameterAndCreateChangeSet(options) {
try {
await uploadStackTemplateAssets(options.stack, options.deployments);
const env = await options.deployments.envs.accessStackForMutableStackOperations(options.stack);
const bodyParameter = await (0, template_body_parameter_1.makeBodyParameter)(options.stack, env.resolvedEnvironment, new asset_manifest_builder_1.AssetManifestBuilder(), env.resources);
const cfn = env.sdk.cloudFormation();
const exists = (await CloudFormationStack.lookup(cfn, options.stack.stackName, false)).exists;
const executionRoleArn = await env.replacePlaceholders(options.stack.cloudFormationExecutionRoleArn);
options.stream.write('Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)\n');
return await createChangeSet({
cfn,
changeSetName: 'cdk-diff-change-set',
stack: options.stack,
exists,
uuid: options.uuid,
willExecute: options.willExecute,
bodyParameter,
parameters: options.parameters,
resourcesToImport: options.resourcesToImport,
role: executionRoleArn,
});
}
catch (e) {
(0, logging_1.debug)(e);
options.stream.write('Could not create a change set, will base the diff on template differences (run again with -v to see the reason)\n');
return undefined;
}
}
/**
* Uploads the assets that look like templates for this CloudFormation stack
*
* This is necessary for any CloudFormation call that needs the template, it may need
* to be uploaded to an S3 bucket first. We have to follow the instructions in the
* asset manifest, because technically that is the only place that knows about
* bucket and assumed roles and such.
*/
async function uploadStackTemplateAssets(stack, deployments) {
for (const artifact of stack.dependencies) {
// Skip artifact if it is not an Asset Manifest Artifact
if (!cxapi.AssetManifestArtifact.isAssetManifestArtifact(artifact)) {
continue;
}
const [assetManifest, file_entries] = templatesFromAssetManifestArtifact(artifact);
for (const entry of file_entries) {
await deployments.buildSingleAsset(artifact, assetManifest, entry, {
stack,
});
await deployments.publishSingleAsset(assetManifest, entry, {
stack,
});
}
}
}
async function createChangeSet(options) {
await cleanupOldChangeset(options.changeSetName, options.stack.stackName, options.cfn);
(0, logging_1.debug)(`Attempting to create ChangeSet with name ${options.changeSetName} for stack ${options.stack.stackName}`);
const templateParams = TemplateParameters.fromTemplate(options.stack.template);
const stackParams = templateParams.supplyAll(options.parameters);
const changeSet = await options.cfn.createChangeSet({
StackName: options.stack.stackName,
ChangeSetName: options.changeSetName,
ChangeSetType: options.resourcesToImport ? 'IMPORT' : options.exists ? 'UPDATE' : 'CREATE',
Description: `CDK Changeset for diff ${options.uuid}`,
ClientToken: `diff${options.uuid}`,
TemplateURL: options.bodyParameter.TemplateURL,
TemplateBody: options.bodyParameter.TemplateBody,
Parameters: stackParams.apiParameters,
ResourcesToImport: options.resourcesToImport,
RoleARN: options.role,
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'],
});
(0, logging_1.debug)('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id);
// Fetching all pages if we'll execute, so we can have the correct change count when monitoring.
const createdChangeSet = await waitForChangeSet(options.cfn, options.stack.stackName, options.changeSetName, {
fetchAll: options.willExecute,
});
await cleanupOldChangeset(options.changeSetName, options.stack.stackName, options.cfn);
return createdChangeSet;
}
async function cleanupOldChangeset(changeSetName, stackName, cfn) {
// Delete any existing change sets generated by CDK since change set names must be unique.
// The delete request is successful as long as the stack exists (even if the change set does not exist).
(0, logging_1.debug)(`Removing existing change set with name ${changeSetName} if it exists`);
await cfn.deleteChangeSet({
StackName: stackName,
ChangeSetName: changeSetName,
});
}
/**
* Return true if the given change set has no changes
*
* This must be determined from the status, not the 'Changes' array on the
* object; the latter can be empty because no resources were changed, but if
* there are changes to Outputs, the change set can still be executed.
*/
function changeSetHasNoChanges(description) {
const noChangeErrorPrefixes = [
// Error message for a regular template
"The submitted information didn't contain changes.",
// Error message when a Transform is involved (see #10650)
'No updates are to be performed.',
];
return (description.Status === 'FAILED' && noChangeErrorPrefixes.some((p) => (description.StatusReason ?? '').startsWith(p)));
}
/**
* Waits for a CloudFormation stack to stabilize in a complete/available state
* after a delete operation is issued.
*
* Fails if the stack is in a FAILED state. Will not fail if the stack was
* already deleted.
*
* @param cfn a CloudFormation client
* @param stackName the name of the stack to wait for after a delete
*
* @returns the CloudFormation description of the stabilized stack after the delete attempt
*/
async function waitForStackDelete(cfn, stackName) {
const stack = await stabilizeStack(cfn, stackName);
if (!stack) {
return undefined;
}
const status = stack.stackStatus;
if (status.isFailure) {
throw new error_1.ToolkitError(`The stack named ${stackName} is in a failed state. You may need to delete it from the AWS console : ${status}`);
}
else if (status.isDeleted) {
return undefined;
}
return stack;
}
/**
* Waits for a CloudFormation stack to stabilize in a complete/available state
* after an update/create operation is issued.
*
* Fails if the stack is in a FAILED state, ROLLBACK state, or DELETED state.
*
* @param cfn a CloudFormation client
* @param stackName the name of the stack to wait for after an update
*
* @returns the CloudFormation description of the stabilized stack after the update attempt
*/
async function waitForStackDeploy(cfn, stackName) {
const stack = await stabilizeStack(cfn, stackName);
if (!stack) {
return undefined;
}
const status = stack.stackStatus;
if (status.isCreationFailure) {
throw new error_1.ToolkitError(`The stack named ${stackName} failed creation, it may need to be manually deleted from the AWS console: ${status}`);
}
else if (!status.isDeploySuccess) {
throw new error_1.ToolkitError(`The stack named ${stackName} failed to deploy: ${status}`);
}
return stack;
}
/**
* Wait for a stack to become stable (no longer _IN_PROGRESS), returning it
*/
async function stabilizeStack(cfn, stackName) {
(0, logging_1.debug)('Waiting for stack %s to finish creating or updating...', stackName);
return waitFor(async () => {
const stack = await CloudFormationStack.lookup(cfn, stackName);
if (!stack.exists) {
(0, logging_1.debug)('Stack %s does not exist', stackName);
return null;
}
const status = stack.stackStatus;
if (status.isInProgress) {
(0, logging_1.debug)('Stack %s has an ongoing operation in progress and is not stable (%s)', stackName, status);
return undefined;
}
else if (status.isReviewInProgress) {
// This may happen if a stack creation operation is interrupted before the ChangeSet execution starts. Recovering
// from this would requiring manual intervention (deleting or executing the pending ChangeSet), and failing to do
// so will result in an endless wait here (the ChangeSet wont delete or execute itself). Instead of blocking
// "forever" we proceed as if the stack was existing and stable. If there is a concurrent operation that just
// hasn't finished proceeding just yet, either this operation or the concurrent one may fail due to the other one
// having made progress. Which is fine. I guess.
(0, logging_1.debug)('Stack %s is in REVIEW_IN_PROGRESS state. Considering this is a stable status (%s)', stackName, status);
}
return stack;
});
}
/**
* The set of (formal) parameters that have been declared in a template
*/
class TemplateParameters {
static fromTemplate(template) {
return new TemplateParameters(template.Parameters || {});
}
constructor(params) {
this.params = params;
}
/**
* Calculate stack parameters to pass from the given desired parameter values
*
* Will throw if parameters without a Default value or a Previous value are not
* supplied.
*/
supplyAll(updates) {
return new ParameterValues(this.params, updates);
}
/**
* From the template, the given desired values and the current values, calculate the changes to the stack parameters
*
* Will take into account parameters already set on the template (will emit
* 'UsePreviousValue: true' for those unless the value is changed), and will
* throw if parameters without a Default value or a Previous value are not
* supplied.
*/
updateExisting(updates, previousValues) {
return new ParameterValues(this.params, updates, previousValues);
}
}
exports.TemplateParameters = TemplateParameters;
/**
* The set of parameters we're going to pass to a Stack
*/
class ParameterValues {
constructor(formalParams, updates, previousValues = {}) {
this.formalParams = formalParams;
this.values = {};
this.apiParameters = [];
const missingRequired = new Array();
for (const [key, formalParam] of Object.entries(this.formalParams)) {
// Check updates first, then use the previous value (if available), then use
// the default (if available).
//
// If we don't find a parameter value using any of these methods, then that's an error.
const updatedValue = updates[key];
if (updatedValue !== undefined) {
this.values[key] = updatedValue;
this.apiParameters.push({
ParameterKey: key,
ParameterValue: updates[key],
});
continue;
}
if (key in previousValues) {
this.values[key] = previousValues[key];
this.apiParameters.push({ ParameterKey: key, UsePreviousValue: true });
continue;
}
if (formalParam.Default !== undefined) {
this.values[key] = formalParam.Default;
continue;
}
// Oh no
missingRequired.push(key);
}
if (missingRequired.length > 0) {
throw new error_1.ToolkitError(`The following CloudFormation Parameters are missing a value: ${missingRequired.join(', ')}`);
}
// Just append all supplied overrides that aren't really expected (this
// will fail CFN but maybe people made typos that they want to be notified
// of)
const unknownParam = ([key, _]) => this.formalParams[key] === undefined;
const hasValue = ([_, value]) => !!value;
for (const [key, value] of Object.entries(updates).filter(unknownParam).filter(hasValue)) {
this.values[key] = value;
this.apiParameters.push({ ParameterKey: key, ParameterValue: value });
}
}
/**
* Whether this set of parameter updates will change the actual stack values
*/
hasChanges(currentValues) {
// If any of the parameters are SSM parameters, deploying must always happen
// because we can't predict what the values will be. We will allow some
// parameters to opt out of this check by having a magic string in their description.
if (Object.values(this.formalParams).some((p) => p.Type.startsWith('AWS::SSM::Parameter::') && !p.Description?.includes(cx_api_1.SSMPARAM_NO_INVALIDATE))) {
return 'ssm';
}
// Otherwise we're dirty if:
// - any of the existing values are removed, or changed
if (Object.entries(currentValues).some(([key, value]) => !(key in this.values) || value !== this.values[key])) {
return true;
}
// - any of the values we're setting are new
if (Object.keys(this.values).some((key) => !(key in currentValues))) {
return true;
}
return false;
}
}
exports.ParameterValues = ParameterValues;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvdWRmb3JtYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjbG91ZGZvcm1hdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUE4UkEsNENBaUNDO0FBNkJELGtEQWVDO0FBK0VELDhEQWlCQztBQWtDRCxrREFRQztBQVNELHNEQVdDO0FBY0QsZ0RBa0JDO0FBYUQsZ0RBb0JDO0FBS0Qsd0NBd0JDO0FBdm1CRCx5Q0FBeUM7QUFDekMsNENBQXlEO0FBQ3pELDBFQVF3QztBQUN4QywyQ0FBOEQ7QUFDOUQscUVBQWdFO0FBQ2hFLDJDQUFzQztBQUN0QywrQ0FBdUQ7QUFDdkQsK0NBQW1EO0FBQ25ELDRDQUFzRDtBQUd0RCxzRUFBa0U7QUFDbEUsNkVBQTJGO0FBa0IzRjs7Ozs7R0FLRztBQUNILE1BQWEsbUJBQW1CO0lBQ3ZCLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUN4QixHQUEwQixFQUMxQixTQUFpQixFQUNqQiw0QkFBcUMsS0FBSztRQUUxQyxJQUFJLENBQUM7WUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsQ0FBQyxjQUFjLENBQUMsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztZQUNwRSxPQUFPLElBQUksbUJBQW1CLENBQUMsR0FBRyxFQUFFLFNBQVMsRUFBRSxRQUFRLENBQUMsTUFBTSxJQUFJLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUseUJBQXlCLENBQUMsQ0FBQztRQUNuSCxDQUFDO1FBQUMsT0FBTyxDQUFNLEVBQUUsQ0FBQztZQUNoQixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssaUJBQWlCLElBQUksSUFBQSwwQkFBa0IsRUFBQyxDQUFDLENBQUMsS0FBSyxpQkFBaUIsU0FBUyxpQkFBaUIsRUFBRSxDQUFDO2dCQUMxRyxPQUFPLElBQUksbUJBQW1CLENBQUMsR0FBRyxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FBQztZQUM1RCxDQUFDO1lBQ0QsTUFB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bnQiOlsiaW1wb3J0ICogYXMgY3hhcGkgZnJvbSAnQGF3cy1jZGsvY3gtYXBpJztcbmltcG9ydCB7IFNTTVBBUkFNX05PX0lOVkFMSURBVEUgfSBmcm9tICdAYXdzLWNkay9jeC1hcGknO1xuaW1wb3J0IHtcbiAgQ2hhbmdlU2V0U3RhdHVzLFxuICB0eXBlIERlc2NyaWJlQ2hhbmdlU2V0Q29tbWFuZE91dHB1dCxcbiAgdHlwZSBQYXJhbWV0ZXIsXG4gIHR5cGUgUmVzb3VyY2VJZGVudGlmaWVyU3VtbWFyeSxcbiAgdHlwZSBSZXNvdXJjZVRvSW1wb3J0LFxuICB0eXBlIFN0YWNrLFxuICB0eXBlIFRhZyxcbn0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LWNsb3VkZm9ybWF0aW9uJztcbmltcG9ydCB7IEFzc2V0TWFuaWZlc3QsIEZpbGVNYW5pZmVzdEVudHJ5IH0gZnJvbSAnY2RrLWFzc2V0cyc7XG5pbXBvcnQgeyBBc3NldE1hbmlmZXN0QnVpbGRlciB9IGZyb20gJy4vYXNzZXQtbWFuaWZlc3QtYnVpbGRlcic7XG5pbXBvcnQgeyBkZWJ1ZyB9IGZyb20gJy4uLy4uL2xvZ2dpbmcnO1xuaW1wb3J0IHsgZGVzZXJpYWxpemVTdHJ1Y3R1cmUgfSBmcm9tICcuLi8uLi9zZXJpYWxpemUnO1xuaW1wb3J0IHsgVG9vbGtpdEVycm9yIH0gZnJvbSAnLi4vLi4vdG9vbGtpdC9lcnJvcic7XG5pbXBvcnQgeyBmb3JtYXRFcnJvck1lc3NhZ2UgfSBmcm9tICcuLi8uLi91dGlsL2Vycm9yJztcbmltcG9ydCB0eXBlIHsgSUNsb3VkRm9ybWF0aW9uQ2xpZW50LCBTZGtQcm92aWRlciB9IGZyb20gJy4uL2F3cy1hdXRoJztcbmltcG9ydCB0eXBlIHsgRGVwbG95bWVudHMgfSBmcm9tICcuL2RlcGxveW1lbnRzJztcbmltcG9ydCB7IFN0YWNrU3RhdHVzIH0gZnJvbSAnLi4vdXRpbC9jbG91ZGZvcm1hdGlvbi9zdGFjay1zdGF0dXMnO1xuaW1wb3J0IHsgbWFrZUJvZHlQYXJhbWV0ZXIsIFRlbXBsYXRlQm9keVBhcmFtZXRlciB9IGZyb20gJy4uL3V0aWwvdGVtcGxhdGUtYm9keS1wYXJhbWV0ZXInO1xuXG5leHBvcnQgdHlwZSBSZXNvdXJjZXNUb0ltcG9ydCA9IFJlc291cmNlVG9JbXBvcnRbXTtcbmV4cG9ydCB0eXBlIFJlc291cmNlSWRlbnRpZmllclN1bW1hcmllcyA9IFJlc291cmNlSWRlbnRpZmllclN1bW1hcnlbXTtcbmV4cG9ydCB0eXBlIFJlc291cmNlSWRlbnRpZmllclByb3BlcnRpZXMgPSBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuXG5leHBvcnQgdHlwZSBUZW1wbGF0ZSA9IHtcbiAgUGFyYW1ldGVycz86IFJlY29yZDxzdHJpbmcsIFRlbXBsYXRlUGFyYW1ldGVyPjtcbiAgW2tleTogc3RyaW5nXTogYW55O1xufTtcblxuaW50ZXJmYWNlIFRlbXBsYXRlUGFyYW1ldGVyIHtcbiAgVHlwZTogc3RyaW5nO1xuICBEZWZhdWx0PzogYW55O1xuICBEZXNjcmlwdGlvbj86IHN0cmluZztcbiAgW2tleTogc3RyaW5nXTogYW55O1xufVxuXG4vKipcbiAqIFJlcHJlc2VudHMgYW4gKGV4aXN0aW5nKSBTdGFjayBpbiBDbG91ZEZvcm1hdGlvblxuICpcbiAqIEJ1bmRsZSBhbmQgY2FjaGUgc29tZSBpbmZvcm1hdGlvbiB0aGF0IHdlIG5lZWQgZHVyaW5nIGRlcGxveW1lbnQgKHNvIHdlIGRvbid0IGhhdmUgdG8gbWFrZVxuICogcmVwZWF0ZWQgY2FsbHMgdG8gQ2xvdWRGb3JtYXRpb24pLlxuICovXG5leHBvcnQgY2xhc3MgQ2xvdWRGb3JtYXRpb25TdGFjayB7XG4gIHB1YmxpYyBzdGF0aWMgYXN5bmMgbG9va3VwKFxuICAgIGNmbjogSUNsb3VkRm9ybWF0aW9uQ2xpZW50LFxuICAgIHN0YWNrTmFtZTogc3RyaW5nLFxuICAgIHJldHJpZXZlUHJvY2Vzc2VkVGVtcGxhdGU6IGJvb2xlYW4gPSBmYWxzZSxcbiAgKTogUHJvbWlzZTxDbG91ZEZvcm1hdGlvblN0YWNrPiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgY2ZuLmRlc2NyaWJlU3RhY2tzKHsgU3RhY2tOYW1lOiBzdGFja05hbWUgfSk7XG4gICAgICByZXR1cm4gbmV3IENsb3VkRm9ybWF0aW9uU3RhY2soY2ZuLCBzdGFja05hbWUsIHJlc3BvbnNlLlN0YWNrcyAmJiByZXNwb25zZS5TdGFja3NbMF0sIHJldHJpZXZlUHJvY2Vzc2VkVGVtcGxhdGUpO1xuICAgIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgICAgaWYgKGUubmFtZSA9PT0gJ1ZhbGlkYXRpb25FcnJvcicgJiYgZm9ybWF0RXJyb3JNZXNzYWdlKGUpID09PSBgU3RhY2sgd2l0aCBpZCAke3N0YWNrTmFtZX0gZG9lcyBub3QgZXhpc3RgKSB7XG4gICAgICAgIHJldHVybiBuZXcgQ2xvdWRGb3JtYXRpb25TdGFjayhjZm4sIHN0YWNrTmFtZSwgdW5kZWZpbmVkKTtcbiAgICAgIH1cbiAgICAgIHRocm93IGU7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiBhIGNvcHkgb2YgdGhlIGdpdmVuIHN0YWNrIHRoYXQgZG9lcyBub3QgZXhpc3RcbiAgICpcbiAgICogSXQncyBhIGxpdHRsZSBzaWxseSB0aGF0IGl0IG5lZWRzIGFyZ3VtZW50cyB0byBkbyB0aGF0LCBidXQgdGhlcmUgd2UgZ28uXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGRvZXNOb3RFeGlzdChjZm46IElDbG91ZEZvcm1hdGlvbkNsaWVudCwgc3RhY2tOYW1lOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gbmV3IENsb3VkRm9ybWF0aW9uU3RhY2soY2ZuLCBzdGFja05hbWUpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZyb20gc3RhdGljIGluZm9ybWF0aW9uIChmb3IgdGVzdGluZylcbiAgICovXG4gIHB1YmxpYyBzdGF0aWMgZnJvbVN0YXRpY0luZm9ybWF0aW9uKGNmbjogSUNsb3VkRm9ybWF0aW9uQ2xpZW50LCBzdGFja05hbWU6IHN0cmluZywgc3RhY2s6IFN0YWNrKSB7XG4gICAgcmV0dXJuIG5ldyBDbG91ZEZvcm1hdGlvblN0YWNrKGNmbiwgc3RhY2tOYW1lLCBzdGFjayk7XG4gIH1cblxuICBwcml2YXRlIF90ZW1wbGF0ZTogYW55O1xuXG4gIHByb3RlY3RlZCBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IGNmbjogSUNsb3VkRm9ybWF0aW9uQ2xpZW50LFxuICAgIHB1YmxpYyByZWFkb25seSBzdGFja05hbWU6IHN0cmluZyxcbiAgICBwcml2YXRlIHJlYWRvbmx5IHN0YWNrPzogU3RhY2ssXG4gICAgcHJpdmF0ZSByZWFkb25seSByZXRyaWV2ZVByb2Nlc3NlZFRlbXBsYXRlOiBib29sZWFuID0gZmFsc2UsXG4gICkge31cblxuICAvKipcbiAgICogUmV0cmlldmUgdGhlIHN0YWNrJ3MgZGVwbG95ZWQgdGVtcGxhdGVcbiAgICpcbiAgICogQ2FjaGVkLCBzbyB3aWxsIG9ubHkgYmUgcmV0cmlldmVkIG9uY2UuIFdpbGwgcmV0dXJuIGFuIGVtcHR5XG4gICAqIHN0cnVjdHVyZSBpZiB0aGUgc3RhY2sgZG9lcyBub3QgZXhpc3QuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgdGVtcGxhdGUoKTogUHJvbWlzZTxUZW1wbGF0ZT4ge1xuICAgIGlmICghdGhpcy5leGlzdHMpIHtcbiAgICAgIHJldHVybiB7fTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fdGVtcGxhdGUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0aGlzLmNmbi5nZXRUZW1wbGF0ZSh7XG4gICAgICAgIFN0YWNrTmFtZTogdGhpcy5zdGFja05hbWUsXG4gICAgICAgIFRlbXBsYXRlU3RhZ2U6IHRoaXMucmV0cmlldmVQcm9jZXNzZWRUZW1wbGF0ZSA/ICdQcm9jZXNzZWQnIDogJ09yaWdpbmFsJyxcbiAgICAgIH0pO1xuICAgICAgdGhpcy5fdGVtcGxhdGUgPSAocmVzcG9uc2UuVGVtcGxhdGVCb2R5ICYmIGRlc2VyaWFsaXplU3RydWN0dXJlKHJlc3BvbnNlLlRlbXBsYXRlQm9keSkpIHx8IHt9O1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fdGVtcGxhdGU7XG4gIH1cblxuICAvKipcbiAgICogV2hldGhlciB0aGUgc3RhY2sgZXhpc3RzXG4gICAqL1xuICBwdWJsaWMgZ2V0IGV4aXN0cygpIHtcbiAgICByZXR1cm4gdGhpcy5zdGFjayAhPT0gdW5kZWZpbmVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBzdGFjaydzIElEXG4gICAqXG4gICAqIFRocm93cyBpZiB0aGUgc3RhY2sgZG9lc24ndCBleGlzdC5cbiAgICovXG4gIHB1YmxpYyBnZXQgc3RhY2tJZCgpIHtcbiAgICB0aGlzLmFzc2VydEV4aXN0cygpO1xuICAgIHJldHVybiB0aGlzLnN0YWNrIS5TdGFja0lkITtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgc3RhY2sncyBjdXJyZW50IG91dHB1dHNcbiAgICpcbiAgICogRW1wdHkgb2JqZWN0IGlmIHRoZSBzdGFjayBkb2Vzbid0IGV4aXN0XG4gICAqL1xuICBwdWJsaWMgZ2V0IG91dHB1dHMoKTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiB7XG4gICAgaWYgKCF0aGlzLmV4aXN0cykge1xuICAgICAgcmV0dXJuIHt9O1xuICAgIH1cbiAgICBjb25zdCByZXN1bHQ6IHsgW25hbWU6IHN0cmluZ106IHN0cmluZyB9ID0ge307XG4gICAgKHRoaXMuc3RhY2shLk91dHB1dHMgfHwgW10pLmZvckVhY2goKG91dHB1dCkgPT4ge1xuICAgICAgcmVzdWx0W291dHB1dC5PdXRwdXRLZXkhXSA9IG91dHB1dC5PdXRwdXRWYWx1ZSE7XG4gICAgfSk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgc3RhY2sncyBzdGF0dXNcbiAgICpcbiAgICogU3BlY2lhbCBzdGF0dXMgTk9UX0ZPVU5EIGlmIHRoZSBzdGFjayBkb2VzIG5vdCBleGlzdC5cbiAgICovXG4gIHB1YmxpYyBnZXQgc3RhY2tTdGF0dXMoKTogU3RhY2tTdGF0dXMge1xuICAgIGlmICghdGhpcy5leGlzdHMpIHtcbiAgICAgIHJldHVybiBuZXcgU3RhY2tTdGF0dXMoJ05PVF9GT1VORCcsICdTdGFjayBub3QgZm91bmQgZHVyaW5nIGxvb2t1cCcpO1xuICAgIH1cbiAgICByZXR1cm