-
Notifications
You must be signed in to change notification settings - Fork 127
feat(cli): emit telemetry for the validate action #1864
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
base: main
Are you sure you want to change the base?
Changes from all commits
5b3b764
24cdbdd
c718b41
174c904
7acbb8e
228bf7e
110fc03
a76b0ac
74b1511
9d1cb98
84d61c3
a238dbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import * as path from 'path'; | ||
| import * as fs from 'fs-extra'; | ||
| import { integTest, withSpecificFixture } from '../../lib'; | ||
|
|
||
| integTest( | ||
| 'cdk validate records offlineWouldFailDeploy on the SYNTH telemetry event', | ||
| withSpecificFixture('validate-app', async (fixture) => { | ||
| const telemetryFile = path.join(fixture.integTestDir, `telemetry-validate-${Date.now()}.json`); | ||
|
|
||
| // --no-online keeps the run deterministic; offline validation still runs | ||
| // during synthesis, and its deploy-blocking outcome is recorded on the | ||
| // SYNTH event as offlineWouldFailDeploy (visible for synth/deploy/validate). | ||
| await fixture.cdk( | ||
| ['--unstable=validate', 'validate', fixture.fullStackName('validate'), '--no-online', `--telemetry-file=${telemetryFile}`], | ||
| { | ||
| verboseLevel: 3, // trace mode | ||
| allowErrExit: true, // violations make validate exit non-zero | ||
| }, | ||
| ); | ||
|
|
||
| // Assert against the local telemetry file: the CLI dispatches the batch to | ||
| // the endpoint from a background process, so its own output only confirms | ||
| // dispatch, not delivery. Real endpoint delivery is covered separately by | ||
| // cdk-telemetry-reaches-the-endpoint.integtest.ts. | ||
| const json = fs.readJSONSync(telemetryFile); | ||
| const synthEvent = json.find((e: any) => e.event?.eventType === 'SYNTH'); | ||
| expect(synthEvent).toBeDefined(); | ||
|
|
||
| // The app's single S3 bucket makes SecurityPlugin report a fatal/error | ||
| // violation (what would have failed a deploy) and a warning-severity | ||
| // violation (counted by offlineValidationWarnings, separate from annotation warnings). | ||
| expect(synthEvent.counters).toEqual( | ||
| expect.objectContaining({ | ||
| offlineWouldFailDeploy: 1, | ||
| offlineValidationWarnings: 1, | ||
| }), | ||
| ); | ||
|
|
||
| fs.unlinkSync(telemetryFile); | ||
| }), | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import type { PluginReportJson } from '@aws-cdk/cloud-assembly-schema'; | ||
| import type { IMessageSpan } from '../../api/io/private/span'; | ||
| import { sum } from '../../util'; | ||
|
|
||
| /** | ||
| * Add counters describing the online validation outcome to the given span | ||
| * | ||
| * `onlineViolations` counts the violations online validation reported, and | ||
| * `online:stacksIncomplete` records how many selected stacks could not be | ||
| * validated (0 on a clean run; every selected stack when the engine could not | ||
| * run at all, e.g. a setup failure before validation started). Both are always | ||
| * emitted so consumers can distinguish zero from missing data. | ||
| */ | ||
| export function countOnlineValidationResults(span: IMessageSpan<any>, onlineReports: PluginReportJson[] | undefined, incompleteStacks: number) { | ||
| span.incCounter('onlineViolations', sum((onlineReports ?? []).map((r) => r.violations.length))); | ||
|
Copilot marked this conversation as resolved.
|
||
| span.incCounter('online:stacksIncomplete', incompleteStacks); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,26 +88,36 @@ export async function throwIfValidationFailures( | |
| const result: ValidateResult = { conclusion, pluginReports }; | ||
| await ioHelper.notify(hostMessageFromValidation(process.cwd(), result)); | ||
|
|
||
| if (!wouldFailDeploy(pluginReports, failAt)) { | ||
| return; | ||
| } | ||
|
|
||
| if (failAt === 'warn') { | ||
| const error = AssemblyError.withStacks('Synthesis finished with warnings (--strict mode)', stacks.stackArtifacts); | ||
| error.attachSynthesisErrorCode('StrictAnnotationWarnings'); | ||
| throw error; | ||
| } | ||
|
Comment on lines
+91
to
+99
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not necessarily true. Just because we WOULD fail if there were warnings, doesn't mean there WERE warnings. I also don't like Don't combine 2 bits of information here ( // If any of the variants had associated data here we would do a union of objects with a 'type' field, for this case strings are sufficient
type ValidationAction = 'fail-errors' | 'fail-strict-warnings' | 'continue';
function decideValidationAction(...): ValidationAction { ... }
switch (decideValidationAction(pluginReports, failAt) {
case 'fail-errors':
// ...
case 'fail-strict-warnings':
// ...
case 'continue':
break;
}Type this with your own hands, don't let AI do it. Get this pattern ingrained in your mind. |
||
|
|
||
| const error = AssemblyError.withStacks('Synthesis finished with errors', stacks.stackArtifacts); | ||
| error.attachSynthesisErrorCode('AnnotationErrors'); | ||
| throw error; | ||
| } | ||
|
|
||
| /** | ||
| * Whether the given validation reports make a deploy-like action fail at the given severity threshold | ||
| * | ||
| * This is the exact predicate applied by `throwIfValidationFailures`. | ||
| */ | ||
| export function wouldFailDeploy(pluginReports: PluginReportJson[], failAt: MinimumSeverity): boolean { | ||
| switch (failAt) { | ||
| case 'error': | ||
| if (conclusion === 'failure') { | ||
| const error = AssemblyError.withStacks('Synthesis finished with errors', stacks.stackArtifacts); | ||
| error.attachSynthesisErrorCode('AnnotationErrors'); | ||
| throw error; | ||
| } | ||
| break; | ||
| return combineConclusions(pluginReports) === 'failure'; | ||
| case 'warn': | ||
| // if we're failing at 'warn', then both warnings and errors cause failure, so the initial conclusion is correct | ||
| if (conclusion === 'failure' || hasWarnings(pluginReports)) { | ||
| const error = AssemblyError.withStacks('Synthesis finished with warnings (--strict mode)', stacks.stackArtifacts); | ||
| error.attachSynthesisErrorCode('StrictAnnotationWarnings'); | ||
| throw error; | ||
| } | ||
|
|
||
| break; | ||
| // if we're failing at 'warn', then both warnings and errors cause failure | ||
| return combineConclusions(pluginReports) === 'failure' || hasWarnings(pluginReports); | ||
| case 'none': | ||
| // if we're not failing at all, then the conclusion is always success | ||
| break; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? I'm pretty sure we have a counters facility built-in to the whole messages system already?