Skip to content

chore(cli): allow valid enum values in telemetry data #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 113 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
0c1c0b3
chore(cli): telemetry client
kaizencc Jun 9, 2025
2fe74df
docs
kaizencc Jun 9, 2025
c6bf5f4
wip
kaizencc Jun 13, 2025
e91e7e3
use interfaces and fix tests
kaizencc Jun 13, 2025
0d33ac5
add schema and pr feedback
kaizencc Jun 16, 2025
7d5a19a
feat(cli): telemetry for list
kaizencc Jun 18, 2025
41a461b
Merge branch 'main' into conroy/list
kaizencc Jun 18, 2025
c380240
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jun 18, 2025
e51d30d
change to parsed url
kaizencc Jun 18, 2025
9d980d5
readonly
kaizencc Jun 18, 2025
d8dd12f
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jun 18, 2025
9126147
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jun 19, 2025
ab560fd
small change
kaizencc Jun 19, 2025
4e361ae
retries implemented, not yet tested
kaizencc Jun 19, 2025
69e63cd
add commented out test
kaizencc Jun 19, 2025
da20533
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jun 19, 2025
ca3f172
refactor list telemetry, still wip
kaizencc Jun 20, 2025
3b0082e
1 line change
kaizencc Jun 20, 2025
8824e28
add message code
kaizencc Jun 20, 2025
f7cb38a
add failed synth
kaizencc Jun 20, 2025
8760b44
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jun 21, 2025
ece2874
fixup sanitation"
kaizencc Jun 23, 2025
4dea47d
pr feedback
kaizencc Jun 23, 2025
3dbbe62
add proxy support, better retries, test succeeds
kaizencc Jun 25, 2025
feaf376
lint
kaizencc Jun 25, 2025
83ebd08
lints
kaizencc Jun 25, 2025
4b8397b
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jun 25, 2025
cf285bb
wip
kaizencc Jun 25, 2025
2c8191b
reworked with iohost
kaizencc Jun 26, 2025
6c977b6
include spans
kaizencc Jun 26, 2025
56f23e5
force not ci in tests
kaizencc Jun 26, 2025
079bbc5
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jun 26, 2025
8e43ba7
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jun 26, 2025
9dd1580
refactors from friday are in a working state
kaizencc Jun 30, 2025
55a4a73
add cdk library version
kaizencc Jun 30, 2025
1041bf8
collect telemetry
kaizencc Jun 30, 2025
635d6b7
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jun 30, 2025
18d9349
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jul 1, 2025
1811c9a
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jul 1, 2025
ea4720c
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jul 2, 2025
b9c0d6d
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jul 2, 2025
46cc2c8
remove circular dependencies
kaizencc Jul 2, 2025
76a19ff
diff tests need to not depend on message order
kaizencc Jul 2, 2025
2433e3e
update type registry name
kaizencc Jul 2, 2025
b30d188
can collect telemetry
kaizencc Jul 2, 2025
fe47576
callback function on crtl-c
kaizencc Jul 2, 2025
4bf3be9
Merge branch 'main' into conroy/basic-telemetry-client
kaizencc Jul 2, 2025
359a442
telemetry interface includes flush
kaizencc Jul 2, 2025
c20ad8b
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jul 2, 2025
54109d6
rename
kaizencc Jul 2, 2025
334b9dd
lint
kaizencc Jul 2, 2025
39152d3
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jul 2, 2025
fbd6dd6
small fixes
kaizencc Jul 2, 2025
70fa3e3
minor enhancements
kaizencc Jul 2, 2025
e02a283
wip
kaizencc Jul 2, 2025
25dec4a
pr feedback
kaizencc Jul 2, 2025
5628ed9
trace
kaizencc Jul 2, 2025
cc8c6dd
pr feedback
kaizencc Jul 2, 2025
9433b36
tests for collect-telemetry
kaizencc Jul 2, 2025
72c862e
lint
kaizencc Jul 2, 2025
d3c745c
test for installation ids
kaizencc Jul 2, 2025
da64d2b
library-version tests
kaizencc Jul 2, 2025
b007f0e
library version tests
kaizencc Jul 2, 2025
9caf42e
refactor telemetry session
kaizencc Jul 3, 2025
ad0e6cd
minors
kaizencc Jul 3, 2025
c7d365a
renames
kaizencc Jul 3, 2025
fb85dfa
Merge branch 'conroy/basic-telemetry-client' into conroy/list
kaizencc Jul 3, 2025
e139111
Merge branch 'main' into conroy/list
kaizencc Jul 3, 2025
27ea7e6
fix merge
kaizencc Jul 3, 2025
f74bb90
small updates
kaizencc Jul 3, 2025
d79756f
fix stupid await
kaizencc Jul 3, 2025
80fad80
sanitize tests
kaizencc Jul 3, 2025
e087b50
accountid test
kaizencc Jul 3, 2025
66bb8c1
region fetcher tests
kaizencc Jul 3, 2025
64b5fa2
fix tests from merge
kaizencc Jul 7, 2025
b5d0f32
fix build
kaizencc Jul 7, 2025
b2b56d3
Merge branch 'main' into conroy/list
kaizencc Jul 7, 2025
1adc88e
session unit tests
kaizencc Jul 7, 2025
541eeb4
Update packages/aws-cdk/test/commands/diff.test.ts
kaizencc Jul 7, 2025
59aecf1
iohost unit tests
kaizencc Jul 7, 2025
af1bbbb
Update packages/aws-cdk/lib/init-templates/.init-version.json
kaizencc Jul 7, 2025
6a86ee3
Update .recommended-feature-flags.json
kaizencc Jul 7, 2025
6427847
Update .recommended-feature-flags.json
kaizencc Jul 7, 2025
effed08
s
kaizencc Jul 7, 2025
27fa7de
fix
kaizencc Jul 8, 2025
75b6490
Merge branch 'main' into conroy/list
kaizencc Jul 8, 2025
9dc5a68
update test
kaizencc Jul 8, 2025
8341898
Apply suggestions from code review
kaizencc Jul 8, 2025
e783eb2
ci file
kaizencc Jul 9, 2025
4fad6c6
Merge branch 'main' into conroy/list
kaizencc Jul 9, 2025
31f5a92
refactors
kaizencc Jul 9, 2025
670a289
update-test
kaizencc Jul 10, 2025
8ea3e65
merge
kaizencc Jul 10, 2025
0a15701
delete previously deleted file
kaizencc Jul 10, 2025
d193044
fix merge
kaizencc Jul 10, 2025
c694d99
telemetry to file
kaizencc Jul 10, 2025
2b51b13
update
kaizencc Jul 10, 2025
d17faf7
add cli integ test for telemetry file
kaizencc Jul 10, 2025
1c94d1b
Merge branch 'main' into conroy/list
kaizencc Jul 11, 2025
1e138ac
update version and fix tests
kaizencc Jul 11, 2025
3b0917d
chore: self mutation
invalid-email-address Jul 11, 2025
956aa9f
Merge branch 'main' into conroy/list
kaizencc Jul 11, 2025
cfc070d
Merge branch 'main' into conroy/list
kaizencc Jul 11, 2025
3f850e0
integ test for failure
kaizencc Jul 11, 2025
2edd715
chore: self mutation
invalid-email-address Jul 11, 2025
045821e
chore(cli): allow valid enum values in telemetry
kaizencc Jul 11, 2025
bc26acb
chore: self mutation
invalid-email-address Jul 11, 2025
e317acd
Merge branch 'main' into conroy/list
kaizencc Jul 16, 2025
dc98ae5
Merge branch 'conroy/list' into conroy/sanitize-enums
kaizencc Jul 17, 2025
73b8ce1
Merge branch 'main' into conroy/sanitize-enums
kaizencc Jul 23, 2025
10415ac
fix merge
kaizencc Jul 23, 2025
1fda764
Update packages/aws-cdk/lib/cli/telemetry/sanitation.ts
kaizencc Jul 23, 2025
168ec82
Merge branch 'main' into conroy/sanitize-enums
kaizencc Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'cdk synth with telemetry and validation error leads to invoke failure',
withDefaultFixture(async (fixture) => {
const telemetryFile = path.join(fixture.integTestDir, 'telemetry.json');
const output = await fixture.cdk(['synth', '--unstable=telemetry', `--telemetry-file=${telemetryFile}`], {
allowErrExit: true,
modEnv: {
INTEG_STACK_SET: 'stage-with-errors',
},
});

expect(output).toContain('This is an error');

const json = fs.readJSONSync(telemetryFile);
expect(json).toEqual([
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth'],
parameters: {
verbose: 1,
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
['ignore-errors']: false,
json: false,
debug: false,
staging: true,
notices: true,
['no-color']: false,
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
config: {
bags: true,
fileNames: true,
},
}),
state: 'SUCCEEDED',
eventType: 'SYNTH',
}),
identifiers: expect.objectContaining({
installationId: expect.anything(),
sessionId: expect.anything(),
telemetryVersion: '1.0',
cdkCliVersion: expect.anything(),
region: expect.anything(),
eventId: expect.stringContaining(':1'),
timestamp: expect.anything(),
}),
environment: {
ci: expect.anything(),
os: {
platform: expect.anything(),
release: expect.anything(),
},
nodeVersion: expect.anything(),
},
project: {},
duration: {
total: expect.anything(),
},
}),
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth'],
parameters: {
verbose: 1,
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
['ignore-errors']: false,
json: false,
debug: false,
staging: true,
notices: true,
['no-color']: false,
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
config: {
bags: true,
fileNames: true,
},
}),
state: 'FAILED',
eventType: 'INVOKE',
}),
identifiers: expect.objectContaining({
installationId: expect.anything(),
sessionId: expect.anything(),
telemetryVersion: '1.0',
cdkCliVersion: expect.anything(),
region: expect.anything(),
eventId: expect.stringContaining(':2'),
timestamp: expect.anything(),
}),
environment: {
ci: expect.anything(),
os: {
platform: expect.anything(),
release: expect.anything(),
},
nodeVersion: expect.anything(),
},
project: {},
duration: {
total: expect.anything(),
},
error: {
name: 'AssemblyError',
},
}),
]);
fs.unlinkSync(telemetryFile);
}),
);

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'cdk synth with telemetry data',
withDefaultFixture(async (fixture) => {
const telemetryFile = path.join(fixture.integTestDir, 'telemetry.json');
await fixture.cdk(['synth', fixture.fullStackName('test-1'), '--unstable=telemetry', `--telemetry-file=${telemetryFile}`]);
const json = fs.readJSONSync(telemetryFile);
expect(json).toEqual([
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth', '$STACK1'],
parameters: {
verbose: 1,
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
['ignore-errors']: false,
json: false,
debug: false,
staging: true,
notices: true,
['no-color']: false,
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
config: {
bags: true,
fileNames: true,
},
}),
state: 'SUCCEEDED',
eventType: 'SYNTH',
}),
identifiers: expect.objectContaining({
installationId: expect.anything(),
sessionId: expect.anything(),
telemetryVersion: '1.0',
cdkCliVersion: expect.anything(),
region: expect.anything(),
eventId: expect.stringContaining(':1'),
timestamp: expect.anything(),
}),
environment: {
ci: expect.anything(),
os: {
platform: expect.anything(),
release: expect.anything(),
},
nodeVersion: expect.anything(),
},
project: {},
duration: {
total: expect.anything(),
},
}),
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth', '$STACK1'],
parameters: {
verbose: 1,
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
['ignore-errors']: false,
json: false,
debug: false,
staging: true,
notices: true,
['no-color']: false,
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
config: {
bags: true,
fileNames: true,
},
}),
state: 'SUCCEEDED',
eventType: 'INVOKE',
}),
identifiers: expect.objectContaining({
installationId: expect.anything(),
sessionId: expect.anything(),
telemetryVersion: '1.0',
cdkCliVersion: expect.anything(),
region: expect.anything(),
eventId: expect.stringContaining(':2'),
timestamp: expect.anything(),
}),
environment: {
ci: expect.anything(),
os: {
platform: expect.anything(),
release: expect.anything(),
},
nodeVersion: expect.anything(),
},
project: {},
duration: {
total: expect.anything(),
},
}),
]);
fs.unlinkSync(telemetryFile);
}),
);

