Skip to content

Commit 3b4d18e

Browse files
iankhougithub-actionskaizencc
authored
feat(cli): telemetry opt-out command (#579)
Fixes #575 Enables users of the CDK CLI to opt out of CDK CLI telemetry collection. More details in the [RFC](aws/aws-cdk-rfcs#734). Telemetry will be enabled by default. The following command will disable it, local to your CDK App: `cdk cli-telemetry --disable` The following command will enable it again: `cdk cli-telemetry --enable` To globally enable/disable telemetry add `{ "cli-telemetry": false }` to the context values of your `~/.cdk.json` file. > At the time of writing this PR - the CDK CLI still does not yet send any telemetry data. We are providing the opt-out capability now to give enough time to opt-out in advance if they so choose. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license --------- Signed-off-by: github-actions <[email protected]> Co-authored-by: github-actions <[email protected]> Co-authored-by: Kaizen Conroy <[email protected]> Co-authored-by: Kaizen Conroy <[email protected]>
1 parent 7cac5d2 commit 3b4d18e

File tree

13 files changed

+208
-22
lines changed

13 files changed

+208
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { promises as fs } from 'fs';
2+
import * as path from 'path';
3+
import { integTest, withDefaultFixture } from '../../../lib';
4+
5+
jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime
6+
7+
integTest(
8+
'CLI Telemetry adds context value to cdk.context.json',
9+
withDefaultFixture(async (fixture) => {
10+
const contextFile = path.join(fixture.integTestDir, 'cdk.context.json');
11+
const context = {
12+
existedBefore: 'this was here',
13+
};
14+
await fs.writeFile(
15+
contextFile,
16+
JSON.stringify(context),
17+
);
18+
try {
19+
await fixture.cdk(['cli-telemetry', '--disable']);
20+
const newContext = JSON.parse((await fs.readFile(
21+
contextFile,
22+
)).toString());
23+
expect(newContext).toEqual({
24+
...context,
25+
['cli-telemetry']: false,
26+
});
27+
28+
// Test that cli-telemetry enable works too
29+
await fixture.cdk(['cli-telemetry', '--enable']);
30+
const newerContext = JSON.parse((await fs.readFile(
31+
contextFile,
32+
)).toString());
33+
expect(newerContext).toEqual({
34+
...context,
35+
['cli-telemetry']: true,
36+
});
37+
38+
// Test that cli-telemetry --no-enable works (equals --disable)
39+
await fixture.cdk(['cli-telemetry', '--no-enable']);
40+
const newestContext = JSON.parse((await fs.readFile(
41+
contextFile,
42+
)).toString());
43+
expect(newestContext).toEqual({
44+
...context,
45+
['cli-telemetry']: false,
46+
});
47+
} finally {
48+
await fs.unlink(path.join(fixture.integTestDir, 'cdk.context.json'));
49+
}
50+
}),
51+
);

packages/@aws-cdk/user-input-gen/lib/convert-to-user-input-gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ function buildConfigArgs(config: CliConfig): string {
159159
'const userInput: UserInput = {',
160160
'globalOptions,',
161161
...(Object.keys(config.commands).map((commandName) => {
162-
return `'${commandName}': ${kebabToCamelCase(commandName)}Options,`;
162+
return `'${kebabToCamelCase(commandName)}': ${kebabToCamelCase(commandName)}Options,`;
163163
})),
164164
'}',
165165
'',

packages/aws-cdk/lib/cli/cdk-toolkit.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ export class CdkToolkit {
195195
await this.props.configuration.saveContext();
196196
}
197197

198+
public async cliTelemetry(enable: boolean) {
199+
this.props.configuration.context.set('cli-telemetry', enable);
200+
await this.props.configuration.saveContext();
201+
info(`Telemetry ${enable ? 'enabled' : 'disabled'}`);
202+
}
203+
198204
public async diff(options: DiffOptions): Promise<number> {
199205
const stacks = await this.selectStacksForDiff(options.stackNames, options.exclusively);
200206

packages/aws-cdk/lib/cli/cli-config.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export async function makeConfig(): Promise<CliConfig> {
4444
'unstable': { type: 'array', 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.', default: [] },
4545
},
4646
commands: {
47-
list: {
47+
'list': {
4848
arg: {
4949
name: 'STACKS',
5050
variadic: true,
@@ -56,7 +56,7 @@ export async function makeConfig(): Promise<CliConfig> {
5656
'show-dependencies': { type: 'boolean', default: false, alias: 'd', desc: 'Display stack dependency information for each stack' },
5757
},
5858
},
59-
synth: {
59+
'synth': {
6060
arg: {
6161
name: 'STACKS',
6262
variadic: true,
@@ -69,7 +69,7 @@ export async function makeConfig(): Promise<CliConfig> {
6969
quiet: { type: 'boolean', alias: 'q', desc: 'Do not output CloudFormation Template to stdout', default: false },
7070
},
7171
},
72-
bootstrap: {
72+
'bootstrap': {
7373
arg: {
7474
name: 'ENVIRONMENTS',
7575
variadic: true,
@@ -97,7 +97,7 @@ export async function makeConfig(): Promise<CliConfig> {
9797
'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)' },
9898
},
9999
},
100-
gc: {
100+
'gc': {
101101
description: 'Garbage collect assets. Options detailed here: https://github.com/aws/aws-cdk-cli/tree/main/packages/aws-cdk#cdk-gc',
102102
arg: {
103103
name: 'ENVIRONMENTS',
@@ -112,7 +112,7 @@ export async function makeConfig(): Promise<CliConfig> {
112112
'bootstrap-stack-name': { type: 'string', desc: 'The name of the CDK toolkit stack, if different from the default "CDKToolkit"', requiresArg: true },
113113
},
114114
},
115-
deploy: {
115+
'deploy': {
116116
description: 'Deploys the stack(s) named STACKS into your AWS account',
117117
options: {
118118
'all': { type: 'boolean', desc: 'Deploy all available stacks', default: false },
@@ -193,7 +193,7 @@ export async function makeConfig(): Promise<CliConfig> {
193193
variadic: true,
194194
},
195195
},
196-
rollback: {
196+
'rollback': {
197197
description: 'Rolls back the stack(s) named STACKS to their last stable state',
198198
arg: {
199199
name: 'STACKS',
@@ -219,7 +219,7 @@ export async function makeConfig(): Promise<CliConfig> {
219219
},
220220
},
221221
},
222-
import: {
222+
'import': {
223223
description: 'Import existing resource(s) into the given STACK',
224224
arg: {
225225
name: 'STACK',
@@ -255,7 +255,7 @@ export async function makeConfig(): Promise<CliConfig> {
255255
},
256256
},
257257
},
258-
watch: {
258+
'watch': {
259259
description: "Shortcut for 'deploy --watch'",
260260
arg: {
261261
name: 'STACKS',
@@ -308,7 +308,7 @@ export async function makeConfig(): Promise<CliConfig> {
308308
'concurrency': { type: 'number', desc: 'Maximum number of simultaneous deployments (dependency permitting) to execute.', default: 1, requiresArg: true },
309309
},
310310
},
311-
destroy: {
311+
'destroy': {
312312
description: 'Destroy the stack(s) named STACKS',
313313
arg: {
314314
name: 'STACKS',
@@ -320,7 +320,7 @@ export async function makeConfig(): Promise<CliConfig> {
320320
force: { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' },
321321
},
322322
},
323-
diff: {
323+
'diff': {
324324
description: 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found',
325325
arg: {
326326
name: 'STACKS',
@@ -339,7 +339,7 @@ export async function makeConfig(): Promise<CliConfig> {
339339
'import-existing-resources': { type: 'boolean', desc: 'Whether or not the change set imports resources that already exist', default: false },
340340
},
341341
},
342-
drift: {
342+
'drift': {
343343
description: 'Detect drifts in the given CloudFormation stack(s)',
344344
arg: {
345345
name: 'STACKS',
@@ -349,28 +349,28 @@ export async function makeConfig(): Promise<CliConfig> {
349349
fail: { type: 'boolean', desc: 'Fail with exit code 1 if drift is detected' },
350350
},
351351
},
352-
metadata: {
352+
'metadata': {
353353
description: 'Returns all metadata associated with this stack',
354354
arg: {
355355
name: 'STACK',
356356
variadic: false,
357357
},
358358
},
359-
acknowledge: {
359+
'acknowledge': {
360360
aliases: ['ack'],
361361
description: 'Acknowledge a notice so that it does not show up anymore',
362362
arg: {
363363
name: 'ID',
364364
variadic: false,
365365
},
366366
},
367-
notices: {
367+
'notices': {
368368
description: 'Returns a list of relevant notices',
369369
options: {
370370
unacknowledged: { type: 'boolean', alias: 'u', default: false, desc: 'Returns a list of unacknowledged notices' },
371371
},
372372
},
373-
init: {
373+
'init': {
374374
description: 'Create a new, empty CDK project from a template.',
375375
arg: {
376376
name: 'TEMPLATE',
@@ -383,7 +383,7 @@ export async function makeConfig(): Promise<CliConfig> {
383383
'lib-version': { type: 'string', alias: 'V', default: undefined, desc: 'The version of the CDK library (aws-cdk-lib) to initialize the project with. Defaults to the version that was current when this CLI was built.' },
384384
},
385385
},
386-
migrate: {
386+
'migrate': {
387387
description: 'Migrate existing AWS resources into a CDK app',
388388
options: {
389389
'stack-name': { type: 'string', alias: 'n', desc: 'The name assigned to the stack created in the new project. The name of the app will be based off this name as well.', requiresArg: true },
@@ -411,15 +411,15 @@ export async function makeConfig(): Promise<CliConfig> {
411411
'compress': { type: 'boolean', desc: 'Use this flag to zip the generated CDK app' },
412412
},
413413
},
414-
context: {
414+
'context': {
415415
description: 'Manage cached context values',
416416
options: {
417417
reset: { alias: 'e', desc: 'The context key (or its index) to reset', type: 'string', requiresArg: true, default: undefined },
418418
force: { alias: 'f', desc: 'Ignore missing key error', type: 'boolean', default: false },
419419
clear: { desc: 'Clear all context', type: 'boolean', default: false },
420420
},
421421
},
422-
docs: {
422+
'docs': {
423423
aliases: ['doc'],
424424
description: 'Opens the reference documentation in a browser',
425425
options: {
@@ -431,10 +431,10 @@ export async function makeConfig(): Promise<CliConfig> {
431431
},
432432
},
433433
},
434-
doctor: {
434+
'doctor': {
435435
description: 'Check your set-up for potential problems',
436436
},
437-
refactor: {
437+
'refactor': {
438438
description: 'Moves resources between stacks or within the same stack',
439439
arg: {
440440
name: 'STACKS',
@@ -463,6 +463,21 @@ export async function makeConfig(): Promise<CliConfig> {
463463
},
464464
},
465465
},
466+
'cli-telemetry': {
467+
description: 'Enable or disable anonymous telemetry',
468+
options: {
469+
enable: {
470+
type: 'boolean',
471+
desc: 'Enable anonymous telemetry',
472+
conflicts: 'disable',
473+
},
474+
disable: {
475+
type: 'boolean',
476+
desc: 'Disable anonymous telemetry',
477+
conflicts: 'enable',
478+
},
479+
},
480+
},
466481
},
467482
};
468483
}

packages/aws-cdk/lib/cli/cli.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,15 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
454454
ioHost.currentAction = 'notices';
455455
return cli.acknowledge(args.ID);
456456

457+
case 'cli-telemetry':
458+
ioHost.currentAction = 'cli-telemetry';
459+
if (args.enable === undefined && args.disable === undefined) {
460+
throw new ToolkitError('Must specify either \'--enable\' or \'--disable\'');
461+
}
462+
463+
const enable = args.enable ?? !args.disable;
464+
return cli.cliTelemetry(enable);
465+
457466
case 'init':
458467
ioHost.currentAction = 'init';
459468
const language = configuration.settings.get(['language']);

packages/aws-cdk/lib/cli/convert-to-user-input.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ export function convertYargsToUserInput(args: any): UserInput {
275275
STACKS: args.STACKS,
276276
};
277277
break;
278+
279+
case 'cli-telemetry':
280+
commandOptions = {
281+
enable: args.enable,
282+
disable: args.disable,
283+
};
284+
break;
278285
}
279286
const userInput: UserInput = {
280287
command: args._[0],
@@ -472,6 +479,10 @@ export function convertConfigToUserInput(config: any): UserInput {
472479
mappingFile: config.refactor?.mappingFile,
473480
revert: config.refactor?.revert,
474481
};
482+
const cliTelemetryOptions = {
483+
enable: config.cliTelemetry?.enable,
484+
disable: config.cliTelemetry?.disable,
485+
};
475486
const userInput: UserInput = {
476487
globalOptions,
477488
list: listOptions,
@@ -494,6 +505,7 @@ export function convertConfigToUserInput(config: any): UserInput {
494505
docs: docsOptions,
495506
doctor: doctorOptions,
496507
refactor: refactorOptions,
508+
cliTelemetry: cliTelemetryOptions,
497509
};
498510

499511
return userInput;

packages/aws-cdk/lib/cli/io-host/cli-io-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type CliAction =
1919
| 'docs'
2020
| 'notices'
2121
| 'version'
22+
| 'cli-telemetry'
2223
| 'none';
2324

2425
export interface CliIoHostProps {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,21 @@ export function parseCommandLineArguments(args: Array<string>): any {
907907
desc: 'If specified, the command will revert the refactor operation. This is only valid if a mapping file was provided.',
908908
}),
909909
)
910+
.command('cli-telemetry', 'Enable or disable anonymous telemetry', (yargs: Argv) =>
911+
yargs
912+
.option('enable', {
913+
default: undefined,
914+
type: 'boolean',
915+
desc: 'Enable anonymous telemetry',
916+
conflicts: 'disable',
917+
})
918+
.option('disable', {
919+
default: undefined,
920+
type: 'boolean',
921+
desc: 'Disable anonymous telemetry',
922+
conflicts: 'enable',
923+
}),
924+
)
910925
.version(helpers.cliVersion())
911926
.demandCommand(1, '')
912927
.recommendCommands()

packages/aws-cdk/lib/cli/user-configuration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export enum Command {
3838
DOCTOR = 'doctor',
3939
REFACTOR = 'refactor',
4040
DRIFT = 'drift',
41+
CLI_TELEMETRY = 'cli-telemetry',
4142
}
4243

4344
const BUNDLING_COMMANDS = [

packages/aws-cdk/lib/cli/user-input.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export interface UserInput {
128128
* Moves resources between stacks or within the same stack
129129
*/
130130
readonly refactor?: RefactorOptions;
131+
132+
/**
133+
* Enable or disable anonymous telemetry
134+
*/
135+
readonly cliTelemetry?: CliTelemetryOptions;
131136
}
132137

133138
/**
@@ -1452,3 +1457,24 @@ export interface RefactorOptions {
14521457
*/
14531458
readonly STACKS?: Array<string>;
14541459
}
1460+
1461+
/**
1462+
* Enable or disable anonymous telemetry
1463+
*
1464+
* @struct
1465+
*/
1466+
export interface CliTelemetryOptions {
1467+
/**
1468+
* Enable anonymous telemetry
1469+
*
1470+
* @default - undefined
1471+
*/
1472+
readonly enable?: boolean;
1473+
1474+
/**
1475+
* Disable anonymous telemetry
1476+
*
1477+
* @default - undefined
1478+
*/
1479+
readonly disable?: boolean;
1480+
}

0 commit comments

Comments
 (0)