aws-cdk
Version:
CDK Toolkit, the command line tool for CDK apps
738 lines • 136 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdkToolkit = exports.AssetBuildTime = void 0;
const path = require("path");
const util_1 = require("util");
const cxapi = require("@aws-cdk/cx-api");
const chalk = require("chalk");
const chokidar = require("chokidar");
const fs = require("fs-extra");
const promptly = require("promptly");
const cloud_assembly_1 = require("./api/cxapp/cloud-assembly");
const common_1 = require("./api/hotswap/common");
const find_cloudwatch_logs_1 = require("./api/logs/find-cloudwatch-logs");
const logs_monitor_1 = require("./api/logs/logs-monitor");
const stack_activity_monitor_1 = require("./api/util/cloudformation/stack-activity-monitor");
const migrate_1 = require("./commands/migrate");
const diff_1 = require("./diff");
const import_1 = require("./import");
const logging_1 = require("./logging");
const serialize_1 = require("./serialize");
const settings_1 = require("./settings");
const util_2 = require("./util");
const validate_notification_arn_1 = require("./util/validate-notification-arn");
const work_graph_builder_1 = require("./util/work-graph-builder");
const environments_1 = require("../lib/api/cxapp/environments");
/**
* When to build assets
*/
var AssetBuildTime;
(function (AssetBuildTime) {
/**
* Build all assets before deploying the first stack
*
* This is intended for expensive Docker image builds; so that if the Docker image build
* fails, no stacks are unnecessarily deployed (with the attendant wait time).
*/
AssetBuildTime[AssetBuildTime["ALL_BEFORE_DEPLOY"] = 0] = "ALL_BEFORE_DEPLOY";
/**
* Build assets just-in-time, before publishing
*/
AssetBuildTime[AssetBuildTime["JUST_IN_TIME"] = 1] = "JUST_IN_TIME";
})(AssetBuildTime || (exports.AssetBuildTime = AssetBuildTime = {}));
/**
* Toolkit logic
*
* The toolkit runs the `cloudExecutable` to obtain a cloud assembly and
* deploys applies them to `cloudFormation`.
*/
class CdkToolkit {
constructor(props) {
this.props = props;
}
async metadata(stackName, json) {
const stacks = await this.selectSingleStackByName(stackName);
(0, logging_1.data)((0, serialize_1.serializeStructure)(stacks.firstStack.manifest.metadata ?? {}, json));
}
async acknowledge(noticeId) {
const acks = this.props.configuration.context.get('acknowledged-issue-numbers') ?? [];
acks.push(Number(noticeId));
this.props.configuration.context.set('acknowledged-issue-numbers', acks);
await this.props.configuration.saveContext();
}
async diff(options) {
const stacks = await this.selectStacksForDiff(options.stackNames, options.exclusively);
const strict = !!options.strict;
const contextLines = options.contextLines || 3;
const stream = options.stream || process.stderr;
const quiet = options.quiet || false;
let diffs = 0;
if (options.templatePath !== undefined) {
// Compare single stack against fixed template
if (stacks.stackCount !== 1) {
throw new Error('Can only select one stack when comparing to fixed template. Use --exclusively to avoid selecting multiple stacks.');
}
if (!await fs.pathExists(options.templatePath)) {
throw new Error(`There is no file at ${options.templatePath}`);
}
const template = (0, serialize_1.deserializeStructure)(await fs.readFile(options.templatePath, { encoding: 'UTF-8' }));
diffs = options.securityOnly
? (0, util_2.numberFromBool)((0, diff_1.printSecurityDiff)(template, stacks.firstStack, diff_1.RequireApproval.Broadening))
: (0, diff_1.printStackDiff)(template, stacks.firstStack, strict, contextLines, quiet, stream);
}
else {
// Compare N stacks against deployed templates
for (const stack of stacks.stackArtifacts) {
if (!quiet) {
stream.write((0, util_1.format)('Stack %s\n', chalk.bold(stack.displayName)));
}
const templateWithNames = await this.props.deployments.readCurrentTemplateWithNestedStacks(stack, options.compareAgainstProcessedTemplate);
const currentTemplate = templateWithNames.deployedTemplate;
const nestedStackCount = templateWithNames.nestedStackCount;
const stackCount = options.securityOnly
? ((0, util_2.numberFromBool)((0, diff_1.printSecurityDiff)(currentTemplate, stack, diff_1.RequireApproval.Broadening)) > 0 ? 1 : 0)
: ((0, diff_1.printStackDiff)(currentTemplate, stack, strict, contextLines, quiet, stream) > 0 ? 1 : 0);
diffs += stackCount + nestedStackCount;
}
}
stream.write((0, util_1.format)('\n✨ Number of stacks with differences: %s\n', diffs));
return diffs && options.fail ? 1 : 0;
}
async deploy(options) {
if (options.watch) {
return this.watch(options);
}
if (options.notificationArns) {
options.notificationArns.map(arn => {
if (!(0, validate_notification_arn_1.validateSnsTopicArn)(arn)) {
throw new Error(`Notification arn ${arn} is not a valid arn for an SNS topic`);
}
});
}
const startSynthTime = new Date().getTime();
const stackCollection = await this.selectStacksForDeploy(options.selector, options.exclusively, options.cacheCloudAssembly);
const elapsedSynthTime = new Date().getTime() - startSynthTime;
(0, logging_1.print)('\n✨ Synthesis time: %ss\n', formatTime(elapsedSynthTime));
const requireApproval = options.requireApproval ?? diff_1.RequireApproval.Broadening;
const parameterMap = { '*': {} };
for (const key in options.parameters) {
if (options.parameters.hasOwnProperty(key)) {
const [stack, parameter] = key.split(':', 2);
if (!parameter) {
parameterMap['*'][stack] = options.parameters[key];
}
else {
if (!parameterMap[stack]) {
parameterMap[stack] = {};
}
parameterMap[stack][parameter] = options.parameters[key];
}
}
}
if (options.hotswap !== common_1.HotswapMode.FULL_DEPLOYMENT) {
(0, logging_1.warning)('⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments');
(0, logging_1.warning)('⚠️ They should only be used for development - never use them for your production Stacks!\n');
}
const stacks = stackCollection.stackArtifacts;
const stackOutputs = {};
const outputsFile = options.outputsFile;
const buildAsset = async (assetNode) => {
await this.props.deployments.buildSingleAsset(assetNode.assetManifestArtifact, assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn: options.roleArn,
toolkitStackName: options.toolkitStackName,
stackName: assetNode.parentStack.stackName,
});
};
const publishAsset = async (assetNode) => {
await this.props.deployments.publishSingleAsset(assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn: options.roleArn,
toolkitStackName: options.toolkitStackName,
stackName: assetNode.parentStack.stackName,
});
};
const deployStack = async (assetNode) => {
const stack = assetNode.stack;
if (stackCollection.stackCount !== 1) {
(0, logging_1.highlight)(stack.displayName);
}
if (!stack.environment) {
// eslint-disable-next-line max-len
throw new Error(`Stack ${stack.displayName} does not define an environment, and AWS credentials could not be obtained from standard locations or no region was configured.`);
}
if (Object.keys(stack.template.Resources || {}).length === 0) { // The generated stack has no resources
if (!await this.props.deployments.stackExists({ stack })) {
(0, logging_1.warning)('%s: stack has no resources, skipping deployment.', chalk.bold(stack.displayName));
}
else {
(0, logging_1.warning)('%s: stack has no resources, deleting existing stack.', chalk.bold(stack.displayName));
await this.destroy({
selector: { patterns: [stack.hierarchicalId] },
exclusively: true,
force: true,
roleArn: options.roleArn,
fromDeploy: true,
ci: options.ci,
});
}
return;
}
if (requireApproval !== diff_1.RequireApproval.Never) {
const currentTemplate = await this.props.deployments.readCurrentTemplate(stack);
if ((0, diff_1.printSecurityDiff)(currentTemplate, stack, requireApproval)) {
await (0, logging_1.withCorkedLogging)(async () => {
// only talk to user if STDIN is a terminal (otherwise, fail)
if (!process.stdin.isTTY) {
throw new Error('"--require-approval" is enabled and stack includes security-sensitive updates, ' +
'but terminal (TTY) is not attached so we are unable to get a confirmation from the user');
}
// only talk to user if concurrency is 1 (otherwise, fail)
if (concurrency > 1) {
throw new Error('"--require-approval" is enabled and stack includes security-sensitive updates, ' +
'but concurrency is greater than 1 so we are unable to get a confirmation from the user');
}
const confirmed = await promptly.confirm('Do you wish to deploy these changes (y/n)?');
if (!confirmed) {
throw new Error('Aborted by user');
}
});
}
}
const stackIndex = stacks.indexOf(stack) + 1;
(0, logging_1.print)('%s: deploying... [%s/%s]', chalk.bold(stack.displayName), stackIndex, stackCollection.stackCount);
const startDeployTime = new Date().getTime();
let tags = options.tags;
if (!tags || tags.length === 0) {
tags = tagsForStack(stack);
}
let elapsedDeployTime = 0;
try {
const result = await this.props.deployments.deployStack({
stack,
deployName: stack.stackName,
roleArn: options.roleArn,
toolkitStackName: options.toolkitStackName,
reuseAssets: options.reuseAssets,
notificationArns: options.notificationArns,
tags,
execute: options.execute,
changeSetName: options.changeSetName,
deploymentMethod: options.deploymentMethod,
force: options.force,
parameters: Object.assign({}, parameterMap['*'], parameterMap[stack.stackName]),
usePreviousParameters: options.usePreviousParameters,
progress,
ci: options.ci,
rollback: options.rollback,
hotswap: options.hotswap,
extraUserAgent: options.extraUserAgent,
assetParallelism: options.assetParallelism,
});
const message = result.noOp
? ' ✅ %s (no changes)'
: ' ✅ %s';
(0, logging_1.success)('\n' + message, stack.displayName);
elapsedDeployTime = new Date().getTime() - startDeployTime;
(0, logging_1.print)('\n✨ Deployment time: %ss\n', formatTime(elapsedDeployTime));
if (Object.keys(result.outputs).length > 0) {
(0, logging_1.print)('Outputs:');
stackOutputs[stack.stackName] = result.outputs;
}
for (const name of Object.keys(result.outputs).sort()) {
const value = result.outputs[name];
(0, logging_1.print)('%s.%s = %s', chalk.cyan(stack.id), chalk.cyan(name), chalk.underline(chalk.cyan(value)));
}
(0, logging_1.print)('Stack ARN:');
(0, logging_1.data)(result.stackArn);
}
catch (e) {
(0, logging_1.error)('\n ❌ %s failed: %s', chalk.bold(stack.displayName), e);
throw e;
}
finally {
if (options.cloudWatchLogMonitor) {
const foundLogGroupsResult = await (0, find_cloudwatch_logs_1.findCloudWatchLogGroups)(this.props.sdkProvider, stack);
options.cloudWatchLogMonitor.addLogGroups(foundLogGroupsResult.env, foundLogGroupsResult.sdk, foundLogGroupsResult.logGroupNames);
}
// If an outputs file has been specified, create the file path and write stack outputs to it once.
// Outputs are written after all stacks have been deployed. If a stack deployment fails,
// all of the outputs from successfully deployed stacks before the failure will still be written.
if (outputsFile) {
fs.ensureFileSync(outputsFile);
await fs.writeJson(outputsFile, stackOutputs, {
spaces: 2,
encoding: 'utf8',
});
}
}
(0, logging_1.print)('\n✨ Total time: %ss\n', formatTime(elapsedSynthTime + elapsedDeployTime));
};
const assetBuildTime = options.assetBuildTime ?? AssetBuildTime.ALL_BEFORE_DEPLOY;
const prebuildAssets = assetBuildTime === AssetBuildTime.ALL_BEFORE_DEPLOY;
const concurrency = options.concurrency || 1;
const progress = concurrency > 1 ? stack_activity_monitor_1.StackActivityProgress.EVENTS : options.progress;
if (concurrency > 1 && options.progress && options.progress != stack_activity_monitor_1.StackActivityProgress.EVENTS) {
(0, logging_1.warning)('⚠️ The --concurrency flag only supports --progress "events". Switching to "events".');
}
try {
const stacksAndTheirAssetManifests = stacks.flatMap(stack => [
stack,
...stack.dependencies.filter(cxapi.AssetManifestArtifact.isAssetManifestArtifact),
]);
const workGraph = new work_graph_builder_1.WorkGraphBuilder(prebuildAssets).build(stacksAndTheirAssetManifests);
// Unless we are running with '--force', skip already published assets
if (!options.force) {
await this.removePublishedAssets(workGraph, options);
}
const graphConcurrency = {
'stack': concurrency,
'asset-build': 1,
'asset-publish': (options.assetParallelism ?? true) ? 8 : 1, // This will be I/O-bound, 8 in parallel seems reasonable
};
await workGraph.doParallel(graphConcurrency, {
deployStack,
buildAsset,
publishAsset,
});
}
catch (e) {
(0, logging_1.error)('\n ❌ Deployment failed: %s', e);
throw e;
}
}
async watch(options) {
const rootDir = path.dirname(path.resolve(settings_1.PROJECT_CONFIG));
(0, logging_1.debug)("root directory used for 'watch' is: %s", rootDir);
const watchSettings = this.props.configuration.settings.get(['watch']);
if (!watchSettings) {
throw new Error("Cannot use the 'watch' command without specifying at least one directory to monitor. " +
'Make sure to add a "watch" key to your cdk.json');
}
// For the "include" subkey under the "watch" key, the behavior is:
// 1. No "watch" setting? We error out.
// 2. "watch" setting without an "include" key? We default to observing "./**".
// 3. "watch" setting with an empty "include" key? We default to observing "./**".
// 4. Non-empty "include" key? Just use the "include" key.
const watchIncludes = this.patternsArrayForWatch(watchSettings.include, { rootDir, returnRootDirIfEmpty: true });
(0, logging_1.debug)("'include' patterns for 'watch': %s", watchIncludes);
// For the "exclude" subkey under the "watch" key,
// the behavior is to add some default excludes in addition to the ones specified by the user:
// 1. The CDK output directory.
// 2. Any file whose name starts with a dot.
// 3. Any directory's content whose name starts with a dot.
// 4. Any node_modules and its content (even if it's not a JS/TS project, you might be using a local aws-cli package)
const outputDir = this.props.configuration.settings.get(['output']);
const watchExcludes = this.patternsArrayForWatch(watchSettings.exclude, { rootDir, returnRootDirIfEmpty: false }).concat(`${outputDir}/**`, '**/.*', '**/.*/**', '**/node_modules/**');
(0, logging_1.debug)("'exclude' patterns for 'watch': %s", watchExcludes);
// Since 'cdk deploy' is a relatively slow operation for a 'watch' process,
// introduce a concurrency latch that tracks the state.
// This way, if file change events arrive when a 'cdk deploy' is still executing,
// we will batch them, and trigger another 'cdk deploy' after the current one finishes,
// making sure 'cdk deploy's always execute one at a time.
// Here's a diagram showing the state transitions:
// -------------- -------- file changed -------------- file changed -------------- file changed
// | | ready event | | ------------------> | | ------------------> | | --------------|
// | pre-ready | -------------> | open | | deploying | | queued | |
// | | | | <------------------ | | <------------------ | | <-------------|
// -------------- -------- 'cdk deploy' done -------------- 'cdk deploy' done --------------
let latch = 'pre-ready';
const cloudWatchLogMonitor = options.traceLogs ? new logs_monitor_1.CloudWatchLogEventMonitor() : undefined;
const deployAndWatch = async () => {
latch = 'deploying';
cloudWatchLogMonitor?.deactivate();
await this.invokeDeployFromWatch(options, cloudWatchLogMonitor);
// If latch is still 'deploying' after the 'await', that's fine,
// but if it's 'queued', that means we need to deploy again
while (latch === 'queued') {
// TypeScript doesn't realize latch can change between 'awaits',
// and thinks the above 'while' condition is always 'false' without the cast
latch = 'deploying';
(0, logging_1.print)("Detected file changes during deployment. Invoking 'cdk deploy' again");
await this.invokeDeployFromWatch(options, cloudWatchLogMonitor);
}
latch = 'open';
cloudWatchLogMonitor?.activate();
};
chokidar.watch(watchIncludes, {
ignored: watchExcludes,
cwd: rootDir,
// ignoreInitial: true,
}).on('ready', async () => {
latch = 'open';
(0, logging_1.debug)("'watch' received the 'ready' event. From now on, all file changes will trigger a deployment");
(0, logging_1.print)("Triggering initial 'cdk deploy'");
await deployAndWatch();
}).on('all', async (event, filePath) => {
if (latch === 'pre-ready') {
(0, logging_1.print)(`'watch' is observing ${event === 'addDir' ? 'directory' : 'the file'} '%s' for changes`, filePath);
}
else if (latch === 'open') {
(0, logging_1.print)("Detected change to '%s' (type: %s). Triggering 'cdk deploy'", filePath, event);
await deployAndWatch();
}
else { // this means latch is either 'deploying' or 'queued'
latch = 'queued';
(0, logging_1.print)("Detected change to '%s' (type: %s) while 'cdk deploy' is still running. " +
'Will queue for another deployment after this one finishes', filePath, event);
}
});
}
async import(options) {
const stacks = await this.selectStacksForDeploy(options.selector, true, true);
if (stacks.stackCount > 1) {
throw new Error(`Stack selection is ambiguous, please choose a specific stack for import [${stacks.stackArtifacts.map(x => x.id).join(', ')}]`);
}
if (!process.stdout.isTTY && !options.resourceMappingFile) {
throw new Error('--resource-mapping is required when input is not a terminal');
}
const stack = stacks.stackArtifacts[0];
(0, logging_1.highlight)(stack.displayName);
const resourceImporter = new import_1.ResourceImporter(stack, this.props.deployments);
const { additions, hasNonAdditions } = await resourceImporter.discoverImportableResources(options.force);
if (additions.length === 0) {
(0, logging_1.warning)('%s: no new resources compared to the currently deployed stack, skipping import.', chalk.bold(stack.displayName));
return;
}
// Prepare a mapping of physical resources to CDK constructs
const actualImport = !options.resourceMappingFile
? await resourceImporter.askForResourceIdentifiers(additions)
: await resourceImporter.loadResourceIdentifiers(additions, options.resourceMappingFile);
if (actualImport.importResources.length === 0) {
(0, logging_1.warning)('No resources selected for import.');
return;
}
// If "--create-resource-mapping" option was passed, write the resource mapping to the given file and exit
if (options.recordResourceMapping) {
const outputFile = options.recordResourceMapping;
fs.ensureFileSync(outputFile);
await fs.writeJson(outputFile, actualImport.resourceMap, {
spaces: 2,
encoding: 'utf8',
});
(0, logging_1.print)('%s: mapping file written.', outputFile);
return;
}
// Import the resources according to the given mapping
(0, logging_1.print)('%s: importing resources into stack...', chalk.bold(stack.displayName));
const tags = tagsForStack(stack);
await resourceImporter.importResources(actualImport, {
stack,
deployName: stack.stackName,
roleArn: options.roleArn,
toolkitStackName: options.toolkitStackName,
tags,
deploymentMethod: options.deploymentMethod,
usePreviousParameters: true,
progress: options.progress,
rollback: options.rollback,
});
// Notify user of next steps
(0, logging_1.print)(`Import operation complete. We recommend you run a ${chalk.blueBright('drift detection')} operation `
+ 'to confirm your CDK app resource definitions are up-to-date. Read more here: '
+ chalk.underline.blueBright('https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/detect-drift-stack.html'));
if (actualImport.importResources.length < additions.length) {
(0, logging_1.print)('');
(0, logging_1.warning)(`Some resources were skipped. Run another ${chalk.blueBright('cdk import')} or a ${chalk.blueBright('cdk deploy')} to bring the stack up-to-date with your CDK app definition.`);
}
else if (hasNonAdditions) {
(0, logging_1.print)('');
(0, logging_1.warning)(`Your app has pending updates or deletes excluded from this import operation. Run a ${chalk.blueBright('cdk deploy')} to bring the stack up-to-date with your CDK app definition.`);
}
}
async destroy(options) {
let stacks = await this.selectStacksForDestroy(options.selector, options.exclusively);
// The stacks will have been ordered for deployment, so reverse them for deletion.
stacks = stacks.reversed();
if (!options.force) {
// eslint-disable-next-line max-len
const confirmed = await promptly.confirm(`Are you sure you want to delete: ${chalk.blue(stacks.stackArtifacts.map(s => s.hierarchicalId).join(', '))} (y/n)?`);
if (!confirmed) {
return;
}
}
const action = options.fromDeploy ? 'deploy' : 'destroy';
for (const [index, stack] of stacks.stackArtifacts.entries()) {
(0, logging_1.success)('%s: destroying... [%s/%s]', chalk.blue(stack.displayName), index + 1, stacks.stackCount);
try {
await this.props.deployments.destroyStack({
stack,
deployName: stack.stackName,
roleArn: options.roleArn,
ci: options.ci,
});
(0, logging_1.success)(`\n ✅ %s: ${action}ed`, chalk.blue(stack.displayName));
}
catch (e) {
(0, logging_1.error)(`\n ❌ %s: ${action} failed`, chalk.blue(stack.displayName), e);
throw e;
}
}
}
async list(selectors, options = {}) {
const stacks = await this.selectStacksForList(selectors);
// if we are in "long" mode, emit the array as-is (JSON/YAML)
if (options.long) {
const long = [];
for (const stack of stacks.stackArtifacts) {
long.push({
id: stack.hierarchicalId,
name: stack.stackName,
environment: stack.environment,
});
}
(0, logging_1.data)((0, serialize_1.serializeStructure)(long, options.json ?? false));
return 0;
}
// just print stack IDs
for (const stack of stacks.stackArtifacts) {
(0, logging_1.data)(stack.hierarchicalId);
}
return 0; // exit-code
}
/**
* Synthesize the given set of stacks (called when the user runs 'cdk synth')
*
* INPUT: Stack names can be supplied using a glob filter. If no stacks are
* given, all stacks from the application are implicitly selected.
*
* OUTPUT: If more than one stack ends up being selected, an output directory
* should be supplied, where the templates will be written.
*/
async synth(stackNames, exclusively, quiet, autoValidate, json) {
const stacks = await this.selectStacksForDiff(stackNames, exclusively, autoValidate);
// if we have a single stack, print it to STDOUT
if (stacks.stackCount === 1) {
if (!quiet) {
(0, logging_1.data)((0, serialize_1.serializeStructure)(stacks.firstStack.template, json ?? false));
}
return undefined;
}
// This is a slight hack; in integ mode we allow multiple stacks to be synthesized to stdout sequentially.
// This is to make it so that we can support multi-stack integ test expectations, without so drastically
// having to change the synthesis format that we have to rerun all integ tests.
//
// Because this feature is not useful to consumers (the output is missing
// the stack names), it's not exposed as a CLI flag. Instead, it's hidden
// behind an environment variable.
const isIntegMode = process.env.CDK_INTEG_MODE === '1';
if (isIntegMode) {
(0, logging_1.data)((0, serialize_1.serializeStructure)(stacks.stackArtifacts.map(s => s.template), json ?? false));
}
// not outputting template to stdout, let's explain things to the user a little bit...
(0, logging_1.success)(`Successfully synthesized to ${chalk.blue(path.resolve(stacks.assembly.directory))}`);
(0, logging_1.print)(`Supply a stack id (${stacks.stackArtifacts.map(s => chalk.green(s.hierarchicalId)).join(', ')}) to display its template.`);
return undefined;
}
/**
* Bootstrap the CDK Toolkit stack in the accounts used by the specified stack(s).
*
* @param userEnvironmentSpecs environment names that need to have toolkit support
* provisioned, as a glob filter. If none is provided, all stacks are implicitly selected.
* @param bootstrapper Legacy or modern.
* @param options The name, role ARN, bootstrapping parameters, etc. to be used for the CDK Toolkit stack.
*/
async bootstrap(userEnvironmentSpecs, bootstrapper, options) {
// If there is an '--app' argument and an environment looks like a glob, we
// select the environments from the app. Otherwise, use what the user said.
// By default, glob for everything
const environmentSpecs = userEnvironmentSpecs.length > 0 ? [...userEnvironmentSpecs] : ['**'];
// Partition into globs and non-globs (this will mutate environmentSpecs).
const globSpecs = (0, util_2.partition)(environmentSpecs, environments_1.looksLikeGlob);
if (globSpecs.length > 0 && !this.props.cloudExecutable.hasApp) {
if (userEnvironmentSpecs.length > 0) {
// User did request this glob
throw new Error(`'${globSpecs}' is not an environment name. Specify an environment name like 'aws://123456789012/us-east-1', or run in a directory with 'cdk.json' to use wildcards.`);
}
else {
// User did not request anything
throw new Error('Specify an environment name like \'aws://123456789012/us-east-1\', or run in a directory with \'cdk.json\'.');
}
}
const environments = [
...(0, environments_1.environmentsFromDescriptors)(environmentSpecs),
];
// If there is an '--app' argument, select the environments from the app.
if (this.props.cloudExecutable.hasApp) {
environments.push(...await (0, environments_1.globEnvironmentsFromStacks)(await this.selectStacksForList([]), globSpecs, this.props.sdkProvider));
}
await Promise.all(environments.map(async (environment) => {
(0, logging_1.success)(' ⏳ Bootstrapping environment %s...', chalk.blue(environment.name));
try {
const result = await bootstrapper.bootstrapEnvironment(environment, this.props.sdkProvider, options);
const message = result.noOp
? ' ✅ Environment %s bootstrapped (no changes).'
: ' ✅ Environment %s bootstrapped.';
(0, logging_1.success)(message, chalk.blue(environment.name));
}
catch (e) {
(0, logging_1.error)(' ❌ Environment %s failed bootstrapping: %s', chalk.blue(environment.name), e);
throw e;
}
}));
}
/**
* Migrates a CloudFormation stack/template to a CDK app
* @param options Options for CDK app creation
*/
async migrate(options) {
(0, logging_1.warning)('This is an experimental feature and development on it is still in progress. We make no guarantees about the outcome or stability of the functionality.');
const language = options.language?.toLowerCase() ?? 'typescript';
try {
(0, migrate_1.validateSourceOptions)(options.fromPath, options.fromStack);
const template = (0, migrate_1.readFromPath)(options.fromPath) ||
await (0, migrate_1.readFromStack)(options.stackName, this.props.sdkProvider, (0, migrate_1.setEnvironment)(options.account, options.region));
const stack = (0, migrate_1.generateStack)(template, options.stackName, language);
(0, logging_1.success)(' ⏳ Generating CDK app for %s...', chalk.blue(options.stackName));
await (0, migrate_1.generateCdkApp)(options.stackName, stack, language, options.outputPath, options.compress);
}
catch (e) {
(0, logging_1.error)(' ❌ Migrate failed for `%s`: %s', chalk.blue(options.stackName), e.message);
throw e;
}
}
async selectStacksForList(patterns) {
const assembly = await this.assembly();
const stacks = await assembly.selectStacks({ patterns }, { defaultBehavior: cloud_assembly_1.DefaultSelection.AllStacks });
// No validation
return stacks;
}
async selectStacksForDeploy(selector, exclusively, cacheCloudAssembly) {
const assembly = await this.assembly(cacheCloudAssembly);
const stacks = await assembly.selectStacks(selector, {
extend: exclusively ? cloud_assembly_1.ExtendedStackSelection.None : cloud_assembly_1.ExtendedStackSelection.Upstream,
defaultBehavior: cloud_assembly_1.DefaultSelection.OnlySingle,
});
this.validateStacksSelected(stacks, selector.patterns);
this.validateStacks(stacks);
return stacks;
}
async selectStacksForDiff(stackNames, exclusively, autoValidate) {
const assembly = await this.assembly();
const selectedForDiff = await assembly.selectStacks({ patterns: stackNames }, {
extend: exclusively ? cloud_assembly_1.ExtendedStackSelection.None : cloud_assembly_1.ExtendedStackSelection.Upstream,
defaultBehavior: cloud_assembly_1.DefaultSelection.MainAssembly,
});
const allStacks = await this.selectStacksForList([]);
const autoValidateStacks = autoValidate
? allStacks.filter(art => art.validateOnSynth ?? false)
: new cloud_assembly_1.StackCollection(assembly, []);
this.validateStacksSelected(selectedForDiff.concat(autoValidateStacks), stackNames);
this.validateStacks(selectedForDiff.concat(autoValidateStacks));
return selectedForDiff;
}
async selectStacksForDestroy(selector, exclusively) {
const assembly = await this.assembly();
const stacks = await assembly.selectStacks(selector, {
extend: exclusively ? cloud_assembly_1.ExtendedStackSelection.None : cloud_assembly_1.ExtendedStackSelection.Downstream,
defaultBehavior: cloud_assembly_1.DefaultSelection.OnlySingle,
});
// No validation
return stacks;
}
/**
* Validate the stacks for errors and warnings according to the CLI's current settings
*/
validateStacks(stacks) {
stacks.processMetadataMessages({
ignoreErrors: this.props.ignoreErrors,
strict: this.props.strict,
verbose: this.props.verbose,
});
}
/**
* Validate that if a user specified a stack name there exists at least 1 stack selected
*/
validateStacksSelected(stacks, stackNames) {
if (stackNames.length != 0 && stacks.stackCount == 0) {
throw new Error(`No stacks match the name(s) ${stackNames}`);
}
}
/**
* Select a single stack by its name
*/
async selectSingleStackByName(stackName) {
const assembly = await this.assembly();
const stacks = await assembly.selectStacks({ patterns: [stackName] }, {
extend: cloud_assembly_1.ExtendedStackSelection.None,
defaultBehavior: cloud_assembly_1.DefaultSelection.None,
});
// Could have been a glob so check that we evaluated to exactly one
if (stacks.stackCount > 1) {
throw new Error(`This command requires exactly one stack and we matched more than one: ${stacks.stackIds}`);
}
return assembly.stackById(stacks.firstStack.id);
}
assembly(cacheCloudAssembly) {
return this.props.cloudExecutable.synthesize(cacheCloudAssembly);
}
patternsArrayForWatch(patterns, options) {
const patternsArray = patterns !== undefined
? (Array.isArray(patterns) ? patterns : [patterns])
: [];
return patternsArray.length > 0
? patternsArray
: (options.returnRootDirIfEmpty ? [options.rootDir] : []);
}
async invokeDeployFromWatch(options, cloudWatchLogMonitor) {
const deployOptions = {
...options,
requireApproval: diff_1.RequireApproval.Never,
// if 'watch' is called by invoking 'cdk deploy --watch',
// we need to make sure to not call 'deploy' with 'watch' again,
// as that would lead to a cycle
watch: false,
cloudWatchLogMonitor,
cacheCloudAssembly: false,
hotswap: options.hotswap,
extraUserAgent: `cdk-watch/hotswap-${options.hotswap !== common_1.HotswapMode.FALL_BACK ? 'on' : 'off'}`,
concurrency: options.concurrency,
};
try {
await this.deploy(deployOptions);
}
catch {
// just continue - deploy will show the error
}
}
/**
* Remove the asset publishing and building from the work graph for assets that are already in place
*/
async removePublishedAssets(graph, options) {
await graph.removeUnnecessaryAssets(assetNode => this.props.deployments.isSingleAssetPublished(assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn: options.roleArn,
toolkitStackName: options.toolkitStackName,
stackName: assetNode.parentStack.stackName,
}));
}
}
exports.CdkToolkit = CdkToolkit;
/**
* @returns an array with the tags available in the stack metadata.
*/
function tagsForStack(stack) {
return Object.entries(stack.tags).map(([Key, Value]) => ({ Key, Value }));
}
/**
* Formats time in milliseconds (which we get from 'Date.getTime()')
* to a human-readable time; returns time in seconds rounded to 2
* decimal places.
*/
function formatTime(num) {
return roundPercentage(millisecondsToSeconds(num));
}
/**
* Rounds a decimal number to two decimal points.
* The function is useful for fractions that need to be outputted as percentages.
*/
function roundPercentage(num) {
return Math.round(100 * num) / 100;
}
/**
* Given a time in milliseconds, return an equivalent amount in seconds.
*/
function millisecondsToSeconds(num) {
return num / 1000;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLXRvb2xraXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGstdG9vbGtpdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBNkI7QUFDN0IsK0JBQThCO0FBQzlCLHlDQUF5QztBQUN6QywrQkFBK0I7QUFDL0IscUNBQXFDO0FBQ3JDLCtCQUErQjtBQUMvQixxQ0FBcUM7QUFJckMsK0RBQXFJO0FBR3JJLGlEQUFtRDtBQUNuRCwwRUFBMEU7QUFDMUUsMERBQW9FO0FBQ3BFLDZGQUF5RjtBQUN6RixnREFBdUk7QUFDdkksaUNBQTRFO0FBQzVFLHFDQUE0QztBQUM1Qyx1Q0FBc0c7QUFDdEcsMkNBQXVFO0FBQ3ZFLHlDQUEyRDtBQUMzRCxpQ0FBbUQ7QUFDbkQsZ0ZBQXVFO0FBRXZFLGtFQUE2RDtBQUU3RCxnRUFBdUg7QUE4Q3ZIOztHQUVHO0FBQ0gsSUFBWSxjQWFYO0FBYkQsV0FBWSxjQUFjO0lBQ3hCOzs7OztPQUtHO0lBQ0gsNkVBQWlCLENBQUE7SUFFakI7O09BRUc7SUFDSCxtRUFBWSxDQUFBO0FBQ2QsQ0FBQyxFQWJXLGNBQWMsOEJBQWQsY0FBYyxRQWF6QjtBQUVEOzs7OztHQUtHO0FBQ0gsTUFBYSxVQUFVO0lBQ3JCLFlBQTZCLEtBQXNCO1FBQXRCLFVBQUssR0FBTCxLQUFLLENBQWlCO0lBQ25ELENBQUM7SUFFTSxLQUFLLENBQUMsUUFBUSxDQUFDLFNBQWlCLEVBQUUsSUFBYTtRQUNwRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3RCxJQUFBLGNBQUksRUFBQyxJQUFBLDhCQUFrQixFQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVcsQ0FBQyxRQUFnQjtRQUN2QyxNQUFNLElBQUksR0FBRyxJQUFJLENB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