Skip to content

Commit 2e75924

Browse files
authored
fix(cli): cdk deploy -R does not disable rollback (#32514)
### Issue `cdk deploy -R` should be the same as `cdk deploy --no-rollback` or `cdk deploy --rollback=false`, but has no effect ### Reason for this change PR #31850 introduced this bug by accidentally flipping the order of arguments passed to the `yargsNegativeAlias` helper. This caused the helper to have no effect. ### Description of changes - Changed the codegen to fully generate negative aliases for the user - Fixed the generate code to use the correct argument order again for `yargsNegativeAlias` - Renamed the parameters to make it easier to understand. - Made `nargs: 1` and `requiresArg: true` implied for all array options. Some options did miss one or both of these. This was an oversight. ### Description of how you validated changes Added an explicit test case for `-R`. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f937d30 commit 2e75924

File tree

10 files changed

+167
-94
lines changed

10 files changed

+167
-94
lines changed

aws-cdk.code-workspace

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
{
3131
"name": "aws-custom-resource-sdk-adapter",
3232
"rootPath": "packages/@aws-cdk/aws-custom-resource-sdk-adapter"
33-
}
33+
},
34+
{ "name": "yargs-gen", "rootPath": "tools/@aws-cdk/yargs-gen" }
3435
]
3536
},
3637
"extensions": {

packages/aws-cdk/bin/cdk

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
// source maps must be enabled before importing files
33
process.setSourceMapsEnabled(true);
4-
const { cli } = require("../lib/cli");
4+
const { cli } = require("../lib");
55

66
cli();

packages/aws-cdk/lib/cli.ts

+1-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Notices } from '../lib/notices';
2727
import { Command, Configuration, Settings } from '../lib/settings';
2828
import * as version from '../lib/version';
2929
import { SdkToCliLogger } from './api/aws-auth/sdk-logger';
30+
import { yargsNegativeAlias } from './util/yargs-helpers';
3031

3132
/* eslint-disable max-len */
3233
/* eslint-disable @typescript-eslint/no-shadow */ // yargs
@@ -527,18 +528,6 @@ function arrayFromYargs(xs: string[]): string[] | undefined {
527528
return xs.filter((x) => x !== '');
528529
}
529530