9 changes: 9 additions & 0 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,15 @@ cdk gc --unstable=gc
The command will fail if `--unstable=gc` is not passed in, which acknowledges that the user
is aware of the caveats in place for the feature.

### `telemetry-file`

Send your telemetry data to a local file (note that `--telemetry-file` is unstable, and must
be passed in conjunction with `--unstable=telemetry`).

```bash
cdk list --telemetry-file=my/file/path --unstable=telemetry
```

## Notices

CDK Notices are important messages regarding security vulnerabilities, regressions, and usage of unsupported
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export async function makeConfig(): Promise<CliConfig> {
'no-color': { type: 'boolean', desc: 'Removes colors and other style from console output', default: false },
'ci': { type: 'boolean', desc: 'Force CI detection. If CI=true then logs will be sent to stdout instead of stderr', default: YARGS_HELPERS.isCI() },
'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: [] },
'telemetry-file': { type: 'string', desc: 'Send telemetry data to a local file.', default: undefined },
},
commands: {
'list': {
Expand Down
4 changes: 4 additions & 0 deletions packages/aws-cdk/lib/cli/cli-type-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
"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": []
},
"telemetry-file": {
"type": "string",
"desc": "Send telemetry data to a local file."
}
},
"commands": {
Expand Down
26 changes: 25 additions & 1 deletion packages/aws-cdk/lib/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { getMigrateScanType } from '../commands/migrate';
import { execProgram, CloudExecutable } from '../cxapp';
import type { StackSelector, Synthesizer } from '../cxapp';
import { ProxyAgentProvider } from './proxy-agent';
import type { ErrorDetails } from './telemetry/schema';
import { isDeveloperBuildVersion, versionWithBuild, versionNumber } from './version';

if (!process.stdout.isTTY) {
Expand Down Expand Up @@ -97,6 +98,12 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
caBundlePath: configuration.settings.get(['caBundlePath']),
});

