Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,12 @@ export class TestFixture extends ShellHelper {

await this.cli.makeCliAvailable();

return this.shell(['cdk', ...(verbose ? ['-v'] : []), ...args], {
return this.shell([
'cdk',
...(verbose ? ['-v'] : []),
...args,
...(options?.options ?? []),
], {
...options,
modEnv: {
...this.cdkShellEnv(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { integTest, withDefaultFixture } from '../../../lib';

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

integTest(
'CLI Telemetry --disable does not send to endpoint',
withDefaultFixture(async (fixture) => {
const output = await fixture.cdk(['cli-telemetry', '--disable'], { options: ['-vvv'] });

// Check the trace that telemetry was not executed successfully
expect(output).not.toContain('Telemetry Sent Successfully');

// Check the trace that endpoint telemetry was never connected
expect(output).toContain('Endpoint Telemetry NOT connected');
}),
);
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ integTest(
const telemetryFile = path.join(fixture.integTestDir, 'telemetry.json');

// Deploy stack while collecting telemetry
await fixture.cdkDeploy('test-1', {
const deployOutput = await fixture.cdkDeploy('test-1', {
telemetryFile,
options: ['-vvv'], // force trace mode
});

// Check the trace that telemetry was executed successfully
expect(deployOutput).toContain('Telemetry Sent Successfully');

const json = fs.readJSONSync(telemetryFile);
expect(json).toEqual([
expect.objectContaining({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ integTest(
modEnv: {
INTEG_STACK_SET: 'stage-with-errors',
},
options: ['-vvv'], // force trace mode
});

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

// Check the trace that telemetry was executed successfully despite error in synth
expect(output).toContain('Telemetry Sent Successfully');

const json = fs.readJSONSync(telemetryFile);
expect(json).toEqual([
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth'],
parameters: {
verbose: 1,
parameters: expect.objectContaining({
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
Expand All @@ -37,7 +40,7 @@ integTest(
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
}),
config: {
context: {},
},
Expand Down Expand Up @@ -72,8 +75,7 @@ integTest(
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth'],
parameters: {
verbose: 1,
parameters: expect.objectContaining({
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
Expand All @@ -86,7 +88,7 @@ integTest(
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
}),
config: {
context: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ integTest(
'cdk synth with telemetry data',
withDefaultFixture(async (fixture) => {
const telemetryFile = path.join(fixture.integTestDir, `telemetry-${Date.now()}.json`);
await fixture.cdk(['synth', fixture.fullStackName('test-1'), '--unstable=telemetry', `--telemetry-file=${telemetryFile}`]);

const synthOutput = await fixture.cdk(
['synth', fixture.fullStackName('test-1'), '--unstable=telemetry', `--telemetry-file=${telemetryFile}`],
{ options: ['-vvv'] }, // force trace mode
);

// Check the trace that telemetry was executed successfully
expect(synthOutput).toContain('Telemetry Sent Successfully');

const json = fs.readJSONSync(telemetryFile);
expect(json).toEqual([
expect.objectContaining({
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth', '$STACKS_1'],
parameters: {
verbose: 1,
parameters: expect.objectContaining({
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
Expand All @@ -29,7 +36,7 @@ integTest(
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
}),
config: {
context: {},
},
Expand Down Expand Up @@ -65,8 +72,7 @@ integTest(
event: expect.objectContaining({
command: expect.objectContaining({
path: ['synth', '$STACKS_1'],
parameters: {
verbose: 1,
parameters: expect.objectContaining({
unstable: '<redacted>',
['telemetry-file']: '<redacted>',
lookups: true,
Expand All @@ -79,7 +85,7 @@ integTest(
ci: expect.anything(), // changes based on where this is called
validation: true,
quiet: false,
},
}),
config: {
context: {},
},
Expand Down
52 changes: 34 additions & 18 deletions packages/aws-cdk/lib/cli/io-host/cli-io-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import * as promptly from 'promptly';
import type { IoHelper, ActivityPrinterProps, IActivityPrinter } from '../../../lib/api-private';
import { asIoHelper, IO, isMessageRelevantForLevel, CurrentActivityPrinter, HistoryActivityPrinter } from '../../../lib/api-private';
import { StackActivityProgress } from '../../commands/deploy';
import { canCollectTelemetry } from '../telemetry/collect-telemetry';
import type { EventResult } from '../telemetry/messages';
import { CLI_PRIVATE_IO, CLI_TELEMETRY_CODES } from '../telemetry/messages';
import type { EventType } from '../telemetry/schema';
import { TelemetrySession } from '../telemetry/session';
import { EndpointTelemetrySink } from '../telemetry/sink/endpoint-sink';
import { FileTelemetrySink } from '../telemetry/sink/file-sink';
import { Funnel } from '../telemetry/sink/funnel';
import type { ITelemetrySink } from '../telemetry/sink/sink-interface';
import { isCI } from '../util/ci';

export type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest };
Expand Down Expand Up @@ -168,32 +172,44 @@ export class CliIoHost implements IIoHost {
this.logLevel = props.logLevel ?? 'info';
this.isCI = props.isCI ?? isCI();
this.requireDeployApproval = props.requireDeployApproval ?? RequireApproval.BROADENING;

this.stackProgress = props.stackProgress ?? StackActivityProgress.BAR;
}

public async startTelemetry(args: any, context: Context, _proxyAgent?: Agent) {
let sink;
public async startTelemetry(args: any, context: Context, proxyAgent?: Agent) {
let sinks: ITelemetrySink[] = [];
const telemetryFilePath = args['telemetry-file'];
if (telemetryFilePath) {
sink = new FileTelemetrySink({
ioHost: this,
logFilePath: telemetryFilePath,
});
try {
sinks.push(new FileTelemetrySink({
ioHost: this,
logFilePath: telemetryFilePath,
}));
await this.asIoHelper().defaults.trace('File Telemetry connected');
} catch (e: any) {
await this.asIoHelper().defaults.trace(`File Telemetry instantiation failed: ${e.message}`);
}
}
// TODO: uncomment this at launch
// if (canCollectTelemetry(args, context)) {
// sink = new EndpointTelemetrySink({
// ioHost: this,
// agent: proxyAgent,
// endpoint: '', // TODO: add endpoint
// });
// }

if (sink) {

const telemetryEndpoint = process.env.TELEMETRY_ENDPOINT; // TODO: replace with endpoint at launch
if (canCollectTelemetry(args, context) && telemetryEndpoint) {
try {
sinks.push(new EndpointTelemetrySink({
ioHost: this,
agent: proxyAgent,
endpoint: telemetryEndpoint,
}));
await this.asIoHelper().defaults.trace('Endpoint Telemetry connected');
} catch (e: any) {
await this.asIoHelper().defaults.trace(`Endpoint Telemetry instantiation failed: ${e.message}`);
}
} else {
await this.asIoHelper().defaults.trace('Endpoint Telemetry NOT connected');
}

if (sinks.length > 0) {
this.telemetry = new TelemetrySession({
ioHost: this,
client: sink,
client: new Funnel({ sinks }),
arguments: args,
context: context,
});
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk/lib/cli/telemetry/collect-telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import type { Context } from '../../api/context';
export function canCollectTelemetry(args: any, context: Context): boolean {
if ((['true', '1'].includes(process.env.CDK_DISABLE_CLI_TELEMETRY ?? '')) ||
['false', false].includes(context.get('cli-telemetry')) ||
(args['version-reporting'] !== undefined && !args['version-reporting'])) /* aliased with telemetry option */ {
(args['version-reporting'] !== undefined && !args['version-reporting']) || /* aliased with telemetry option */
(Array.isArray(args._) && args._.includes('cli-telemetry') && args.disable)) /* special case for `cdk cli-telemetry --disable` */ {
return false;
}

Expand Down
8 changes: 7 additions & 1 deletion packages/aws-cdk/lib/cli/telemetry/sink/endpoint-sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export class EndpointTelemetrySink implements ITelemetrySink {

public constructor(props: EndpointTelemetrySinkProps) {
this.endpoint = parse(props.endpoint);

if (!this.endpoint.hostname || !this.endpoint.pathname) {
throw new ToolkitError(`Telemetry Endpoint malformed. Received hostname: ${this.endpoint.hostname}, pathname: ${this.endpoint.pathname}`);
}

this.ioHelper = IoHelper.fromActionAwareIoHost(props.ioHost);
this.agent = props.agent;

Expand Down Expand Up @@ -78,7 +83,7 @@ export class EndpointTelemetrySink implements ITelemetrySink {
}
} catch (e: any) {
// Never throw errors, just log them via ioHost
await this.ioHelper.defaults.trace(`Failed to add telemetry event: ${e.message}`);
await this.ioHelper.defaults.trace(`Failed to send telemetry event: ${e.message}`);
}
}

Expand All @@ -94,6 +99,7 @@ export class EndpointTelemetrySink implements ITelemetrySink {

// Successfully posted
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
await this.ioHelper.defaults.trace('Telemetry Sent Successfully');
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/aws-cdk/test/cli/telemetry/collect-telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ describe(canCollectTelemetry, () => {
test('returns false if no-version-reporting is set', async () => {
expect(canCollectTelemetry({ 'version-reporting': false }, context)).toBeFalsy();
});

test('special case for cli-telemetry --disable', async () => {
expect(canCollectTelemetry({ _: ['cli-telemetry'], disable: true }, context)).toBeFalsy();
});
});