Skip to content

Commit aecb4ce

Browse files
authored
Merge pull request #14113 from aws-amplify/telemetry-updates
Fix: update telemetry collection
2 parents ef4bf37 + 112ab72 commit aecb4ce

File tree

10 files changed

+159
-7
lines changed

10 files changed

+159
-7
lines changed

.github/workflows/build-test-mac.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010

1111
jobs:
1212
build-and-test:
13-
runs-on: macos-latest-xl
13+
runs-on: macos-latest-xlarge
1414
steps:
1515
- name: Checkout Code
1616
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3

packages/amplify-category-auth/src/__tests__/commands/update.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,11 @@ describe('auth update:', () => {
120120
});
121121
});
122122
it('update run method should detect presence of dependent resource and print a message', async () => {
123+
const originalExitCode = process.exitCode;
123124
await update.run(mockContext);
124125
expect(printer.info).toBeCalledWith(messages.dependenciesExists);
126+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
127+
process.exitCode = originalExitCode;
125128
});
126129
it('serviceSelectionPrompt should still be called even when warning displayed for existing resource', async () => {
127130
await update.run(mockContext);

packages/amplify-cli/src/__tests__/amplify-exception-handler.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ describe('test exception handler', () => {
3535
init(contextMock);
3636
});
3737
it('error handler should call usageData emitError', async () => {
38+
const originalExitCode = process.exitCode;
3839
const amplifyError = new AmplifyError('NotImplementedError', {
3940
message: 'Test Not implemented',
4041
resolution: 'Test Not implemented',
4142
});
4243
await handleException(amplifyError);
4344

4445
expect(contextMock.usageData.emitError).toHaveBeenCalledWith(amplifyError);
46+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
47+
process.exitCode = originalExitCode;
4548
});
4649

4750
it('error handler should send error report', async () => {
51+
const originalExitCode = process.exitCode;
4852
const amplifyError = new AmplifyError('NotImplementedError', {
4953
message: 'Test Not implemented',
5054
resolution: 'Test Not implemented',
@@ -53,9 +57,12 @@ describe('test exception handler', () => {
5357

5458
expect(reportErrorMock).toHaveBeenCalledWith(contextMock, amplifyError);
5559
expect(processExit).toHaveBeenCalledWith(1);
60+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
61+
process.exitCode = originalExitCode;
5662
});
5763

5864
it('error handler should print error', async () => {
65+
const originalExitCode = process.exitCode;
5966
const amplifyError = new AmplifyError('NotImplementedError', {
6067
message: 'Test Not implemented(message)',
6168
details: 'Test Not implemented(details)',
@@ -69,9 +76,12 @@ describe('test exception handler', () => {
6976
expect(printerMock.info).toHaveBeenNthCalledWith(1, `Resolution: ${amplifyError.resolution}`);
7077
expect(printerMock.info).toHaveBeenLastCalledWith('Learn more at: https://docs.amplify.aws/cli/project/troubleshooting/');
7178
expect(printerMock.debug).toHaveBeenCalledWith(amplifyError.stack);
79+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
80+
process.exitCode = originalExitCode;
7281
});
7382

7483
it('error handler should handle encountered errors gracefully', async () => {
84+
const originalExitCode = process.exitCode;
7585
const amplifyError = new AmplifyError('NotImplementedError', {
7686
message: 'Test Not implemented(message)',
7787
details: 'Test Not implemented(details)',
@@ -87,9 +97,12 @@ describe('test exception handler', () => {
8797
expect(printerMock.info).toHaveBeenLastCalledWith('Learn more at: https://docs.amplify.aws/cli/project/troubleshooting/');
8898
expect(printerMock.debug).toHaveBeenCalledWith(amplifyError.stack);
8999
expect(printerMock.error).toHaveBeenCalledWith('Failed to report error: MockTestError');
100+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
101+
process.exitCode = originalExitCode;
90102
});
91103

92104
it('error handler should handle nodejs file permission errors for log files', async () => {
105+
const originalExitCode = process.exitCode;
93106
const code = 'EACCES';
94107
const path = '/user/name/.amplify/path/to/log';
95108
const nodeJSError = new Error(`permission denied, open ${path}`) as NodeJS.ErrnoException;
@@ -102,9 +115,12 @@ describe('test exception handler', () => {
102115
new AmplifyError('FileSystemPermissionsError', { message: `permission denied, open ${path}` }),
103116
);
104117
expect(printerMock.info).toHaveBeenCalledWith(`Resolution: Try running 'sudo chown -R $(whoami):$(id -gn) ~/.amplify' to fix this`);
118+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
119+
process.exitCode = originalExitCode;
105120
});
106121

107122
it('error handler should handle nodejs file permission errors for ~/.aws/amplify files', async () => {
123+
const originalExitCode = process.exitCode;
108124
const code = 'EACCES';
109125
const path = '/user/name/.aws/amplify/someFile';
110126
const nodeJSError = new Error(`permission denied, open ${path}`) as NodeJS.ErrnoException;
@@ -117,9 +133,12 @@ describe('test exception handler', () => {
117133
new AmplifyError('FileSystemPermissionsError', { message: `permission denied, open ${path}` }),
118134
);
119135
expect(printerMock.info).toHaveBeenCalledWith(`Resolution: Try running 'sudo chown -R $(whoami):$(id -gn) ~/.aws/amplify' to fix this`);
136+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
137+
process.exitCode = originalExitCode;
120138
});
121139

122140
it('error handler should handle nodejs file permission errors for amplify project', async () => {
141+
const originalExitCode = process.exitCode;
123142
const code = 'EACCES';
124143
const path = '/user/name/workspace/amplify/path/to/manifest';
125144
const nodeJSError = new Error(`permission denied, open ${path}`) as NodeJS.ErrnoException;
@@ -135,9 +154,12 @@ describe('test exception handler', () => {
135154
expect(printerMock.info).toHaveBeenCalledWith(
136155
`Resolution: Try running 'sudo chown -R $(whoami):$(id -gn) <your amplify app directory>' to fix this`,
137156
);
157+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
158+
process.exitCode = originalExitCode;
138159
});
139160

140161
it('error handler should handle nodejs file permission errors for other files', async () => {
162+
const originalExitCode = process.exitCode;
141163
const code = 'EACCES';
142164
const path = '/usr/name/.aws/config';
143165
const nodeJSError = new Error(`permission denied, open ${path}`) as NodeJS.ErrnoException;
@@ -151,6 +173,8 @@ describe('test exception handler', () => {
151173
);
152174
// different resolution based on the file path compared to last test
153175
expect(printerMock.info).toHaveBeenCalledWith(`Resolution: Try running 'sudo chown -R $(whoami):$(id -gn) ${path}' to fix this`);
176+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
177+
process.exitCode = originalExitCode;
154178
});
155179
});
156180

packages/amplify-cli/src/__tests__/flow-report.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { v4 as uuid } from 'uuid';
1616
import { Redactor } from '@aws-amplify/amplify-cli-logger';
1717
import { CLIFlowReport } from '../domain/amplify-usageData/FlowReport';
18+
import { homedir } from 'os';
1819

1920
describe('Test FlowReport Logging', () => {
2021
beforeAll(() => {});
@@ -103,6 +104,20 @@ describe('Test FlowReport Logging', () => {
103104
expect(flowReport.optionFlowData[0].input).toContain(input.serviceConfiguration.mapStyle);
104105
expect(flowReport.optionFlowData[0].input).toEqual(Redactor(inputString));
105106
});
107+
108+
it('runtime does not contain home directory info', () => {
109+
const flowReport = new CLIFlowReport();
110+
flowReport.setInput(mockInputs.headlessInput('auth', 'add'));
111+
112+
expect(flowReport.runtime).not.toContain(homedir());
113+
});
114+
115+
it('executable does not contain home directory info', () => {
116+
const flowReport = new CLIFlowReport();
117+
flowReport.setInput(mockInputs.headlessInput('auth', 'add'));
118+
119+
expect(flowReport.executable).not.toContain(homedir());
120+
});
106121
});
107122

108123
const redactValue = (key: string, value: unknown) => {
@@ -180,7 +195,7 @@ const mockInputs = {
180195
Geo: mockAddGeoInput,
181196
GraphQLAPI: mockAddAPIInput,
182197
headlessInput: (feature, command) => ({
183-
argv: [],
198+
argv: [`${homedir()}/.amplify/bin/amplify.exe`, `${homedir()}/amplify-cli/build/node_modules/@aws-amplify/cli-internal/bin/amplify`],
184199
plugin: feature,
185200
command,
186201
}),

packages/amplify-cli/src/__tests__/serializable-error.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,23 @@ describe('test serializabe error', () => {
5454
expect(serializableError.name).toBe('NotImplementedError');
5555
expect(serializableError.details).toBe('some error details with arn: <escaped ARN> and arn: <escaped ARN> and something else');
5656
});
57+
58+
it('test SerializableError has no stack arns in error message', () => {
59+
const testStack = 'amplify-someStackName-dev-u9910';
60+
const error = new AmplifyError('NotImplementedError', {
61+
message: `Could not initialize ${testStack}`,
62+
});
63+
64+
const serializableError = new SerializableError(error);
65+
expect(serializableError.message).toBe('Could not initialize <escaped stack>');
66+
});
67+
68+
it('test SerializeError error message has no home directories in file paths', () => {
69+
const error = new AmplifyError('FileSystemPermissionsError', {
70+
message: `Permission denied, open '${os.homedir()}/.amplify/logs/amplify-cli-10371230.log`,
71+
});
72+
73+
const serializableError = new SerializableError(error);
74+
expect(serializableError.message).not.toContain(os.homedir());
75+
});
5776
});

packages/amplify-cli/src/__tests__/test-aborting.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ describe('test SIGINT with execute', () => {
111111

112112
jest.mock('@aws-amplify/amplify-environment-parameters');
113113

114+
const originalExitCode = process.exitCode;
114115
setTimeout(() => {
115116
process.emit('SIGINT', 'SIGINT');
116117
process.exitCode = 2;
@@ -124,6 +125,8 @@ describe('test SIGINT with execute', () => {
124125
expect(mockContext.usageData.emitError).toHaveBeenCalledTimes(0);
125126
expect(mockContext.usageData.emitSuccess).toHaveBeenCalledTimes(0);
126127
expect(mockExit).toBeCalledWith(2);
128+
// Setting exitCode back to original, see https://github.com/jestjs/jest/issues/9324#issuecomment-1808090455
129+
process.exitCode = originalExitCode;
127130
}, 10000);
128131
});
129132

packages/amplify-cli/src/__tests__/usage-data.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,41 @@ describe('test usageData', () => {
6767
expect(a).toEqual(b);
6868
});
6969

70+
it('expects accountId to be a valid UUID', () => {
71+
const a = UsageData.Instance;
72+
const timeStamp = Date.now();
73+
const testAccountID = '123456789012';
74+
a.init(
75+
uuid.v4(),
76+
'',
77+
new CommandLineInput([]),
78+
testAccountID,
79+
{ editor: 'vscode', framework: 'react', frontend: 'javascript' } as unknown as ProjectSettings,
80+
timeStamp,
81+
);
82+
83+
expect(uuid.validate(a.getUsageDataPayload(null, 'SUCCESS').accountId)).toEqual(true);
84+
});
85+
86+
it('returns cached accountId on subsequent calls', () => {
87+
const a = UsageData.Instance;
88+
const timeStamp = Date.now();
89+
const testAccountID = '123456789012';
90+
a.init(
91+
uuid.v4(),
92+
'',
93+
new CommandLineInput([]),
94+
testAccountID,
95+
{ editor: 'vscode', framework: 'react', frontend: 'javascript' } as unknown as ProjectSettings,
96+
timeStamp,
97+
);
98+
99+
const accountId1 = a.getUsageDataPayload(null, 'SUCCESS').accountId;
100+
const accountId2 = a.getUsageDataPayload(null, 'SUCCESS').accountId;
101+
102+
expect(accountId1).toEqual(accountId2);
103+
});
104+
70105
it('records specified code path timer', async () => {
71106
const usageData = UsageData.Instance;
72107
usageData.startCodePathTimer(ManuallyTimedCodePath.PLUGIN_TIME);

packages/amplify-cli/src/domain/amplify-usageData/FlowReport.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TypeOptionFlowData,
1313
} from '@aws-amplify/amplify-cli-shared-interfaces';
1414
import { createHashedIdentifier } from '../../commands/helpers/encryption-helpers';
15+
import { homedir } from 'os';
1516

1617
/**
1718
* Store the data and sequence of events of CLI walkthrough
@@ -32,6 +33,11 @@ export class CLIFlowReport implements IFlowData {
3233
projectIdentifier?: string; // hash( ProjectName + Amplify App Id)
3334
envName?: string;
3435

36+
// (file:/+)? -> matches optional file url prefix
37+
// homedir() -> users home directory, replacing \ with /
38+
// [\\w.\\-_@\\\\/]+ -> matches nested directories and file name
39+
filePathRegex = new RegExp(`(file:/+)?${homedir().replaceAll('\\', '/')}[\\w.\\-_@\\\\/]+`, 'g');
40+
3541
constructor() {
3642
const currentTime = Date.now();
3743
this.logger = getAmplifyLogger();
@@ -66,8 +72,15 @@ export class CLIFlowReport implements IFlowData {
6672
*/
6773
setInput(input: CLIInput): void {
6874
this.input = input;
69-
this.runtime = input.argv[0] as string;
70-
this.executable = input.argv[1] as string;
75+
for (let i = 0; i < input.argv.length; i++) {
76+
if (input.argv[i].match(this.filePathRegex)) {
77+
const arg = this.anonymizePath(input.argv[i]);
78+
this.input.argv[i] = arg;
79+
}
80+
}
81+
82+
this.runtime = this.input.argv[0] as string;
83+
this.executable = this.input.argv[1] as string;
7184
this.cmd = input.argv[2] as string;
7285
this.subCmd = input.argv[3] ? input.argv[3] : undefined;
7386
this.optionFlowData = []; // key-value store with ordering maintained
@@ -139,4 +152,12 @@ export class CLIFlowReport implements IFlowData {
139152
const timeStampedOption: IOptionFlowHeadlessData = { input: cleanOption, timestamp: new Date().valueOf() }; // attach unix-style timestamp
140153
this.optionFlowData.push(timeStampedOption);
141154
}
155+
156+
private anonymizePath = (str: string): string => {
157+
let result = str;
158+
if (result.match(this.filePathRegex)) {
159+
result = result.replace(this.filePathRegex, '');
160+
}
161+
return result;
162+
};
142163
}

packages/amplify-cli/src/domain/amplify-usageData/SerializableError.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { $TSAny } from '@aws-amplify/amplify-cli-core';
2+
import { homedir } from 'os';
23
import * as path from 'path';
34

45
const stackTraceRegex = /^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
56
const ARNRegex =
67
/arn:[a-z0-9][-.a-z0-9]{0,62}:[A-Za-z0-9][A-Za-z0-9_/.-]{0,62}:[A-Za-z0-9_/.-]{0,63}:[A-Za-z0-9_/.-]{0,63}:[A-Za-z0-9][A-Za-z0-9:_/+=,@.-]{0,1023}/g;
78

9+
// Gen1 stacks have same structure as Gen2 stacks
10+
const stackRegex = /amplify-[a-zA-Z0-9-]+/g;
11+
// (file:/+)? -> matches optional file url prefix
12+
// homedir() -> users home directory, replacing \ with /
13+
// [\\w.\\-_@\\\\/]+ -> matches nested directories and file name
14+
const filePathRegex = new RegExp(`(file:/+)?${homedir().replaceAll('\\', '/')}[\\w.\\-_@\\\\/]+`, 'g');
15+
816
/**
917
* Wrapper around Error.name
1018
*/
@@ -16,7 +24,7 @@ export class SerializableError {
1624
trace?: Trace[];
1725
constructor(error: Error) {
1826
this.name = error.name;
19-
this.message = removeARN(error.message)!;
27+
this.message = anonymizePaths(sanitize(error.message)!);
2028
this.details = removeARN((error as $TSAny)?.details);
2129
this.code = (error as $TSAny)?.code;
2230
this.trace = extractStackTrace(error);
@@ -83,10 +91,32 @@ const processPaths = (paths: string[]): string[] => {
8391
});
8492
};
8593

94+
const sanitize = (str?: string): string | undefined => {
95+
let result = str;
96+
result = removeARN(result);
97+
result = removeStackIdentifier(result);
98+
99+
return result;
100+
};
101+
86102
const removeARN = (str?: string): string | undefined => {
87103
return str?.replace(ARNRegex, '<escaped ARN>');
88104
};
89105

106+
const removeStackIdentifier = (str?: string): string | undefined => {
107+
return str?.replace(stackRegex, '<escaped stack>') ?? '';
108+
};
109+
110+
const anonymizePaths = (str: string): string => {
111+
let result = str;
112+
113+
if (result.match(filePathRegex)) {
114+
result = result.replaceAll(filePathRegex, '');
115+
}
116+
117+
return result;
118+
};
119+
90120
type Trace = {
91121
methodName: string;
92122
file: string;

packages/amplify-cli/src/domain/amplify-usageData/UsageData.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { prompter, printer } from '@aws-amplify/amplify-prompts';
44
import https from 'https';
55
import { pick } from 'lodash';
66
import { UrlWithStringQuery } from 'url';
7-
import { v4 as uuid } from 'uuid';
7+
import { v4 as uuid, v5 as uuidV5 } from 'uuid';
88
import { CLIInput } from '../command-input';
99
import {
1010
JSONUtilities,
@@ -24,6 +24,8 @@ import redactInput from './identifiable-input-regex';
2424
import { Timer } from './Timer';
2525
import { UsageDataPayload } from './UsageDataPayload';
2626

27+
const AMPLIFY_CLI_UUID_NAMESPACE = '7bf41e64-b0be-4a57-8d92-9cfbc1600f0f';
28+
2729
/**
2830
* Singleton class that manages the lifecycle of usage data during a CLI command
2931
*/
@@ -63,7 +65,7 @@ export class UsageData implements IUsageData {
6365
processStartTimeStamp: number,
6466
): void {
6567
this.installationUuid = installationUuid;
66-
this.accountId = accountId;
68+
this.accountId = uuidV5(accountId.slice(0, -2), AMPLIFY_CLI_UUID_NAMESPACE);
6769
this.projectSettings = projectSettings;
6870
this.version = version;
6971
this.inputOptions = input.options ? pick(input.options as InputOptions, ['sandboxId']) : {};

0 commit comments

Comments
 (0)