try {
await ioHost.startTelemetry(argv, configuration.context);
} catch (e: any) {
await ioHost.asIoHelper().defaults.trace(`Telemetry instantiation failed: ${e.message}`);
}

const shouldDisplayNotices = configuration.settings.get(['notices']);
// Notices either go to stderr, or nowhere
ioHost.noticesDestination = shouldDisplayNotices ? 'stderr' : 'drop';
Expand Down Expand Up @@ -125,6 +132,8 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
pluginHost: GLOBAL_PLUGIN_HOST,
}, configuration.settings.get(['profile']));

await ioHost.telemetry?.attachRegion(sdkProvider.defaultRegion);

let outDirLock: IReadLock | undefined;
const cloudExecutable = new CloudExecutable({
configuration,
Expand Down Expand Up @@ -195,6 +204,10 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
throw new ToolkitError('You must either specify a list of Stacks or the `--all` argument');
}

if (args['telemetry-file'] && !configuration.settings.get(['unstable']).includes('telemetry')) {
throw new ToolkitError('Unstable feature use: \'telemetry-file\' is unstable. It must be opted in via \'--unstable\', e.g. \'cdk deploy --unstable=telemetry --telemetry-file=my/file/path\'');
}

args.STACKS = args.STACKS ?? (args.STACK ? [args.STACK] : []);
args.ENVIRONMENTS = args.ENVIRONMENTS ?? [];

Expand Down Expand Up @@ -645,17 +658,28 @@ function determineHotswapMode(hotswap?: boolean, hotswapFallback?: boolean, watc

/* c8 ignore start */ // we never call this in unit tests
export function cli(args: string[] = process.argv.slice(2)) {
let error: ErrorDetails | undefined;
exec(args)
.then(async (value) => {
if (typeof value === 'number') {
process.exitCode = value;
}
})
.catch((err) => {
.catch(async (err) => {
// Log the stack trace if we're on a developer workstation. Otherwise this will be into a minified
// file and the printed code line and stack trace are huge and useless.
prettyPrintError(err, isDeveloperBuildVersion());
error = err;
process.exitCode = 1;
})
.finally(async () => {
if (!error && process.exitCode === 1) {
// The existence of an error determines if telemetry is successful or not so we create a
// dummy error in the event that exit code is 1 but no error is thrown
error = { name: 'ExitCode1Error' };
}

await CliIoHost.get()?.telemetry?.end(error);
});
}
/* c8 ignore stop */
2 changes: 2 additions & 0 deletions packages/aws-cdk/lib/cli/convert-to-user-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function convertYargsToUserInput(args: any): UserInput {
noColor: args.noColor,
ci: args.ci,
unstable: args.unstable,
telemetryFile: args.telemetryFile,
};
let commandOptions;
switch (args._[0] as Command) {
Expand Down Expand Up @@ -324,6 +325,7 @@ export function convertConfigToUserInput(config: any): UserInput {
noColor: config.noColor,
ci: config.ci,
unstable: config.unstable,
telemetryFile: config.telemetryFile,
};
const listOptions = {
long: config.list?.long,
Expand Down
Loading