@datadog/datadog-ci 0.17.12 → 0.17.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +6 -1
  2. package/dist/commands/junit/upload.js +1 -1
  3. package/dist/commands/lambda/__tests__/fixtures.d.ts +5 -1
  4. package/dist/commands/lambda/__tests__/fixtures.js +13 -2
  5. package/dist/commands/lambda/__tests__/functions/commons.test.js +221 -0
  6. package/dist/commands/lambda/__tests__/functions/instrument.test.js +64 -42
  7. package/dist/commands/lambda/__tests__/instrument.test.js +425 -4
  8. package/dist/commands/lambda/__tests__/prompt.test.d.ts +1 -0
  9. package/dist/commands/lambda/__tests__/prompt.test.js +216 -0
  10. package/dist/commands/lambda/__tests__/uninstrument.test.js +310 -4
  11. package/dist/commands/lambda/constants.d.ts +10 -0
  12. package/dist/commands/lambda/constants.js +36 -2
  13. package/dist/commands/lambda/functions/commons.d.ts +15 -0
  14. package/dist/commands/lambda/functions/commons.js +105 -2
  15. package/dist/commands/lambda/functions/instrument.d.ts +1 -1
  16. package/dist/commands/lambda/functions/instrument.js +17 -7
  17. package/dist/commands/lambda/functions/uninstrument.js +1 -0
  18. package/dist/commands/lambda/instrument.d.ts +2 -0
  19. package/dist/commands/lambda/instrument.js +100 -47
  20. package/dist/commands/lambda/interfaces.d.ts +4 -0
  21. package/dist/commands/lambda/prompt.d.ts +9 -0
  22. package/dist/commands/lambda/prompt.js +187 -0
  23. package/dist/commands/lambda/uninstrument.d.ts +1 -0
  24. package/dist/commands/lambda/uninstrument.js +68 -30
  25. package/dist/commands/synthetics/__tests__/cli.test.js +1 -0
  26. package/dist/commands/synthetics/__tests__/fixtures.js +1 -0
  27. package/dist/commands/synthetics/__tests__/run-test.test.js +48 -2
  28. package/dist/commands/synthetics/__tests__/utils.test.js +11 -0
  29. package/dist/commands/synthetics/command.d.ts +1 -0
  30. package/dist/commands/synthetics/command.js +11 -5
  31. package/dist/commands/synthetics/interfaces.d.ts +8 -3
  32. package/dist/commands/synthetics/interfaces.js +7 -3
  33. package/dist/commands/synthetics/reporters/default.js +5 -1
  34. package/dist/commands/synthetics/run-test.js +3 -1
  35. package/dist/commands/synthetics/utils.d.ts +3 -0
  36. package/dist/commands/synthetics/utils.js +20 -1
  37. package/dist/commands/trace/api.js +1 -1
  38. package/dist/helpers/__tests__/ci.test.js +71 -24
  39. package/dist/helpers/__tests__/user-provided-git.test.js +69 -27
  40. package/dist/helpers/ci.js +1 -1
  41. package/dist/helpers/user-provided-git.d.ts +2 -1
  42. package/dist/helpers/user-provided-git.js +18 -3
  43. package/package.json +1 -1
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.requestFunctionSelection = exports.requestChangesConfirmation = exports.requestDatadogEnvVars = exports.requestAWSCredentials = exports.functionSelectionQuestion = exports.confirmationQuestion = exports.datadogEnvVarsQuestions = exports.datadogApiKeyTypeQuestion = void 0;
13
+ const chalk_1 = require("chalk");
14
+ const inquirer_1 = require("inquirer");
15
+ const constants_1 = require("./constants");
16
+ const commons_1 = require("./functions/commons");
17
+ const awsCredentialsQuestions = [
18
+ {
19
+ // AWS_ACCESS_KEY_ID question
20
+ message: 'Enter AWS Access Key ID:',
21
+ name: constants_1.AWS_ACCESS_KEY_ID_ENV_VAR,
22
+ type: 'input',
23
+ validate: (value) => {
24
+ if (!value || !commons_1.sentenceMatchesRegEx(value, constants_1.AWS_ACCESS_KEY_ID_REG_EXP)) {
25
+ return 'Enter a valid AWS Access Key ID.';
26
+ }
27
+ return true;
28
+ },
29
+ },
30
+ {
31
+ // AWS_SECRET_ACCESS_KEY_ENV_VAR question
32
+ mask: true,
33
+ message: 'Enter AWS Secret Access Key:',
34
+ name: constants_1.AWS_SECRET_ACCESS_KEY_ENV_VAR,
35
+ type: 'password',
36
+ validate: (value) => {
37
+ if (!value || !commons_1.sentenceMatchesRegEx(value, constants_1.AWS_SECRET_ACCESS_KEY_REG_EXP)) {
38
+ return 'Enter a valid AWS Secret Access Key.';
39
+ }
40
+ return true;
41
+ },
42
+ },
43
+ {
44
+ // AWS_SESSION_TOKEN
45
+ mask: true,
46
+ message: 'Enter AWS Session Token (optional):',
47
+ name: constants_1.AWS_SESSION_TOKEN_ENV_VAR,
48
+ type: 'password',
49
+ },
50
+ {
51
+ // AWS_DEFAULT_REGION
52
+ choices: constants_1.AWS_REGIONS,
53
+ default: 0,
54
+ message: 'Select an AWS region:',
55
+ name: constants_1.AWS_DEFAULT_REGION_ENV_VAR,
56
+ type: 'list',
57
+ },
58
+ ];
59
+ const datadogApiKeyTypeQuestion = (datadogSite) => ({
60
+ choices: [
61
+ {
62
+ name: `Plain text ${chalk_1.bold('API Key')} (Recommended for trial users) `,
63
+ value: {
64
+ envVar: constants_1.CI_API_KEY_ENV_VAR,
65
+ message: 'API Key:',
66
+ },
67
+ },
68
+ new inquirer_1.Separator(),
69
+ {
70
+ name: `API key encrypted with AWS Key Management Service ${chalk_1.bold('(KMS) API Key')}`,
71
+ value: {
72
+ envVar: constants_1.CI_KMS_API_KEY_ENV_VAR,
73
+ message: 'KMS Encrypted API Key:',
74
+ },
75
+ },
76
+ {
77
+ name: `AWS Secrets Manager ${chalk_1.bold('API Key Secret ARN')}`,
78
+ value: {
79
+ envVar: constants_1.CI_API_KEY_SECRET_ARN_ENV_VAR,
80
+ message: 'API Key Secret ARN:',
81
+ },
82
+ },
83
+ ],
84
+ message: `Which type of Datadog API Key you want to set? \nLearn more at ${chalk_1.blueBright(`https://app.${datadogSite}/organization-settings/api-keys`)}`,
85
+ name: 'type',
86
+ type: 'list',
87
+ });
88
+ exports.datadogApiKeyTypeQuestion = datadogApiKeyTypeQuestion;
89
+ const datadogSiteQuestion = {
90
+ // DATADOG SITE
91
+ choices: constants_1.SITES,
92
+ message: `Select the Datadog site to send data. \nLearn more at ${chalk_1.blueBright('https://docs.datadoghq.com/getting_started/site/')}`,
93
+ name: constants_1.CI_SITE_ENV_VAR,
94
+ type: 'list',
95
+ };
96
+ const datadogEnvVarsQuestions = (datadogApiKeyType) => ({
97
+ // DATADOG API KEY given type
98
+ default: process.env[datadogApiKeyType.envVar],
99
+ message: datadogApiKeyType.message,
100
+ name: datadogApiKeyType.envVar,
101
+ type: 'input',
102
+ validate: (value) => {
103
+ if (!value || !commons_1.sentenceMatchesRegEx(value, constants_1.DATADOG_API_KEY_REG_EXP)) {
104
+ return 'Enter a valid Datadog API Key.';
105
+ }
106
+ return true;
107
+ },
108
+ });
109
+ exports.datadogEnvVarsQuestions = datadogEnvVarsQuestions;
110
+ const confirmationQuestion = (message) => ({
111
+ message,
112
+ name: 'confirmation',
113
+ type: 'confirm',
114
+ });
115
+ exports.confirmationQuestion = confirmationQuestion;
116
+ const functionSelectionQuestion = (functionNames) => ({
117
+ choices: functionNames,
118
+ message: 'Select the functions to modify',
119
+ name: 'functions',
120
+ type: 'checkbox',
121
+ validate: (selectedFunctions) => {
122
+ if (selectedFunctions.length < 1) {
123
+ return 'You must choose at least one function.';
124
+ }
125
+ return true;
126
+ },
127
+ });
128
+ exports.functionSelectionQuestion = functionSelectionQuestion;
129
+ const requestAWSCredentials = () => __awaiter(void 0, void 0, void 0, function* () {
130
+ try {
131
+ const awsCredentialsAnswers = yield inquirer_1.prompt(awsCredentialsQuestions);
132
+ process.env[constants_1.AWS_ACCESS_KEY_ID_ENV_VAR] = awsCredentialsAnswers[constants_1.AWS_ACCESS_KEY_ID_ENV_VAR];
133
+ process.env[constants_1.AWS_SECRET_ACCESS_KEY_ENV_VAR] = awsCredentialsAnswers[constants_1.AWS_SECRET_ACCESS_KEY_ENV_VAR];
134
+ process.env[constants_1.AWS_DEFAULT_REGION_ENV_VAR] = awsCredentialsAnswers[constants_1.AWS_DEFAULT_REGION_ENV_VAR];
135
+ if (awsCredentialsAnswers[constants_1.AWS_SESSION_TOKEN_ENV_VAR] !== undefined) {
136
+ process.env[constants_1.AWS_SESSION_TOKEN_ENV_VAR] = awsCredentialsAnswers[constants_1.AWS_SESSION_TOKEN_ENV_VAR];
137
+ }
138
+ }
139
+ catch (e) {
140
+ if (e instanceof Error) {
141
+ throw Error(`Couldn't set AWS Credentials. ${e.message}`);
142
+ }
143
+ }
144
+ });
145
+ exports.requestAWSCredentials = requestAWSCredentials;
146
+ const requestDatadogEnvVars = () => __awaiter(void 0, void 0, void 0, function* () {
147
+ try {
148
+ const datadogSiteAnswer = yield inquirer_1.prompt(datadogSiteQuestion);
149
+ const selectedDatadogSite = datadogSiteAnswer[constants_1.CI_SITE_ENV_VAR];
150
+ process.env[constants_1.CI_SITE_ENV_VAR] = selectedDatadogSite;
151
+ const datadogApiKeyTypeAnswer = yield inquirer_1.prompt(exports.datadogApiKeyTypeQuestion(selectedDatadogSite));
152
+ const datadogApiKeyType = datadogApiKeyTypeAnswer.type;
153
+ const datadogEnvVars = yield inquirer_1.prompt(exports.datadogEnvVarsQuestions(datadogApiKeyType));
154
+ const selectedDatadogApiKeyEnvVar = datadogApiKeyType.envVar;
155
+ process.env[selectedDatadogApiKeyEnvVar] = datadogEnvVars[selectedDatadogApiKeyEnvVar];
156
+ }
157
+ catch (e) {
158
+ if (e instanceof Error) {
159
+ throw Error(`Couldn't set Datadog Environment Variables. ${e.message}`);
160
+ }
161
+ }
162
+ });
163
+ exports.requestDatadogEnvVars = requestDatadogEnvVars;
164
+ const requestChangesConfirmation = (message) => __awaiter(void 0, void 0, void 0, function* () {
165
+ try {
166
+ const confirmationAnswer = yield inquirer_1.prompt(exports.confirmationQuestion(message));
167
+ return confirmationAnswer.confirmation;
168
+ }
169
+ catch (e) {
170
+ if (e instanceof Error) {
171
+ throw Error(`Couldn't receive confirmation. ${e.message}`);
172
+ }
173
+ }
174
+ });
175
+ exports.requestChangesConfirmation = requestChangesConfirmation;
176
+ const requestFunctionSelection = (functionNames) => __awaiter(void 0, void 0, void 0, function* () {
177
+ try {
178
+ const selectedFunctionsAnswer = yield inquirer_1.prompt(exports.functionSelectionQuestion(functionNames));
179
+ return selectedFunctionsAnswer.functions;
180
+ }
181
+ catch (e) {
182
+ if (e instanceof Error) {
183
+ throw Error(`Couldn't receive selected functions. ${e.message}`);
184
+ }
185
+ }
186
+ });
187
+ exports.requestFunctionSelection = requestFunctionSelection;
@@ -5,6 +5,7 @@ export declare class UninstrumentCommand extends Command {
5
5
  private dryRun;
6
6
  private forwarder?;
7
7
  private functions;
8
+ private interactive;
8
9
  private regExPattern?;
9
10
  private region?;
10
11
  execute(): Promise<1 | 0>;
@@ -14,55 +14,91 @@ const aws_sdk_1 = require("aws-sdk");
14
14
  const chalk_1 = require("chalk");
15
15
  const clipanion_1 = require("clipanion");
16
16
  const utils_1 = require("../../helpers/utils");
17
+ const constants_1 = require("./constants");
17
18
  const commons_1 = require("./functions/commons");
18
19
  const uninstrument_1 = require("./functions/uninstrument");
20
+ const prompt_1 = require("./prompt");
19
21
  class UninstrumentCommand extends clipanion_1.Command {
20
22
  constructor() {
21
23
  super(...arguments);
22
24
  this.config = {
23
25
  functions: [],
24
- region: process.env.AWS_DEFAULT_REGION,
26
+ region: process.env[constants_1.AWS_DEFAULT_REGION_ENV_VAR],
25
27
  };
26
28
  this.dryRun = false;
27
29
  this.functions = [];
30
+ this.interactive = false;
28
31
  }
29
32
  execute() {
33
+ var _a, _b, _c;
30
34
  return __awaiter(this, void 0, void 0, function* () {
31
35
  const lambdaConfig = { lambda: this.config };
32
36
  this.config = (yield utils_1.parseConfigFile(lambdaConfig, this.configPath)).lambda;
33
- const hasSpecifiedFuntions = this.functions.length !== 0 || this.config.functions.length !== 0;
37
+ let hasSpecifiedFunctions = this.functions.length !== 0 || this.config.functions.length !== 0;
38
+ if (this.interactive) {
39
+ try {
40
+ if (commons_1.isMissingAWSCredentials()) {
41
+ this.context.stdout.write(`${chalk_1.bold(chalk_1.yellow('[!]'))} No existing AWS credentials found, let's set them up!\n`);
42
+ yield prompt_1.requestAWSCredentials();
43
+ }
44
+ }
45
+ catch (e) {
46
+ this.context.stdout.write(`${chalk_1.red('[Error]')} ${e}\n`);
47
+ return 1;
48
+ }
49
+ const region = (_b = (_a = this.region) !== null && _a !== void 0 ? _a : this.config.region) !== null && _b !== void 0 ? _b : process.env[constants_1.AWS_DEFAULT_REGION_ENV_VAR];
50
+ this.region = region;
51
+ if (!hasSpecifiedFunctions) {
52
+ try {
53
+ const lambda = new aws_sdk_1.Lambda({ region });
54
+ this.context.stdout.write('Fetching Lambda functions, this might take a while.\n');
55
+ const functionNames = (_c = (yield commons_1.getAllLambdaFunctionConfigs(lambda)).map((config) => config.FunctionName).sort()) !== null && _c !== void 0 ? _c : [];
56
+ if (functionNames.length === 0) {
57
+ this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't find any Lambda functions in the specified region.\n`);
58
+ return 1;
59
+ }
60
+ const functions = yield prompt_1.requestFunctionSelection(functionNames);
61
+ this.functions = functions;
62
+ }
63
+ catch (err) {
64
+ this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't fetch Lambda functions. ${err}\n`);
65
+ return 1;
66
+ }
67
+ }
68
+ }
69
+ hasSpecifiedFunctions = this.functions.length !== 0 || this.config.functions.length !== 0;
34
70
  const hasSpecifiedRegExPattern = this.regExPattern !== undefined && this.regExPattern !== '';
35
- if (!hasSpecifiedFuntions && !hasSpecifiedRegExPattern) {
36
- this.context.stdout.write('No functions specified for un-instrumentation.\n');
71
+ if (!hasSpecifiedFunctions && !hasSpecifiedRegExPattern) {
72
+ this.context.stdout.write(`${chalk_1.red('[Error]')} No functions specified for un-instrumentation.\n`);
37
73
  return 1;
38
74
  }
39
75
  const configGroups = [];
40
76
  // Fetch lambda function configurations that are
41
77
  // available to be un-instrumented.
42
78
  if (hasSpecifiedRegExPattern) {
43
- if (hasSpecifiedFuntions) {
79
+ if (hasSpecifiedFunctions) {
44
80
  const usedCommand = this.functions.length !== 0 ? '"--functions"' : 'Functions in config file';
45
- this.context.stdout.write(`${usedCommand} and "--functions-regex" should not be used at the same time.\n`);
81
+ this.context.stdout.write(`${chalk_1.red('[Error]')} ${usedCommand} and "--functions-regex" should not be used at the same time.\n`);
46
82
  return 1;
47
83
  }
48
84
  if (this.regExPattern.match(':')) {
49
- this.context.stdout.write(`"--functions-regex" isn't meant to be used with ARNs.\n`);
85
+ this.context.stdout.write(`${chalk_1.red('[Error]')} "--functions-regex" isn't meant to be used with ARNs.\n`);
50
86
  return 1;
51
87
  }
52
88
  const region = this.region || this.config.region;
53
89
  if (!region) {
54
- this.context.stdout.write('No default region specified. Use `-r`, `--region`.');
90
+ this.context.stdout.write(`${chalk_1.red('[Error]')} No default region specified. Use \`-r\`, \`--region\`.`);
55
91
  return 1;
56
92
  }
57
93
  try {
58
94
  const cloudWatchLogs = new aws_sdk_1.CloudWatchLogs({ region });
59
95
  const lambda = new aws_sdk_1.Lambda({ region });
60
- this.context.stdout.write('Fetching lambda functions, this might take a while.\n');
96
+ this.context.stdout.write('Fetching Lambda functions, this might take a while.\n');
61
97
  const configs = yield uninstrument_1.getUninstrumentedFunctionConfigsFromRegEx(lambda, cloudWatchLogs, this.regExPattern, this.forwarder);
62
98
  configGroups.push({ configs, lambda, cloudWatchLogs });
63
99
  }
64
100
  catch (err) {
65
- this.context.stdout.write(`Couldn't fetch lambda functions. ${err}\n`);
101
+ this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't fetch Lambda functions. ${err}\n`);
66
102
  return 1;
67
103
  }
68
104
  }
@@ -72,7 +108,7 @@ class UninstrumentCommand extends clipanion_1.Command {
72
108
  functionGroups = commons_1.collectFunctionsByRegion(this.functions.length !== 0 ? this.functions : this.config.functions, this.region || this.config.region);
73
109
  }
74
110
  catch (err) {
75
- this.context.stdout.write(`Couldn't group functions. ${err}`);
111
+ this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't group functions. ${err}`);
76
112
  return 1;
77
113
  }
78
114
  for (const [region, functionARNs] of Object.entries(functionGroups)) {
@@ -83,7 +119,7 @@ class UninstrumentCommand extends clipanion_1.Command {
83
119
  configGroups.push({ configs, lambda, cloudWatchLogs });
84
120
  }
85
121
  catch (err) {
86
- this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't fetch lambda functions. ${err}\n`);
122
+ this.context.stdout.write(`${chalk_1.red('[Error]')} Couldn't fetch Lambda functions. ${err}\n`);
87
123
  return 1;
88
124
  }
89
125
  }
@@ -93,6 +129,15 @@ class UninstrumentCommand extends clipanion_1.Command {
93
129
  if (this.dryRun || configList.length === 0) {
94
130
  return 0;
95
131
  }
132
+ const willUpdate = commons_1.willUpdateFunctionConfigs(configList);
133
+ if (this.interactive && willUpdate) {
134
+ this.context.stdout.write(`${chalk_1.yellow('[!]')} Confirmation needed.\n`);
135
+ const isConfirmed = yield prompt_1.requestChangesConfirmation('Do you want to apply the changes?');
136
+ if (!isConfirmed) {
137
+ return 0;
138
+ }
139
+ this.context.stdout.write(`${chalk_1.yellow('[!]')} Uninstrumenting functions.\n`);
140
+ }
96
141
  // Un-instrument functions.
97
142
  const promises = Object.values(configGroups).map((group) => {
98
143
  commons_1.updateLambdaFunctionConfigs(group.lambda, group.cloudWatchLogs, group.configs);
@@ -108,18 +153,9 @@ class UninstrumentCommand extends clipanion_1.Command {
108
153
  });
109
154
  }
110
155
  printPlannedActions(configs) {
111
- var _a;
112
156
  const prefix = this.dryRun ? chalk_1.bold(chalk_1.cyan('[Dry Run] ')) : '';
113
- let anyUpdates = false;
114
- for (const config of configs) {
115
- if (config.updateRequest !== undefined ||
116
- ((_a = config.logGroupConfiguration) === null || _a === void 0 ? void 0 : _a.deleteSubscriptionFilterRequest) !== undefined ||
117
- (config === null || config === void 0 ? void 0 : config.tagConfiguration) !== undefined) {
118
- anyUpdates = true;
119
- break;
120
- }
121
- }
122
- if (!anyUpdates) {
157
+ const willUpdate = commons_1.willUpdateFunctionConfigs(configs);
158
+ if (!willUpdate) {
123
159
  this.context.stdout.write(`${prefix}No updates will be applied\n`);
124
160
  return;
125
161
  }
@@ -149,18 +185,20 @@ UninstrumentCommand.addOption('region', clipanion_1.Command.String('-r,--region'
149
185
  UninstrumentCommand.addOption('configPath', clipanion_1.Command.String('--config'));
150
186
  UninstrumentCommand.addOption('dryRun', clipanion_1.Command.Boolean('-d,--dry'));
151
187
  UninstrumentCommand.addOption('forwarder', clipanion_1.Command.String('--forwarder'));
152
- UninstrumentCommand.addOption('regExPattern', clipanion_1.Command.String('--functions-regex'));
188
+ UninstrumentCommand.addOption('regExPattern', clipanion_1.Command.String('--functions-regex,--functionsRegex'));
189
+ UninstrumentCommand.addOption('interactive', clipanion_1.Command.Boolean('-i,--interactive'));
153
190
  /**
154
191
  * Commands that are not really in use, but to
155
192
  * make uninstrumentation easier for the user.
156
193
  */
157
- UninstrumentCommand.addOption('extensionVersion', clipanion_1.Command.String('-e,--extensionVersion', { hidden: true }));
158
- UninstrumentCommand.addOption('layerVersion', clipanion_1.Command.String('-v,--layerVersion', { hidden: true }));
194
+ UninstrumentCommand.addOption('extensionVersion', clipanion_1.Command.String('-e,--extension-version,--extensionVersion', { hidden: true }));
195
+ UninstrumentCommand.addOption('layerVersion', clipanion_1.Command.String('-v,--layer-version,--layerVersion', { hidden: true }));
159
196
  UninstrumentCommand.addOption('tracing', clipanion_1.Command.String('--tracing', { hidden: true }));
160
- UninstrumentCommand.addOption('mergeXrayTraces', clipanion_1.Command.String('--mergeXrayTraces', { hidden: true }));
161
- UninstrumentCommand.addOption('flushMetricsToLogs', clipanion_1.Command.String('--flushMetricsToLogs', { hidden: true }));
162
- UninstrumentCommand.addOption('logLevel', clipanion_1.Command.String('--logLevel', { hidden: true }));
197
+ UninstrumentCommand.addOption('mergeXrayTraces', clipanion_1.Command.String('--merge-xray-traces,--mergeXrayTraces', { hidden: true }));
198
+ UninstrumentCommand.addOption('flushMetricsToLogs', clipanion_1.Command.String('--flush-metrics-to-logs,--flushMetricsToLogs', { hidden: true }));
199
+ UninstrumentCommand.addOption('logLevel', clipanion_1.Command.String('--log-level,--logLevel', { hidden: true }));
163
200
  UninstrumentCommand.addOption('service', clipanion_1.Command.String('--service', { hidden: true }));
164
201
  UninstrumentCommand.addOption('environment', clipanion_1.Command.String('--env', { hidden: true }));
165
202
  UninstrumentCommand.addOption('version', clipanion_1.Command.String('--version', { hidden: true }));
166
- UninstrumentCommand.addOption('extraTags', clipanion_1.Command.String('--extra-tags', { hidden: true }));
203
+ UninstrumentCommand.addOption('extraTags', clipanion_1.Command.String('--extra-tags,--extraTags', { hidden: true }));
204
+ UninstrumentCommand.addOption('captureLambdaPayload', clipanion_1.Command.String('--capture-lambda-payload,--captureLambdaPayload', { hidden: true }));
@@ -144,6 +144,7 @@ describe('run-test', () => {
144
144
  publicIds: ['ran-dom-id'],
145
145
  subdomain: 'ppa',
146
146
  tunnel: true,
147
+ variableStrings: [],
147
148
  };
148
149
  jest.spyOn(ciUtils, 'getConfig').mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return overrideConfigFile; }));
149
150
  const command = new command_1.RunTestCommand();
@@ -54,6 +54,7 @@ exports.ciConfig = {
54
54
  publicIds: [],
55
55
  subdomain: 'app',
56
56
  tunnel: false,
57
+ variableStrings: [],
57
58
  };
58
59
  const getApiTest = (publicId) => ({
59
60
  config: {
@@ -32,6 +32,7 @@ const ciUtils = __importStar(require("../../../helpers/utils"));
32
32
  const errors_1 = require("../errors");
33
33
  const interfaces_1 = require("../interfaces");
34
34
  const runTests = __importStar(require("../run-test"));
35
+ const tunnel_1 = require("../tunnel");
35
36
  const utils = __importStar(require("../utils"));
36
37
  const fixtures_1 = require("./fixtures");
37
38
  describe('run-test', () => {
@@ -95,6 +96,39 @@ describe('run-test', () => {
95
96
  ]), expect.anything());
96
97
  expect(apiHelper.getPresignedURL).not.toHaveBeenCalled();
97
98
  }));
99
+ test('open and close tunnel for successful runs', () => __awaiter(void 0, void 0, void 0, function* () {
100
+ const location = {
101
+ display_name: 'us1',
102
+ id: 1,
103
+ is_active: true,
104
+ name: 'us1',
105
+ region: 'us1',
106
+ };
107
+ jest.spyOn(utils, 'wait').mockImplementation(() => new Promise((res) => setTimeout(res, 10)));
108
+ const startTunnelSpy = jest
109
+ .spyOn(tunnel_1.Tunnel.prototype, 'start')
110
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return ({ host: 'host', id: 'id', privateKey: 'key' }); }));
111
+ const stopTunnelSpy = jest.spyOn(tunnel_1.Tunnel.prototype, 'stop');
112
+ jest.spyOn(utils, 'getTestsToTrigger').mockReturnValue(Promise.resolve({
113
+ overriddenTestsToTrigger: [],
114
+ summary: utils.createSummary(),
115
+ tests: [{ options: { ci: { executionRule: interfaces_1.ExecutionRule.BLOCKING } }, public_id: '123-456-789' }],
116
+ }));
117
+ jest.spyOn(utils, 'runTests').mockReturnValue(Promise.resolve({
118
+ locations: [location],
119
+ results: [{ device: 'chrome_laptop.large', location: 1, public_id: '123-456-789', result_id: '1' }],
120
+ triggered_check_ids: [],
121
+ }));
122
+ const apiHelper = {
123
+ getPresignedURL: () => ({ url: 'url' }),
124
+ pollResults: () => fixtures_1.mockPollResultResponse,
125
+ triggerTests: () => fixtures_1.mockTestTriggerResponse,
126
+ };
127
+ jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
128
+ yield runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { failOnCriticalErrors: true, publicIds: ['123-456-789'], tunnel: true }));
129
+ expect(startTunnelSpy).toHaveBeenCalledTimes(1);
130
+ expect(stopTunnelSpy).toHaveBeenCalledTimes(1);
131
+ }));
98
132
  test('getTestsList throws', () => __awaiter(void 0, void 0, void 0, function* () {
99
133
  const serverError = new Error('Server Error');
100
134
  serverError.response = { data: { errors: ['Bad Gateway'] }, status: 502 };
@@ -137,6 +171,10 @@ describe('run-test', () => {
137
171
  yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { publicIds: ['public-id-1', 'public-id-2'], tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError('UNAVAILABLE_TUNNEL_CONFIG'));
138
172
  }));
139
173
  test('runTests throws', () => __awaiter(void 0, void 0, void 0, function* () {
174
+ jest
175
+ .spyOn(tunnel_1.Tunnel.prototype, 'start')
176
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return ({ host: 'host', id: 'id', privateKey: 'key' }); }));
177
+ const stopTunnelSpy = jest.spyOn(tunnel_1.Tunnel.prototype, 'stop');
140
178
  jest.spyOn(utils, 'getTestsToTrigger').mockReturnValue(Promise.resolve({
141
179
  overriddenTestsToTrigger: [],
142
180
  summary: utils.createSummary(),
@@ -146,12 +184,14 @@ describe('run-test', () => {
146
184
  serverError.response = { data: { errors: ['Bad Gateway'] }, status: 502 };
147
185
  serverError.config = { baseURL: 'baseURL', url: 'url' };
148
186
  const apiHelper = {
187
+ getPresignedURL: () => ({ url: 'url' }),
149
188
  triggerTests: jest.fn(() => {
150
189
  throw serverError;
151
190
  }),
152
191
  };
153
192
  jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
154
- yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { publicIds: ['public-id-1', 'public-id-2'] }))).rejects.toMatchError(new errors_1.CriticalError('TRIGGER_TESTS_FAILED'));
193
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { publicIds: ['public-id-1', 'public-id-2'], tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError('TRIGGER_TESTS_FAILED'));
194
+ expect(stopTunnelSpy).toHaveBeenCalledTimes(1);
155
195
  }));
156
196
  test('waitForResults throws', () => __awaiter(void 0, void 0, void 0, function* () {
157
197
  const location = {
@@ -161,6 +201,10 @@ describe('run-test', () => {
161
201
  name: 'us1',
162
202
  region: 'us1',
163
203
  };
204
+ jest
205
+ .spyOn(tunnel_1.Tunnel.prototype, 'start')
206
+ .mockImplementation(() => __awaiter(void 0, void 0, void 0, function* () { return ({ host: 'host', id: 'id', privateKey: 'key' }); }));
207
+ const stopTunnelSpy = jest.spyOn(tunnel_1.Tunnel.prototype, 'stop');
164
208
  jest.spyOn(utils, 'getTestsToTrigger').mockReturnValue(Promise.resolve({
165
209
  overriddenTestsToTrigger: [],
166
210
  summary: utils.createSummary(),
@@ -175,12 +219,14 @@ describe('run-test', () => {
175
219
  serverError.response = { data: { errors: ['Bad Gateway'] }, status: 502 };
176
220
  serverError.config = { baseURL: 'baseURL', url: 'url' };
177
221
  const apiHelper = {
222
+ getPresignedURL: () => ({ url: 'url' }),
178
223
  pollResults: jest.fn(() => {
179
224
  throw serverError;
180
225
  }),
181
226
  };
182
227
  jest.spyOn(runTests, 'getApiHelper').mockImplementation(() => apiHelper);
183
- yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { failOnCriticalErrors: true, publicIds: ['public-id-1', 'public-id-2'] }))).rejects.toMatchError(new errors_1.CriticalError('POLL_RESULTS_FAILED'));
228
+ yield expect(runTests.executeTests(fixtures_1.mockReporter, Object.assign(Object.assign({}, fixtures_1.ciConfig), { failOnCriticalErrors: true, publicIds: ['public-id-1', 'public-id-2'], tunnel: true }))).rejects.toMatchError(new errors_1.CriticalError('POLL_RESULTS_FAILED'));
229
+ expect(stopTunnelSpy).toHaveBeenCalledTimes(1);
184
230
  }));
185
231
  });
186
232
  describe('getDatadogHost', () => {
@@ -625,4 +625,15 @@ describe('utils', () => {
625
625
  expect(counter).toBe(3);
626
626
  }));
627
627
  });
628
+ test('parseVariablesFromCli', () => {
629
+ const mockLogFunction = (message) => undefined;
630
+ expect(utils.parseVariablesFromCli(['TEST=42'], mockLogFunction)).toEqual({ TEST: '42' });
631
+ expect(utils.parseVariablesFromCli(['TEST=42 with some spaces'], mockLogFunction)).toEqual({
632
+ TEST: '42 with some spaces',
633
+ });
634
+ expect(utils.parseVariablesFromCli(['TEST=42=43=44'], mockLogFunction)).toEqual({ TEST: '42=43=44' });
635
+ expect(utils.parseVariablesFromCli(['TEST='], mockLogFunction)).toEqual({ TEST: '' });
636
+ expect(utils.parseVariablesFromCli([''], mockLogFunction)).toBeUndefined();
637
+ expect(utils.parseVariablesFromCli(undefined, mockLogFunction)).toBeUndefined();
638
+ });
628
639
  });
@@ -17,6 +17,7 @@ export declare class RunTestCommand extends Command {
17
17
  private subdomain?;
18
18
  private testSearchQuery?;
19
19
  private tunnel?;
20
+ private variableStrings?;
20
21
  execute(): Promise<1 | 0>;
21
22
  private getAppBaseURL;
22
23
  private renderResults;
@@ -38,6 +38,7 @@ exports.DEFAULT_COMMAND_CONFIG = {
38
38
  publicIds: [],
39
39
  subdomain: 'app',
40
40
  tunnel: false,
41
+ variableStrings: [],
41
42
  };
42
43
  class RunTestCommand extends clipanion_1.Command {
43
44
  constructor() {
@@ -190,6 +191,10 @@ class RunTestCommand extends clipanion_1.Command {
190
191
  testSearchQuery: this.testSearchQuery,
191
192
  tunnel: this.tunnel,
192
193
  }));
194
+ // Override with Global CLI parameters
195
+ this.config.global = deep_extend_1.default(this.config.global, utils_1.removeUndefinedValues({
196
+ variables: utils_2.parseVariablesFromCli(this.variableStrings, (log) => { var _a; return (_a = this.reporter) === null || _a === void 0 ? void 0 : _a.log(log); }),
197
+ }));
193
198
  if (typeof this.config.files === 'string') {
194
199
  this.reporter.log('[DEPRECATED] "files" should be an array of string instead of a string.\n');
195
200
  this.config.files = [this.config.files];
@@ -217,14 +222,15 @@ exports.RunTestCommand = RunTestCommand;
217
222
  RunTestCommand.addPath('synthetics', 'run-tests');
218
223
  RunTestCommand.addOption('apiKey', clipanion_1.Command.String('--apiKey'));
219
224
  RunTestCommand.addOption('appKey', clipanion_1.Command.String('--appKey'));
220
- RunTestCommand.addOption('failOnCriticalErrors', clipanion_1.Command.Boolean('--failOnCriticalErrors'));
221
225
  RunTestCommand.addOption('configPath', clipanion_1.Command.String('--config'));
222
226
  RunTestCommand.addOption('datadogSite', clipanion_1.Command.String('--datadogSite'));
223
- RunTestCommand.addOption('files', clipanion_1.Command.Array('-f,--files'));
227
+ RunTestCommand.addOption('failOnCriticalErrors', clipanion_1.Command.Boolean('--failOnCriticalErrors'));
224
228
  RunTestCommand.addOption('failOnTimeout', clipanion_1.Command.Boolean('--failOnTimeout'));
229
+ RunTestCommand.addOption('files', clipanion_1.Command.Array('-f,--files'));
230
+ RunTestCommand.addOption('jUnitReport', clipanion_1.Command.String('-j,--jUnitReport'));
225
231
  RunTestCommand.addOption('publicIds', clipanion_1.Command.Array('-p,--public-id'));
226
- RunTestCommand.addOption('testSearchQuery', clipanion_1.Command.String('-s,--search'));
232
+ RunTestCommand.addOption('runName', clipanion_1.Command.String('-n,--runName'));
227
233
  RunTestCommand.addOption('subdomain', clipanion_1.Command.Boolean('--subdomain'));
234
+ RunTestCommand.addOption('testSearchQuery', clipanion_1.Command.String('-s,--search'));
228
235
  RunTestCommand.addOption('tunnel', clipanion_1.Command.Boolean('-t,--tunnel'));
229
- RunTestCommand.addOption('jUnitReport', clipanion_1.Command.String('-j,--jUnitReport'));
230
- RunTestCommand.addOption('runName', clipanion_1.Command.String('-n,--runName'));
236
+ RunTestCommand.addOption('variableStrings', clipanion_1.Command.Array('-v,--variable'));
@@ -179,12 +179,16 @@ export declare enum Operator {
179
179
  doesNotContain = "doesNotContain",
180
180
  is = "is",
181
181
  isNot = "isNot",
182
+ isInLessThan = "isInLessThan",
183
+ isInMoreThan = "isInMoreThan",
182
184
  lessThan = "lessThan",
185
+ lessThanOrEqual = "lessThanOrEqual",
186
+ moreThan = "moreThan",
187
+ moreThanOrEqual = "moreThanOrEqual",
183
188
  matches = "matches",
184
189
  doesNotMatch = "doesNotMatch",
185
- validates = "validates",
186
- isInMoreThan = "isInMoreThan",
187
- isInLessThan = "isInLessThan"
190
+ validatesJSONPath = "validatesJSONPath",
191
+ validatesXPath = "validatesXPath"
188
192
  }
189
193
  export interface User {
190
194
  email: string;
@@ -335,6 +339,7 @@ export interface SyntheticsCIConfig {
335
339
  subdomain: string;
336
340
  testSearchQuery?: string;
337
341
  tunnel: boolean;
342
+ variableStrings: string[];
338
343
  }
339
344
  export interface CommandConfig extends SyntheticsCIConfig {
340
345
  failOnTimeout: boolean;
@@ -13,12 +13,16 @@ var Operator;
13
13
  Operator["doesNotContain"] = "doesNotContain";
14
14
  Operator["is"] = "is";
15
15
  Operator["isNot"] = "isNot";
16
+ Operator["isInLessThan"] = "isInLessThan";
17
+ Operator["isInMoreThan"] = "isInMoreThan";
16
18
  Operator["lessThan"] = "lessThan";
19
+ Operator["lessThanOrEqual"] = "lessThanOrEqual";
20
+ Operator["moreThan"] = "moreThan";
21
+ Operator["moreThanOrEqual"] = "moreThanOrEqual";
17
22
  Operator["matches"] = "matches";
18
23
  Operator["doesNotMatch"] = "doesNotMatch";
19
- Operator["validates"] = "validates";
20
- Operator["isInMoreThan"] = "isInMoreThan";
21
- Operator["isInLessThan"] = "isInLessThan";
24
+ Operator["validatesJSONPath"] = "validatesJSONPath";
25
+ Operator["validatesXPath"] = "validatesXPath";
22
26
  })(Operator = exports.Operator || (exports.Operator = {}));
23
27
  var ExecutionRule;
24
28
  (function (ExecutionRule) {
@@ -50,9 +50,13 @@ const readableOperation = {
50
50
  [interfaces_1.Operator.lessThan]: 'should be less than',
51
51
  [interfaces_1.Operator.matches]: 'should match',
52
52
  [interfaces_1.Operator.doesNotMatch]: 'should not match',
53
- [interfaces_1.Operator.validates]: 'will expire in less than',
54
53
  [interfaces_1.Operator.isInLessThan]: 'will expire in less than',
55
54
  [interfaces_1.Operator.isInMoreThan]: 'will expire in more than',
55
+ [interfaces_1.Operator.lessThanOrEqual]: 'should be less than or equal to',
56
+ [interfaces_1.Operator.moreThan]: 'should be more than',
57
+ [interfaces_1.Operator.moreThanOrEqual]: 'should be less than or equal to',
58
+ [interfaces_1.Operator.validatesJSONPath]: 'assert on JSONPath extracted value',
59
+ [interfaces_1.Operator.validatesXPath]: 'assert on XPath extracted value',
56
60
  };
57
61
  const renderApiError = (errorCode, errorMessage, color) => {
58
62
  if (errorCode === 'INCORRECT_ASSERTION') {
@@ -98,9 +98,11 @@ const executeTests = (reporter, config) => __awaiter(void 0, void 0, void 0, fun
98
98
  }
99
99
  catch (error) {
100
100
  const isCriticalError = api_1.is5xxError(error);
101
- yield stopTunnel();
102
101
  throw new (isCriticalError ? errors_1.CriticalError : errors_1.CiError)('POLL_RESULTS_FAILED');
103
102
  }
103
+ finally {
104
+ yield stopTunnel();
105
+ }
104
106
  return { results, summary, tests, triggers };
105
107
  });
106
108
  exports.executeTests = executeTests;