UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

738 lines 136 kB
"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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