@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.
- package/README.md +6 -1
- package/dist/commands/junit/upload.js +1 -1
- package/dist/commands/lambda/__tests__/fixtures.d.ts +5 -1
- package/dist/commands/lambda/__tests__/fixtures.js +13 -2
- package/dist/commands/lambda/__tests__/functions/commons.test.js +221 -0
- package/dist/commands/lambda/__tests__/functions/instrument.test.js +64 -42
- package/dist/commands/lambda/__tests__/instrument.test.js +425 -4
- package/dist/commands/lambda/__tests__/prompt.test.d.ts +1 -0
- package/dist/commands/lambda/__tests__/prompt.test.js +216 -0
- package/dist/commands/lambda/__tests__/uninstrument.test.js +310 -4
- package/dist/commands/lambda/constants.d.ts +10 -0
- package/dist/commands/lambda/constants.js +36 -2
- package/dist/commands/lambda/functions/commons.d.ts +15 -0
- package/dist/commands/lambda/functions/commons.js +105 -2
- package/dist/commands/lambda/functions/instrument.d.ts +1 -1
- package/dist/commands/lambda/functions/instrument.js +17 -7
- package/dist/commands/lambda/functions/uninstrument.js +1 -0
- package/dist/commands/lambda/instrument.d.ts +2 -0
- package/dist/commands/lambda/instrument.js +100 -47
- package/dist/commands/lambda/interfaces.d.ts +4 -0
- package/dist/commands/lambda/prompt.d.ts +9 -0
- package/dist/commands/lambda/prompt.js +187 -0
- package/dist/commands/lambda/uninstrument.d.ts +1 -0
- package/dist/commands/lambda/uninstrument.js +68 -30
- package/dist/commands/synthetics/__tests__/cli.test.js +1 -0
- package/dist/commands/synthetics/__tests__/fixtures.js +1 -0
- package/dist/commands/synthetics/__tests__/run-test.test.js +48 -2
- package/dist/commands/synthetics/__tests__/utils.test.js +11 -0
- package/dist/commands/synthetics/command.d.ts +1 -0
- package/dist/commands/synthetics/command.js +11 -5
- package/dist/commands/synthetics/interfaces.d.ts +8 -3
- package/dist/commands/synthetics/interfaces.js +7 -3
- package/dist/commands/synthetics/reporters/default.js +5 -1
- package/dist/commands/synthetics/run-test.js +3 -1
- package/dist/commands/synthetics/utils.d.ts +3 -0
- package/dist/commands/synthetics/utils.js +20 -1
- package/dist/commands/trace/api.js +1 -1
- package/dist/helpers/__tests__/ci.test.js +71 -24
- package/dist/helpers/__tests__/user-provided-git.test.js +69 -27
- package/dist/helpers/ci.js +1 -1
- package/dist/helpers/user-provided-git.d.ts +2 -1
- package/dist/helpers/user-provided-git.js +18 -3
- 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;
|
|
@@ -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.
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
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();
|
|
@@ -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
|
});
|
|
@@ -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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
186
|
-
|
|
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["
|
|
20
|
-
Operator["
|
|
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;
|