1
1
#!/usr/bin/env node
2
2
import 'source-map-support/register' ;
3
3
4
- import cxapi = require ( '@aws-cdk/cx-api' ) ;
5
4
import colors = require ( 'colors/safe' ) ;
6
5
import fs = require ( 'fs-extra' ) ;
7
6
import util = require ( 'util' ) ;
8
7
import yargs = require ( 'yargs' ) ;
9
8
10
- import { bootstrapEnvironment , deployStack , destroyStack , loadToolkitInfo , Mode , SDK } from '../lib' ;
9
+ import { bootstrapEnvironment , deployStack , destroyStack , loadToolkitInfo , SDK } from '../lib' ;
11
10
import { environmentsFromDescriptors , globEnvironmentsFromStacks } from '../lib/api/cxapp/environments' ;
12
11
import { execProgram } from '../lib/api/cxapp/exec' ;
13
12
import { AppStacks , ExtendedStackSelection , listStackNames } from '../lib/api/cxapp/stacks' ;
13
+ import { CloudFormationDeploymentTarget } from '../lib/api/deployment-target' ;
14
14
import { leftPad } from '../lib/api/util/string-manipulation' ;
15
- import { printSecurityDiff , printStackDiff , RequireApproval } from '../lib/diff' ;
15
+ import { CdkToolkit } from '../lib/cdk-toolkit' ;
16
+ import { printSecurityDiff , RequireApproval } from '../lib/diff' ;
16
17
import { availableInitLanguages , cliInit , printAvailableTemplates } from '../lib/init' ;
17
18
import { interactive } from '../lib/interactive' ;
18
19
import { data , debug , error , highlight , print , setVerbose , success } from '../lib/logging' ;
19
20
import { PluginHost } from '../lib/plugin' ;
20
21
import { parseRenames } from '../lib/renames' ;
21
- import { deserializeStructure , serializeStructure } from '../lib/serialize' ;
22
+ import { serializeStructure } from '../lib/serialize' ;
22
23
import { Configuration , Settings } from '../lib/settings' ;
23
24
import { VERSION } from '../lib/version' ;
24
25
@@ -66,7 +67,8 @@ async function parseCommandLineArguments() {
66
67
. command ( 'destroy [STACKS..]' , 'Destroy the stack(s) named STACKS' , yargs => yargs
67
68
. option ( 'exclusively' , { type : 'boolean' , alias : 'x' , desc : 'only deploy requested stacks, don\'t include dependees' } )
68
69
. option ( 'force' , { type : 'boolean' , alias : 'f' , desc : 'Do not ask for confirmation before destroying the stacks' } ) )
69
- . command ( 'diff [STACK]' , 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found' , yargs => yargs
70
+ . command ( 'diff [STACKS..]' , 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found' , yargs => yargs
71
+ . option ( 'exclusively' , { type : 'boolean' , alias : 'e' , desc : 'only diff requested stacks, don\'t include dependencies' } )
70
72
. option ( 'context-lines' , { type : 'number' , desc : 'number of context lines to include in arbitrary JSON diff rendering' , default : 3 } )
71
73
. option ( 'template' , { type : 'string' , desc : 'the path to the CloudFormation template to compare with' } )
72
74
. option ( 'strict' , { type : 'boolean' , desc : 'do not filter out AWS::CDK::Metadata resources' , default : false } ) )
@@ -107,13 +109,17 @@ async function initCommandLine() {
107
109
const configuration = new Configuration ( argv ) ;
108
110
await configuration . load ( ) ;
109
111
112
+ const provisioner = new CloudFormationDeploymentTarget ( { aws } ) ;
113
+
110
114
const appStacks = new AppStacks ( {
111
115
verbose : argv . trace || argv . verbose ,
112
116
ignoreErrors : argv . ignoreErrors ,
113
117
strict : argv . strict ,
114
- configuration, aws, synthesizer : execProgram } ) ;
115
-
116
- const renames = parseRenames ( argv . rename ) ;
118
+ configuration,
119
+ aws,
120
+ synthesizer : execProgram ,
121
+ renames : parseRenames ( argv . rename )
122
+ } ) ;
117
123
118
124
/** Function to load plug-ins, using configurations additively. */
119
125
function loadPlugins ( ...settings : Settings [ ] ) {
@@ -165,13 +171,21 @@ async function initCommandLine() {
165
171
args . STACKS = args . STACKS || [ ] ;
166
172
args . ENVIRONMENTS = args . ENVIRONMENTS || [ ] ;
167
173
174
+ const cli = new CdkToolkit ( { appStacks, provisioner } ) ;
175
+
168
176
switch ( command ) {
169
177
case 'ls' :
170
178
case 'list' :
171
179
return await cliList ( { long : args . long } ) ;
172
180
173
181
case 'diff' :
174
- return await diffStack ( await findStack ( args . STACK ) , args . template , args . strict , args . contextLines ) ;
182
+ return await cli . diff ( {
183
+ stackNames : args . STACKS ,
184
+ exclusively : args . exclusively ,
185
+ templatePath : args . template ,
186
+ strict : args . strict ,
187
+ contextLines : args . contextLines
188
+ } ) ;
175
189
176
190
case 'bootstrap' :
177
191
return await cliBootstrap ( args . ENVIRONMENTS , toolkitStackName , args . roleArn ) ;
@@ -258,7 +272,6 @@ async function initCommandLine() {
258
272
const autoSelectDependencies = ! exclusively && outputDir !== undefined ;
259
273
260
274
const stacks = await appStacks . selectStacks ( stackNames , autoSelectDependencies ? ExtendedStackSelection . Upstream : ExtendedStackSelection . None ) ;
261
- renames . validateSelectedStacks ( stacks ) ;
262
275
263
276
if ( doInteractive ) {
264
277
if ( stacks . length !== 1 ) {
@@ -294,9 +307,8 @@ async function initCommandLine() {
294
307
295
308
let i = 0 ;
296
309
for ( const stack of stacks ) {
297
- const finalName = renames . finalName ( stack . name ) ;
298
310
const prefix = numbered ? leftPad ( `${ i } ` , 3 , '0' ) + '.' : '';
299
- const fileName = `${outputDir } / ${prefix } ${finalName } . template . $ { json ? 'json' : 'yaml' } `;
311
+ const fileName = `${outputDir } / ${prefix } ${stack . name } . template . ${json ? 'json ' : 'yaml '} `;
300
312
highlight(fileName);
301
313
await fs.writeFile(fileName, toJsonOrYaml(stack.template));
302
314
i++;
@@ -337,7 +349,6 @@ async function initCommandLine() {
337
349
if (requireApproval === undefined) { requireApproval = RequireApproval.Broadening; }
338
350
339
351
const stacks = await appStacks.selectStacks(stackNames, exclusively ? ExtendedStackSelection.None : ExtendedStackSelection.Upstream);
340
- renames.validateSelectedStacks(stacks);
341
352
342
353
for (const stack of stacks) {
343
354
if (stacks.length !== 1) { highlight(stack.name); }
@@ -346,10 +357,9 @@ async function initCommandLine() {
346
357
throw new Error(` Stack ${stack . name } does not define an environment , and AWS credentials could not be obtained from standard locations or no region was configured . `);
347
358
}
348
359
const toolkitInfo = await loadToolkitInfo(stack.environment, aws, toolkitStackName);
349
- const deployName = renames.finalName(stack.name);
350
360
351
361
if (requireApproval !== RequireApproval.Never) {
352
- const currentTemplate = await readCurrentTemplate(stack);
362
+ const currentTemplate = await provisioner. readCurrentTemplate(stack);
353
363
if (printSecurityDiff(currentTemplate, stack, requireApproval)) {
354
364
355
365
// only talk to user if we STDIN is a terminal (otherwise, fail)
@@ -364,14 +374,14 @@ async function initCommandLine() {
364
374
}
365
375
}
366
376
367
- if (deployName !== stack.name ) {
368
- print('%s: deploying... (was %s)', colors.bold(deployName ), colors.bold(stack.name ));
377
+ if (stack.name !== stack.originalName ) {
378
+ print('%s: deploying... (was %s)', colors.bold(stack.name ), colors.bold(stack.originalName ));
369
379
} else {
370
380
print('%s: deploying...', colors.bold(stack.name));
371
381
}
372
382
373
383
try {
374
- const result = await deployStack({ stack, sdk: aws, toolkitInfo, deployName, roleArn, ci });
384
+ const result = await deployStack({ stack, sdk: aws, toolkitInfo, deployName: stack.name , roleArn, ci });
375
385
const message = result.noOp
376
386
? ` ✅ % s ( no changes ) `
377
387
: ` ✅ % s `;
@@ -384,7 +394,7 @@ async function initCommandLine() {
384
394
385
395
for (const name of Object.keys(result.outputs)) {
386
396
const value = result.outputs[name];
387
- print('%s.%s = %s', colors.cyan(deployName ), colors.cyan(name), colors.underline(colors.cyan(value)));
397
+ print('%s.%s = %s', colors.cyan(stack.name ), colors.cyan(name), colors.underline(colors.cyan(value)));
388
398
}
389
399
390
400
print('\nStack ARN:');
@@ -403,8 +413,6 @@ async function initCommandLine() {
403
413
// The stacks will have been ordered for deployment, so reverse them for deletion.
404
414
stacks.reverse();
405
415
406
- renames.validateSelectedStacks(stacks);
407
-
408
416
if (!force) {
409
417
// tslint:disable-next-line:max-line-length
410
418
const confirmed = await confirm(` Are you sure you want to delete : $ { colors . blue ( stacks . map ( s => s . name ) . join ( ', ' ) ) } ( y / n ) ?`);
@@ -414,59 +422,17 @@ async function initCommandLine() {
414
422
}
415
423
416
424
for (const stack of stacks) {
417
- const deployName = renames.finalName(stack.name);
418
-
419
- success('%s: destroying...', colors.blue(deployName));
425
+ success('%s: destroying...', colors.blue(stack.name));
420
426
try {
421
- await destroyStack({ stack, sdk: aws, deployName, roleArn });
422
- success('\n ✅ %s: destroyed', colors.blue(deployName ));
427
+ await destroyStack({ stack, sdk: aws, deployName: stack.name , roleArn });
428
+ success('\n ✅ %s: destroyed', colors.blue(stack.name ));
423
429
} catch (e) {
424
- error('\n ❌ %s: destroy failed', colors.blue(deployName ), e);
430
+ error('\n ❌ %s: destroy failed', colors.blue(stack.name ), e);
425
431
throw e;
426
432
}
427
433
}
428
434
}
429
435
430
- async function diffStack(stackName: string, templatePath: string | undefined, strict: boolean, context: number): Promise<number> {
431
- const stack = await appStacks.synthesizeStack(stackName);
432
- const currentTemplate = await readCurrentTemplate(stack, templatePath);
433
- if (printStackDiff(currentTemplate, stack, strict, context) === 0) {
434
- return 0;
435
- } else {
436
- return 1;
437
- }
438
- }
439
-
440
- async function readCurrentTemplate(stack: cxapi.SynthesizedStack, templatePath?: string): Promise<{ [key: string]: any }> {
441
- if (templatePath) {
442
- if (!await fs.pathExists(templatePath)) {
443
- throw new Error(` There is no file at ${templatePath } `);
444
- }
445
- const fileContent = await fs.readFile(templatePath, { encoding: 'UTF-8' });
446
- return parseTemplate(fileContent);
447
- } else {
448
- const stackName = renames.finalName(stack.name);
449
- debug(` Reading existing template for stack ${stackName } . `);
450
-
451
- const cfn = await aws.cloudFormation(stack.environment, Mode.ForReading);
452
- try {
453
- const response = await cfn.getTemplate({ StackName: stackName }).promise();
454
- return (response.TemplateBody && parseTemplate(response.TemplateBody)) || {};
455
- } catch (e) {
456
- if (e.code === 'ValidationError' && e.message === ` Stack with id ${stackName } does not exist `) {
457
- return {};
458
- } else {
459
- throw e;
460
- }
461
- }
462
- }
463
-
464
- /* Attempt to parse YAML, fall back to JSON. */
465
- function parseTemplate(text: string): any {
466
- return deserializeStructure(text);
467
- }
468
- }
469
-
470
436
/**
471
437
* Match a single stack from the list of available stacks
472
438
*/
0 commit comments