UNPKG

@aws/cloudfront-hosting-toolkit

Version:

CloudFront Hosting Toolkit offers the convenience of a managed frontend hosting service while retaining full control over the hosting and deployment infrastructure to make it your own.

588 lines (585 loc) 77.4 kB
#!/usr/bin/env node "use strict"; /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getPipelineStatus = exports.startPipelineExecution = exports.getSSMParameter = exports.pendingConnections = exports.validateCertificate = exports.deleteACMCertificate = exports.createACMCertificate = exports.checkCFARecordExists = exports.deleteCFCNAME = exports.createCFARecord = exports.waitCertificateToBeIssued = exports.checkCertificateExists = exports.checkBucketExists = void 0; const client_ssm_1 = require("@aws-sdk/client-ssm"); const client_sts_1 = require("@aws-sdk/client-sts"); const client_codepipeline_1 = require("@aws-sdk/client-codepipeline"); const client_codestar_connections_1 = require("@aws-sdk/client-codestar-connections"); const client_acm_1 = require("@aws-sdk/client-acm"); const client_route_53_1 = require("@aws-sdk/client-route-53"); const client_s3_1 = require("@aws-sdk/client-s3"); const clientSSM = new client_ssm_1.SSMClient(); const clientACM = new client_acm_1.ACMClient({ region: "us-east-1" }); const clientR53 = new client_route_53_1.Route53Client({ region: "us-east-1" }); const clientS3 = new client_s3_1.S3Client({}); const clientCodeStar = new client_codestar_connections_1.CodeStarConnectionsClient(); const clientCodePipeline = new client_codepipeline_1.CodePipelineClient(); const constants_1 = require("../shared/constants"); const prompt_questions_1 = require("./prompt_questions"); const helper_1 = require("./helper"); const util = require("util"); const node_config_provider_1 = require("@aws-sdk/node-config-provider"); const config_resolver_1 = require("@aws-sdk/config-resolver"); /** * Checks the connection to the AWS account using AWS STS (Security Token Service). * Returns true if the connection is successful, otherwise displays an error and exits. */ async function checkAWSConnection() { const stsClient = new client_sts_1.STSClient({}); const getCallerIdentityCommand = new client_sts_1.GetCallerIdentityCommand({}); try { await stsClient.send(getCallerIdentityCommand); const currentRegion = await (0, node_config_provider_1.loadConfig)(config_resolver_1.NODE_REGION_CONFIG_OPTIONS, config_resolver_1.NODE_REGION_CONFIG_FILE_OPTIONS)(); return true; } catch (error) { console.error(`${constants_1.ERROR_PREFIX} Impossible to connect to your the AWS account. Try to authenticate and try again.`); process.exit(1); } } exports.default = checkAWSConnection; /** * Checks if a specified bucket exists in Amazon S3. * * @param {string} bucketName - The name of the bucket to check. * @returns {Promise<boolean>} - Returns `true` if the bucket exists, `false` otherwise. */ async function checkBucketExists(bucketName) { try { const headBucketCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName }); // Check if the bucket exists await clientS3.send(headBucketCommand); // Get the current region of the bucket const getBucketLocationCommand = new client_s3_1.GetBucketLocationCommand({ Bucket: bucketName, }); await clientS3.send(getBucketLocationCommand); return true; } catch (error) { const typedError = error; if (typedError.name === "NotFound") { //console.log(`Bucket '${bucketName}' does not exist.`); return false; } else { //console.error("Error:", error); return false; } } } exports.checkBucketExists = checkBucketExists; /** * Checks if an ACM (AWS Certificate Manager) certificate exists for the specified domain name. * * @param {string} domainName - The primary domain name for which to check certificate existence. * @returns {Promise<string | null>} - The ARN of the existing certificate, or null if not found. */ const checkCertificateExists = async (domainName) => { var _a; try { const command = new client_acm_1.ListCertificatesCommand({}); const response = await clientACM.send(command); let certificateArn; let certificateStatus; const domainNames = (0, helper_1.getDomainNames)(domainName); if (response.CertificateSummaryList) { for (const certificate of response.CertificateSummaryList) { if (certificate.DomainName === domainNames[0] && ((_a = certificate.SubjectAlternativeNameSummaries) === null || _a === void 0 ? void 0 : _a.includes(domainNames[1]))) { certificateArn = certificate.CertificateArn; certificateStatus = certificate.Status; break; } } } return { certificateArn: certificateArn, status: certificateStatus }; } catch (error) { console.error("Error checking ACM Certificate", error); throw error; } }; exports.checkCertificateExists = checkCertificateExists; /** * Waits for an ACM (AWS Certificate Manager) certificate to be issued before proceeding. * * @param {string} certificateArn - The ARN (Amazon Resource Name) of the certificate to wait for. * @param {string | undefined} hostedZoneId - The optional hosted zone ID associated with the certificate. */ async function waitCertificateToBeIssued(certificateArn, hostedZoneId) { var _a, _b; try { await validateCertificate(certificateArn, hostedZoneId); let certificateStatus; const input = { CertificateArn: certificateArn, }; const command = new client_acm_1.DescribeCertificateCommand(input); let response = await clientACM.send(command); certificateStatus = (_a = response.Certificate) === null || _a === void 0 ? void 0 : _a.Status; while (certificateStatus != "ISSUED") { response = await clientACM.send(command); certificateStatus = (_b = response.Certificate) === null || _b === void 0 ? void 0 : _b.Status; console.log(`\nCertificate is not ready to be used. Waiting ...`); await new Promise((resolve, reject) => { setTimeout(resolve, 10000); }); } if (certificateStatus == "ISSUED") { console.log("\nThe certificate is ready to be used."); } else { console.log("\nThe certificate is still not ready. Wait a few minutes and then execute this command again."); } } catch (error) { console.error("Error waiting certificate to be issued", error); throw error; } } exports.waitCertificateToBeIssued = waitCertificateToBeIssued; /** * Creates or updates an Alias resource record set in Amazon Route 53 to associate a custom domain with a CloudFront distribution. * * @param {string} domainName - The custom domain name (CNAME) to be associated with the CloudFront distribution. * @param {string} cloudFrontDomainName - The DNS name of the CloudFront distribution. * @param {string} hostedZoneId - The ID of the Route 53 hosted zone where the Alias resource record will be created/updated. */ async function createCFARecord(domainName, cloudFrontDomainName, hostedZoneId) { const input = { ChangeBatch: { Changes: [ { Action: "CREATE", ResourceRecordSet: { Name: domainName, Type: "A", AliasTarget: { HostedZoneId: constants_1.CLOUDFRONT_HOSTEDZONE_ID, DNSName: cloudFrontDomainName, EvaluateTargetHealth: false, }, }, }, ], }, HostedZoneId: hostedZoneId, }; try { const command = new client_route_53_1.ChangeResourceRecordSetsCommand(input); await clientR53.send(command); console.log(`\nA new A Record has been added/updated to your DNS records that points to your CloudFront distribution: \n`); console.log(`> ${domainName} -> ${cloudFrontDomainName}\n`); console.log(`It may take a few minutes to reflect the change.`); } catch (error) { console.error("Error creating CloudFront CNAME", error); throw error; } } exports.createCFARecord = createCFARecord; async function deleteCFCNAME(domainName, cloudFrontDomainName, hostedZoneId) { const input = { ChangeBatch: { Changes: [ { Action: "DELETE", ResourceRecordSet: { Name: domainName, Type: "A", AliasTarget: { HostedZoneId: constants_1.CLOUDFRONT_HOSTEDZONE_ID, DNSName: cloudFrontDomainName, EvaluateTargetHealth: false, }, }, }, ], }, HostedZoneId: hostedZoneId, }; try { const command = new client_route_53_1.ChangeResourceRecordSetsCommand(input); await clientR53.send(command); console.log(`\nThe A Record has been deleted from your DNS records: \n`); console.log(`> ${domainName} -> ${cloudFrontDomainName}\n`); console.log(`It may take a few minutes to reflect the change.`); } catch (error) { console.error("Error deleting CloudFront CNAME", error); throw error; } } exports.deleteCFCNAME = deleteCFCNAME; async function checkCFARecordExists(domainName, cloudFrontDomainName, hostedZoneId) { var _a; const input = { HostedZoneId: hostedZoneId, StartRecordName: domainName, StartRecordType: "A", }; let matchingRecords = []; try { const command = new client_route_53_1.ListResourceRecordSetsCommand(input); const response = await clientR53.send(command); if (response.ResourceRecordSets) { matchingRecords = response.ResourceRecordSets.filter((recordSet) => (recordSet.Name === domainName || recordSet.Name === domainName + ".") && recordSet.Type === "A"); if (matchingRecords.length > 0) { const existingDNSName = (_a = matchingRecords[0].AliasTarget) === null || _a === void 0 ? void 0 : _a.DNSName; if (existingDNSName && existingDNSName !== cloudFrontDomainName && existingDNSName !== cloudFrontDomainName + ".") { console.error(`${constants_1.ERROR_PREFIX} An A record already exists for "${domainName}" with a different CloudFront DNS name: "${existingDNSName}". Delete it manually and try again.`); } } } return matchingRecords.length > 0; } catch (error) { console.error("Error checking or creating Alias resource record set:", error); throw error; } } exports.checkCFARecordExists = checkCFARecordExists; /** * Creates an ACM (AWS Certificate Manager) certificate request for the specified domain name. * * @param {string} domainName - The primary domain name for which the certificate is requested. * @returns {Promise<string>} - The ARN (Amazon Resource Name) of the created ACM certificate. */ const createACMCertificate = async (domainName) => { try { const domainNames = (0, helper_1.getDomainNames)(domainName); const input = { DomainName: domainNames[0], ValidationMethod: "DNS", SubjectAlternativeNames: [domainNames[1]], }; const command = new client_acm_1.RequestCertificateCommand(input); const response = await clientACM.send(command); let certificateArn; if (response) { certificateArn = response.CertificateArn; } else { console.error(`${constants_1.ERROR_PREFIX} An error occured when creating the ACM Certificate`); process.exit(1); } return certificateArn; } catch (error) { console.error("Error creating ACM certificate", error); throw error; } }; exports.createACMCertificate = createACMCertificate; /** * Deletes an AWS ACM (Amazon Certificate Manager) certificate using its ARN (Amazon Resource Name). * * @param {string} certificateArn - The ARN of the ACM certificate to be deleted. * @returns {Promise<void>} A Promise that resolves when the certificate is successfully deleted, or rejects on error. * * @throws {Error} If an error occurs during the deletion process. */ const deleteACMCertificate = async (certificateArn) => { const input = { CertificateArn: certificateArn, }; try { const command = new client_acm_1.DeleteCertificateCommand(input); await clientACM.send(command); console.log(`ACM Certificate with ARN ${certificateArn} has been deleted.`); } catch (error) { console.error(util.format("%d An error occurred when deleting the ACM Certificate", constants_1.ERROR_PREFIX), error); process.exit(1); } }; exports.deleteACMCertificate = deleteACMCertificate; /** * Validates an ACM (AWS Certificate Manager) certificate by ensuring the presence of a CNAME record. * If the hostedZoneId is provided, it adds the CNAME record to the hosted zone for domain validation. * * @param {string} certificateArn - The ARN of the certificate to be validated. * @param {string | undefined} hostedZoneId - The ID of the hosted zone where the CNAME record should be added. */ async function validateCertificate(certificateArn, hostedZoneId) { var _a, _b; let isCNAMERecordPresent = false; let count = 0; const cnames = []; try { while (!isCNAMERecordPresent && count <= 10) { const input = { CertificateArn: certificateArn, }; const command = new client_acm_1.DescribeCertificateCommand(input); const response = await clientACM.send(command); if ((_a = response.Certificate) === null || _a === void 0 ? void 0 : _a.DomainValidationOptions) { for (const validationRecord of (_b = response.Certificate) === null || _b === void 0 ? void 0 : _b.DomainValidationOptions) { if (validationRecord.ResourceRecord && validationRecord.ResourceRecord.Type === "CNAME" && validationRecord.ResourceRecord.Name && validationRecord.ResourceRecord.Value) { isCNAMERecordPresent = true; if (hostedZoneId) { const cnameExists = await checkCnameExists(hostedZoneId, validationRecord.ResourceRecord.Name, validationRecord.ResourceRecord.Value); if (!cnameExists) { await createCnameRecord(hostedZoneId, validationRecord.ResourceRecord.Name, validationRecord.ResourceRecord.Value); } } else { cnames.push({ key: validationRecord.ResourceRecord.Name, value: validationRecord.ResourceRecord.Value, }); } } } } if (!isCNAMERecordPresent) { await new Promise((resolve, reject) => { setTimeout(resolve, 10000); }); count++; } } if (!isCNAMERecordPresent) { console.error(`${constants_1.ERROR_PREFIX} Certificate is not yet created`); process.exit(1); } if (cnames.length > 0) { console.log("Please ensure you add the CNAME record to your DNS configuration. If you have already added it, please allow some time for the changes to propagate as the ACM service may take up to 30 minutes to validate the domain.\n"); for (const cname of cnames) { console.log("> CNAME name: " + cname.key); console.log("> CNAME value: " + cname.value); console.log("\n"); } await (0, helper_1.startPrompt)(prompt_questions_1.continueConfirmationQuestion); } } catch (error) { console.error("Error validating ACM certificate", error); throw error; } } exports.validateCertificate = validateCertificate; async function checkCnameExists(hostedZoneId, cnameName, cnameValue) { const input = { HostedZoneId: hostedZoneId, MaxItems: 1, // Change to number type StartRecordName: cnameName, StartRecordType: "CNAME", }; try { const command = new client_route_53_1.ListResourceRecordSetsCommand(input); const response = await clientR53.send(command); if (response.ResourceRecordSets && response.ResourceRecordSets.length > 0) { const firstRecord = response.ResourceRecordSets[0]; if (firstRecord.Name === cnameName && firstRecord.Type === "CNAME" && firstRecord.ResourceRecords && firstRecord.ResourceRecords[0].Value === cnameValue) { console.log("A CNAME record with the same name and value is already found in your Route 53 settings."); return true; } } return false; // CNAME record doesn't exist or doesn't match } catch (error) { console.error("Error checking CNAME record:", error); throw error; } } async function createCnameRecord(hostedZoneId, cnameName, cnameValue) { const input = { ChangeBatch: { Changes: [ { Action: "CREATE", ResourceRecordSet: { Name: cnameName, ResourceRecords: [ { Value: cnameValue, }, ], TTL: 60, Type: "CNAME", }, }, ], }, HostedZoneId: hostedZoneId, }; try { const command = new client_route_53_1.ChangeResourceRecordSetsCommand(input); await clientR53.send(command); console.log("A CNAME record has been added to your hosted zone. It may take a few minutes for the ACM service to validate your domain. Please wait for the validation process to complete."); } catch (error) { console.error("Error creating CNAME record:", error); throw error; } } /** * Checks if there are any pending connections by querying the status of a CodeStar connection. * * @returns {Promise<boolean>} - True if there are pending connections, false otherwise. */ async function pendingConnections() { const connectionArn = await getSSMParameter(constants_1.SSM_CONNECTION_ARN_STR); const connectionRegion = await getSSMParameter(constants_1.SSM_CONNECTION_REGION_STR); let connectionStatus; if (!connectionArn || !connectionRegion) return false; try { const input = { ConnectionArn: connectionArn, }; const command = new client_codestar_connections_1.GetConnectionCommand(input); const response = await clientCodeStar.send(command); connectionStatus = response.Connection ? response.Connection.ConnectionStatus : undefined; return connectionStatus === "PENDING"; } catch (error) { console.error(`Failed to get connection status for ${connectionArn}:`, error); connectionStatus = undefined; } return connectionStatus === "PENDING"; } exports.pendingConnections = pendingConnections; /** * Retrieves the value of an AWS Systems Manager (SSM) parameter by name. * * @param {string} parameterName - The name of the SSM parameter to retrieve. * @returns {Promise<string>} A Promise that resolves to the value of the SSM parameter. * @throws {Error} If there is an error while retrieving the parameter. */ async function getSSMParameter(parameterName) { var _a; try { const hostingConfiguration = await (0, helper_1.loadHostingConfiguration)(); let stackName; if ((parameterName === constants_1.SSM_CONNECTION_ARN_STR || parameterName === constants_1.SSM_CONNECTION_NAME_STR || parameterName === constants_1.SSM_CONNECTION_REGION_STR) && (0, helper_1.isRepoConfig)(hostingConfiguration)) { stackName = (0, helper_1.calculateConnectionStackName)(hostingConfiguration.repoUrl, hostingConfiguration.branchName); } else { stackName = (0, helper_1.calculateMainStackName)(hostingConfiguration); } const ssmParam = "/" + stackName + "/" + parameterName; const command = new client_ssm_1.GetParameterCommand({ Name: ssmParam, }); // Execute the command and retrieve the parameter value const response = await clientSSM.send(command); const paramValue = (_a = response.Parameter) === null || _a === void 0 ? void 0 : _a.Value; return paramValue; } catch (err) { console.error(`Error retrieving parameter ${parameterName}`, err); throw err; } } exports.getSSMParameter = getSSMParameter; async function startPipelineExecution() { try { const pipelineName = await getSSMParameter(constants_1.SSM_PIPELINENAME_STR); const params = { name: pipelineName, }; const pipelineStatus = await getPipelineStatus(); if (pipelineStatus.status !== "InProgress") { const command = new client_codepipeline_1.StartPipelineExecutionCommand(params); const response = await clientCodePipeline.send(command); console.log(`The pipeline has been initiated following the recent deployment to apply any changes made.`); } else { console.log("Pipeline is already in progress."); } } catch (error) { console.error("Error starting pipeline execution:", error); throw error; } } exports.startPipelineExecution = startPipelineExecution; async function getPipelineStatus() { var _a; try { const pipelineName = await getSSMParameter(constants_1.SSM_PIPELINENAME_STR); //Cancelled | InProgress | Failed | Stopped | Stopping | Succeeded if (pipelineName) { const input = { name: pipelineName, }; const command = new client_codepipeline_1.GetPipelineStateCommand(input); const response = await clientCodePipeline.send(command); // Extract the stage states from the response const stageStates = response.stageStates; if (stageStates && stageStates.length > 0) { let hasInProgress = false; let hasFailed = false; let lastStageStatus = null; // Initialize with null let lastStageName = null; // Initialize with null for (const stage of stageStates) { if (stage.latestExecution && stage.latestExecution.status) { lastStageStatus = stage.latestExecution.status; lastStageName = (_a = stage.stageName) !== null && _a !== void 0 ? _a : "Unknown"; // Use "Unknown" if stageName is undefined if (lastStageStatus === "InProgress") { hasInProgress = true; } else if (lastStageStatus === "Failed") { hasFailed = true; } } } if (hasInProgress) { return { status: "InProgress", stageName: lastStageName || "Unknown", }; } else if (hasFailed) { return { status: "Failed", stageName: lastStageName || "Unknown" }; } else { // No stage in progress and no stage has failed, return the status of the last stage along with its name return { status: lastStageStatus || "Unknown", stageName: lastStageName || "Unknown", }; } } } // If hosting.pipeline is not defined or there are no stages, return a default status and stage name (e.g., "Unknown" for both). return { status: "Unknown", stageName: "Unknown" }; } catch (error) { console.error("Error getting Pipeline Status", error); throw error; } } exports.getPipelineStatus = getPipelineStatus; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXdzU0RLVXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImF3c1NES1V0aWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQTs7Ozs7Ozs7Ozs7Ozs7RUFjRTs7O0FBRUYsb0RBQXFFO0FBQ3JFLG9EQUEwRTtBQUMxRSxzRUFJc0M7QUFDdEMsc0ZBRzhDO0FBQzlDLG9EQU82QjtBQUM3Qiw4REFPa0M7QUFFbEMsa0RBSTRCO0FBRTVCLE1BQU0sU0FBUyxHQUFHLElBQUksc0JBQVMsRUFBRSxDQUFDO0FBQ2xDLE1BQU0sU0FBUyxHQUFHLElBQUksc0JBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDO0FBQ3pELE1BQU0sU0FBUyxHQUFHLElBQUksK0JBQWEsQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDO0FBQzdELE1BQU0sUUFBUSxHQUFHLElBQUksb0JBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztBQUVsQyxNQUFNLGNBQWMsR0FBRyxJQUFJLHVEQUF5QixFQUFFLENBQUM7QUFDdkQsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLHdDQUFrQixFQUFFLENBQUM7QUFFcEQsbURBTzZCO0FBRTdCLHlEQUFrRTtBQUNsRSxxQ0FPa0I7QUFFbEIsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRTdCLHdFQUF5RDtBQUN6RCw4REFBcUc7QUFHckc7OztHQUdHO0FBQ1ksS0FBSyxVQUFVLGtCQUFrQjtJQUM5QyxNQUFNLFNBQVMsR0FBRyxJQUFJLHNCQUFTLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDcEMsTUFBTSx3QkFBd0IsR0FBRyxJQUFJLHFDQUF3QixDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2xFLElBQUksQ0FBQztRQUNILE1BQU0sU0FBUyxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sYUFBYSxHQUFHLE1BQU0sSUFBQSxpQ0FBVSxF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QUFDO1FBRUQsZ0lBQWdJO1FBQ2hJLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsQ0FBQztJQUNyRCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsK0JBQStCLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdEQsTUFBTSxLQUFLLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQXZERCw4Q0F1REMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiBub2RlXG4vKlxuICBDb3B5cmlnaHQgQW1hem9uLmNvbSwgSW5jLiBvciBpdHMgYWZmaWxpYXRlcy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAgXG4gIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSBcIkxpY2Vuc2VcIikuXG4gIFlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS5cbiAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gIFxuICAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gIFxuICBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbiAgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmRcbiAgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4qL1xuXG5pbXBvcnQgeyBTU01DbGllbnQsIEdldFBhcmFtZXRlckNvbW1hbmQgfSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXNzbVwiO1xuaW1wb3J0IHsgU1RTQ2xpZW50LCBHZXRDYWxsZXJJZGVudGl0eUNvbW1hbmQgfSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXN0c1wiO1xuaW1wb3J0IHtcbiAgR2V0UGlwZWxpbmVTdGF0ZUNvbW1hbmQsXG4gIENvZGVQaXBlbGluZUNsaWVudCxcbiAgU3RhcnRQaXBlbGluZUV4ZWN1dGlvbkNvbW1hbmQsXG59IGZyb20gXCJAYXdzLXNkay9jbGllbnQtY29kZXBpcGVsaW5lXCI7XG5pbXBvcnQge1xuICBDb2RlU3RhckNvbm5lY3Rpb25zQ2xpZW50LFxuICBHZXRDb25uZWN0aW9uQ29tbWFuZCxcbn0gZnJvbSBcIkBhd3Mtc2RrL2NsaWVudC1jb2Rlc3Rhci1jb25uZWN0aW9uc1wiO1xuaW1wb3J0IHtcbiAgQUNNQ2xpZW50LFxuICBMaXN0Q2VydGlmaWNhdGVzQ29tbWFuZCxcbiAgRGVzY3JpYmVDZXJ0aWZpY2F0ZUNvbW1hbmQsXG4gIFJlcXVlc3RDZXJ0aWZpY2F0ZUNvbW1hbmQsXG4gIERlbGV0ZUNlcnRpZmljYXRlQ29tbWFuZCxcbiAgUmVxdWVzdENlcnRpZmljYXRlQ29tbWFuZElucHV0XG59IGZyb20gXCJAYXdzLXNkay9jbGllbnQtYWNtXCI7XG5pbXBvcnQge1xuICBSb3V0ZTUzQ2xpZW50LFxuICBMaXN0UmVzb3VyY2VSZWNvcmRTZXRzQ29tbWFuZCxcbiAgQ2hhbmdlUmVzb3VyY2VSZWNvcmRTZXRzQ29tbWFuZCxcbiAgQ2hhbmdlUmVzb3VyY2VSZWNvcmRTZXRzQ29tbWFuZElucHV0LFxuICBMaXN0UmVzb3VyY2VSZWNvcmRTZXRzQ29tbWFuZElucHV0LFxuICBcbn0gZnJvbSBcIkBhd3Mtc2RrL2NsaWVudC1yb3V0ZS01M1wiO1xuXG5pbXBvcnQge1xuICBTM0NsaWVudCxcbiAgSGVhZEJ1Y2tldENvbW1hbmQsXG4gIEdldEJ1Y2tldExvY2F0aW9uQ29tbWFuZCxcbn0gZnJvbSBcIkBhd3Mtc2RrL2NsaWVudC1zM1wiO1xuXG5jb25zdCBjbGllbnRTU00gPSBuZXcgU1NNQ2xpZW50KCk7XG5jb25zdCBjbGllbnRBQ00gPSBuZXcgQUNNQ2xpZW50KHsgcmVnaW9uOiBcInVzLWVhc3QtMVwiIH0pO1xuY29uc3QgY2xpZW50UjUzID0gbmV3IFJvdXRlNTNDbGllbnQoeyByZWdpb246IFwidXMtZWFzdC0xXCIgfSk7XG5jb25zdCBjbGllbnRTMyA9IG5ldyBTM0NsaWVudCh7fSk7XG5cbmNvbnN0IGNsaWVudENvZGVTdGFyID0gbmV3IENvZGVTdGFyQ29ubmVjdGlvbnNDbGllbnQoKTtcbmNvbnN0IGNsaWVudENvZGVQaXBlbGluZSA9IG5ldyBDb2RlUGlwZWxpbmVDbGllbnQoKTtcblxuaW1wb3J0IHtcbiAgQ0xPVURGUk9OVF9IT1NURURaT05FX0lELFxuICBFUlJPUl9QUkVGSVgsXG4gIFNTTV9DT05ORUNUSU9OX0FSTl9TVFIsXG4gIFNTTV9DT05ORUNUSU9OX05BTUVfU1RSLFxuICBTU01fQ09OTkVDVElPTl9SRUdJT05fU1RSLFxuICBTU01fUElQRUxJTkVOQU1FX1NUUixcbn0gZnJvbSBcIi4uL3NoYXJlZC9jb25zdGFudHNcIjtcbmltcG9ydCB7IENOQU1FUyB9IGZyb20gXCIuLi9zaGFyZWQvdHlwZXNcIjtcbmltcG9ydCB7IGNvbnRpbnVlQ29uZmlybWF0aW9uUXVlc3Rpb24gfSBmcm9tIFwiLi9wcm9tcHRfcXVlc3Rpb25zXCI7XG5pbXBvcnQge1xuICBjYWxjdWxhdGVDb25uZWN0aW9uU3RhY2tOYW1lLFxuICBjYWxjdWxhdGVNYWluU3RhY2tOYW1lLFxuICBnZXREb21haW5OYW1lcyxcbiAgaXNSZXBvQ29uZmlnLFxuICBsb2FkSG9zdGluZ0NvbmZpZ3VyYXRpb24sXG4gIHN0YXJ0UHJvbXB0LFxufSBmcm9tIFwiLi9oZWxwZXJcIjtcblxuY29uc3QgdXRpbCA9IHJlcXVpcmUoXCJ1dGlsXCIpO1xuXG5pbXBvcnQge2xvYWRDb25maWd9IGZyb20gXCJAYXdzLXNkay9ub2RlLWNvbmZpZy1wcm92aWRlclwiO1xuaW1wb3J0IHtOT0RFX1JFR0lPTl9DT05GSUdfRklMRV9PUFRJT05TLCBOT0RFX1JFR0lPTl9DT05GSUdfT1BUSU9OU30gZnJvbSBcIkBhd3Mtc2RrL2NvbmZpZy1yZXNvbHZlclwiO1xuXG5cbi8qKlxuICogQ2hlY2tzIHRoZSBjb25uZWN0aW9uIHRvIHRoZSBBV1MgYWNjb3VudCB1c2luZyBBV1MgU1RTIChTZWN1cml0eSBUb2tlbiBTZXJ2aWNlKS5cbiAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgY29ubmVjdGlvbiBpcyBzdWNjZXNzZnVsLCBvdGhlcndpc2UgZGlzcGxheXMgYW4gZXJyb3IgYW5kIGV4aXRzLlxuICovXG5leHBvcnQgZGVmYXVsdCBhc3luYyBmdW5jdGlvbiBjaGVja0FXU0Nvbm5lY3Rpb24oKSB7XG4gIGNvbnN0IHN0c0NsaWVudCA9IG5ldyBTVFNDbGllbnQoe30pO1xuICBjb25zdCBnZXRDYWxsZXJJZGVudGl0eUNvbW1hbmQgPSBuZXcgR2V0Q2FsbGVySWRlbnRpdHlDb21tYW5kKHt9KTtcbiAgdHJ5IHtcbiAgICBhd2FpdCBzdHNDbGllbnQuc2VuZChnZXRDYWxsZXJJZGVudGl0eUNvbW1hbmQpO1xuICAgIGNvbnN0IGN1cnJlbnRSZWdpb24gPSBhd2FpdCBsb2FkQ29uZmlnKE5PREVfUkVHSU9OX0NPTkZJR19PUFRJT05TLCBOT0RFX1JFR0lPTl9DT05GSUdfRklMRV9PUFRJT05TKSgpO1xuXG4gICAgcmV0dXJuIHRydWU7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgXG4gICAgY29uc29sZS5lcnJvcihcbiAgICAgIGAke0VSUk9SX1BSRUZJWH0gSW1wb3NzaWJsZSB0byBjb25uZWN0IHRvIHlvdXIgdGhlIEFXUyBhY2NvdW50LiBUcnkgdG8gYXV0aGVudGljYXRlIGFuZCB0cnkgYWdhaW4uYFxuICAgICk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGEgc3BlY2lmaWVkIGJ1Y2tldCBleGlzdHMgaW4gQW1hem9uIFMzLlxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBidWNrZXROYW1lIC0gVGhlIG5hbWUgb2YgdGhlIGJ1Y2tldCB0byBjaGVjay5cbiAqIEByZXR1cm5zIH