530-
function yargsNegativeAlias<T extends { [x in S | L]: boolean | undefined }, S extends string, L extends string>(
531-
shortName: S,
532-
longName: L,
533-
): (argv: T) => T {
534-
return (argv: T) => {
535-
if (shortName in argv && argv[shortName]) {
536-
(argv as any)[longName] = false;
537-
}
538-
return argv;
539-
};
540-
}
541-
542531
function determineHotswapMode(hotswap?: boolean, hotswapFallback?: boolean, watch?: boolean): HotswapMode {
543532
if (hotswap && hotswapFallback) {
544533
throw new Error('Can not supply both --hotswap and --hotswap-fallback at the same time');

packages/aws-cdk/lib/config.ts

+13-25
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export function makeConfig(): CliConfig {
1313
globalOptions: {
1414
'app': { type: 'string', alias: 'a', desc: 'REQUIRED WHEN RUNNING APP: command-line for executing your app or a cloud assembly directory (e.g. "node bin/my-app.js"). Can also be specified in cdk.json or ~/.cdk.json', requiresArg: true },
1515
'build': { type: 'string', desc: 'Command-line for a pre-synth build' },
16-
'context': { type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', nargs: 1, requiresArg: true },
17-
'plugin': { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', nargs: 1 },
16+
'context': { type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)' },
17+
'plugin': { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times' },
1818
'trace': { type: 'boolean', desc: 'Print trace for stack warnings' },
1919
'strict': { type: 'boolean', desc: 'Do not construct stacks with warnings' },
2020
'lookups': { type: 'boolean', desc: 'Perform context lookups (synthesis fails if this is disabled and context lookups need to be performed)', default: true },
@@ -77,11 +77,11 @@ export function makeConfig(): CliConfig {
7777
'bootstrap-customer-key': { type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', default: undefined, conflicts: 'bootstrap-kms-key-id' },
7878
'qualifier': { type: 'string', desc: 'String which must be unique for each bootstrap stack. You must configure it on your CDK app if you change this from the default.', default: undefined },
7979
'public-access-block-configuration': { type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', default: undefined },
80-
'tags': { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] },
80+
'tags': { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', default: [] },
8181
'execute': { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true },
82-
'trust': { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true },
83-
'trust-for-lookup': { type: 'array', desc: 'The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true },
84-
'cloudformation-execution-policies': { type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only)', default: [], nargs: 1, requiresArg: true },
82+
'trust': { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only)', default: [] },
83+
'trust-for-lookup': { type: 'array', desc: 'The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only)', default: [] },
84+
'cloudformation-execution-policies': { type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only)', default: [] },
8585
'force': { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false },
8686
'termination-protection': { type: 'boolean', default: undefined, desc: 'Toggle CloudFormation termination protection on the bootstrap stacks' },
8787
'show-template': { type: 'boolean', desc: 'Instead of actual bootstrapping, print the current CLI\'s bootstrapping template to stdout for customization', default: false },
@@ -109,12 +109,12 @@ export function makeConfig(): CliConfig {
109109
description: 'Deploys the stack(s) named STACKS into your AWS account',
110110
options: {
111111
'all': { type: 'boolean', desc: 'Deploy all available stacks', default: false },
112-
'build-exclude': { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] },
112+
'build-exclude': { type: 'array', alias: 'E', desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] },
113113
'exclusively': { type: 'boolean', alias: 'e', desc: 'Only deploy requested stacks, don\'t include dependencies' },
114114
'require-approval': { type: 'string', choices: [RequireApproval.Never, RequireApproval.AnyChange, RequireApproval.Broadening], desc: 'What security-sensitive changes need manual approval' },
115-
'notification-arns': { type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events. These will be added to ARNs specified with the \'notificationArns\' stack property.', nargs: 1, requiresArg: true },
115+
'notification-arns': { type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events. These will be added to ARNs specified with the \'notificationArns\' stack property.' },
116116
// @deprecated(v2) -- tags are part of the Cloud Assembly and tags specified here will be overwritten on the next deployment
117-
'tags': { type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)', nargs: 1, requiresArg: true },
117+
'tags': { type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)' },
118118
'execute': { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet) (deprecated)', deprecated: true },
119119
'change-set-name': { type: 'string', desc: 'Name of the CloudFormation change set to create (only if method is not direct)' },
120120
'method': {
@@ -125,7 +125,7 @@ export function makeConfig(): CliConfig {
125125
desc: 'How to perform the deployment. Direct is a bit faster but lacks progress information',
126126
},
127127
'force': { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false },
128-
'parameters': { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', nargs: 1, requiresArg: true, default: {} },
128+
'parameters': { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', default: {} },
129129
'outputs-file': { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true },
130130
'previous-parameters': { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' },
131131
'toolkit-stack-name': { type: 'string', desc: 'The name of the existing CDK toolkit stack (only used for app using legacy synthesis)', requiresArg: true },
@@ -136,11 +136,6 @@ export function makeConfig(): CliConfig {
136136
'Note: do **not** disable this flag for deployments with resource replacements, as that will always fail',
137137
negativeAlias: 'R',
138138
},
139-
'R': {
140-
type: 'boolean',
141-
hidden: true,
142-
// Hack to get '-R' as an alias for '--no-rollback', suggested by: https://github.com/yargs/yargs/issues/1729
143-
},
144139
'hotswap': {
145140
type: 'boolean',
146141
desc: "Attempts to perform a 'hotswap' deployment, " +
@@ -199,8 +194,6 @@ export function makeConfig(): CliConfig {
199194
'orphan': {
200195
// alias: 'o' conflicts with --output
201196
type: 'array',
202-
nargs: 1,
203-
requiresArg: true,
204197
desc: 'Orphan the given resources, identified by their logical ID (can be specified multiple times)',
205198
default: [],
206199
},
@@ -261,7 +254,7 @@ export function makeConfig(): CliConfig {
261254
// .option('previous-parameters', { type: 'boolean', default: true, desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)' })
262255
// .option('outputs-file', { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true })
263256
// .option('notification-arns', { type: 'array', desc: 'ARNs of SNS topics that CloudFormation will notify with stack related events', nargs: 1, requiresArg: true })
264-
'build-exclude': { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] },
257+
'build-exclude': { type: 'array', alias: 'E', desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] },
265258
'exclusively': { type: 'boolean', alias: 'e', desc: 'Only deploy requested stacks, don\'t include dependencies' },
266259
'change-set-name': { type: 'string', desc: 'Name of the CloudFormation change set to create' },
267260
'force': { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false },
@@ -271,12 +264,7 @@ export function makeConfig(): CliConfig {
271264
type: 'boolean',
272265
desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. " +
273266
'Note: do **not** disable this flag for deployments with resource replacements, as that will always fail',
274-
negativeAlias: '-R',
275-
},
276-
// same hack for -R as above in 'deploy'
277-
'R': {
278-
type: 'boolean',
279-
hidden: true,
267+
negativeAlias: 'R',
280268
},
281269
'hotswap': {
282270
type: 'boolean',
@@ -436,7 +424,7 @@ export class DynamicValue {
436424
public static fromInline(f: () => any): DynamicResult {
437425
return {
438426
dynamicType: 'function',
439-
dynamicValue: f,
427+
dynamicValue: f.toString(),
440428
};
441429
}
442430
}

packages/aws-cdk/lib/parse-command-line-arguments.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function parseCommandLineArguments(
3939
alias: 'p',
4040
desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times',
4141
nargs: 1,
42+
requiresArg: true,
4243
})
4344
.option('trace', {
4445
type: 'boolean',
@@ -148,6 +149,8 @@ export function parseCommandLineArguments(
148149
type: 'array',
149150
desc: 'Opt in to unstable features. The flag indicates that the scope and API of a feature might still change. Otherwise the feature is generally production ready and fully supported. Can be specified multiple times.',
150151
default: [],
152+
nargs: 1,
153+
requiresArg: true,
151154
})
152155
.command(['list [STACKS..]', 'ls [STACKS..]'], 'Lists all stacks in the app', (yargs: Argv) =>
153156
yargs
@@ -231,9 +234,9 @@ export function parseCommandLineArguments(
231234
type: 'array',
232235
alias: 't',
233236
desc: 'Tags to add for the stack (KEY=VALUE)',
237+
default: [],
234238
nargs: 1,
235239
requiresArg: true,
236-
default: [],
237240
})
238241
.option('execute', {
239242
type: 'boolean',
@@ -339,9 +342,10 @@ export function parseCommandLineArguments(
339342
.option('build-exclude', {
340343
type: 'array',
341344
alias: 'E',
342-
nargs: 1,
343345
desc: 'Do not rebuild asset with the given ID. Can be specified multiple times',
344346
default: [],
347+
nargs: 1,
348+
requiresArg: true,
345349
})
346350
.option('exclusively', {
347351
type: 'boolean',
@@ -391,9 +395,9 @@ export function parseCommandLineArguments(
391395
.option('parameters', {
392396
type: 'array',
393397
desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)',
398+
default: {},
394399
nargs: 1,
395400
requiresArg: true,
396-
default: {},
397401
})
398402
.option('outputs-file', {
399403
type: 'string',
@@ -420,11 +424,8 @@ export function parseCommandLineArguments(
420424
type: 'boolean',
421425
desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. Note: do **not** disable this flag for deployments with resource replacements, as that will always fail",
422426
})
423-
.middleware(yargsNegativeAlias('rollback', 'R'), true)
424-
.option('R', {
425-
type: 'boolean',
426-
hidden: true,
427-
})
427+
.middleware(yargsNegativeAlias('R', 'rollback'), true)
428+
.option('R', { type: 'boolean', hidden: true })
428429
.option('hotswap', {
429430
type: 'boolean',
430431
desc: "Attempts to perform a 'hotswap' deployment, but does not fall back to a full deployment if that is not possible. Instead, changes to any non-hotswappable properties are ignored.Do not use this in production environments",
@@ -486,10 +487,10 @@ export function parseCommandLineArguments(
486487
})
487488
.option('orphan', {
488489
type: 'array',
489-
nargs: 1,
490-
requiresArg: true,
491490
desc: 'Orphan the given resources, identified by their logical ID (can be specified multiple times)',
492491
default: [],
492+
nargs: 1,
493+
requiresArg: true,
493494
})
494495
)
495496
.command('import [STACK]', 'Import existing resource(s) into the given STACK', (yargs: Argv) =>
@@ -535,9 +536,10 @@ export function parseCommandLineArguments(
535536
.option('build-exclude', {
536537
type: 'array',
537538
alias: 'E',
538-
nargs: 1,
539539
desc: 'Do not rebuild asset with the given ID. Can be specified multiple times',
540540
default: [],
541+
nargs: 1,
542+
requiresArg: true,
541543
})
542544
.option('exclusively', {
543545
type: 'boolean',
@@ -568,11 +570,8 @@ export function parseCommandLineArguments(
568570
type: 'boolean',
569571
desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. Note: do **not** disable this flag for deployments with resource replacements, as that will always fail",
570572
})
571-
.middleware(yargsNegativeAlias('rollback', '-R'), true)
572-
.option('R', {
573-
type: 'boolean',
574-
hidden: true,
575-
})
573+
.middleware(yargsNegativeAlias('R', 'rollback'), true)
574+
.option('R', { type: 'boolean', hidden: true })
576575
.option('hotswap', {
577576
type: 'boolean',
578577
desc: "Attempts to perform a 'hotswap' deployment, but does not fall back to a full deployment if that is not possible. Instead, changes to any non-hotswappable properties are ignored.'true' by default, use --no-hotswap to turn off",
@@ -734,6 +733,8 @@ export function parseCommandLineArguments(
734733
.option('filter', {
735734
type: 'array',
736735
desc: 'Filters the resource scan based on the provided criteria in the following format: "key1=value1,key2=value2"\n This field can be passed multiple times for OR style filtering: \n filtering options: \n resource-identifier: A key-value pair that identifies the target resource. i.e. {"ClusterName", "myCluster"}\n resource-type-prefix: A string that represents a type-name prefix. i.e. "AWS::DynamoDB::"\n tag-key: a string that matches resources with at least one tag with the provided key. i.e. "myTagKey"\n tag-value: a string that matches resources with at least one tag with the provided value. i.e. "myTagValue"',
736+
nargs: 1,
737+
requiresArg: true,
737738
})
738739
.option('compress', {
739740
type: 'boolean',
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* yargs middleware to negate an option if a negative alias is provided
3+
* E.g. `-R` will imply `--rollback=false`
4+
*
5+
* @param optionToNegate The name of the option to negate, e.g. `rollback`
6+
* @param negativeAlias The alias that should negate the option, e.g. `R`
7+
* @returns
8+
*/
9+
export function yargsNegativeAlias<T extends { [x in S | L]: boolean | undefined }, S extends string, L extends string>(
10+
negativeAlias: S,
11+
optionToNegate: L,
12+
): (argv: T) => T {
13+
return (argv: T) => {
14+
// if R in argv && argv[R]
15+
// then argv[rollback] = false
16+
if (negativeAlias in argv && argv[negativeAlias]) {
17+
(argv as any)[optionToNegate] = false;
18+
}
19+
return argv;
20+
};
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { parseCommandLineArguments } from '../lib/parse-command-line-arguments';
2+
import { yargsNegativeAlias } from '../lib/util/yargs-helpers';
3+
4+
test('cdk deploy -R sets rollback to false', async () => {
5+
const argv = await parseCommandLineArguments(['deploy', '-R'], 'open', ['typescript'], ['typescript'], 'test', yargsNegativeAlias);
6+
7+
expect(argv.rollback).toBe(false);
8+
});

0 commit comments

Comments
 (0)