@ibm-cloud/cd-tools 1.2.2 → 1.2.4

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 CHANGED
@@ -48,7 +48,7 @@ The tools are provided as an [npx](https://docs.npmjs.com/cli/commands/npx) comm
48
48
 
49
49
  ```shell-session
50
50
  $ npx @ibm-cloud/cd-tools
51
- Usage: npx @ibm-cloud/cd-tools [options] [command]
51
+ Usage: @ibm-cloud/cd-tools [options] [command]
52
52
 
53
53
  Tools for migrating Toolchains, Delivery Pipelines, and Git Repos and Issue Tracking projects.
54
54
 
@@ -49,11 +49,15 @@ async function main(options) {
49
49
  for (let i = 0; i < getToolsRes.tools.length; i++) {
50
50
  const tool = getToolsRes.tools[i];
51
51
 
52
+ // Skip iff it's GitHub/GitLab/GRIT integration with OAuth
53
+ if (['githubconsolidated', 'github_integrated', 'gitlab', 'hostedgit'].includes(tool.tool_type_id) && (tool.parameters?.auth_type === '' || tool.parameters?.auth_type === 'oauth'))
54
+ continue;
55
+
52
56
  // Check tool integrations for any plain text secret values
53
57
  if (SECRET_KEYS_MAP[tool.tool_type_id]) {
54
58
  SECRET_KEYS_MAP[tool.tool_type_id].forEach((entry) => {
55
59
  const updateableSecretParam = entry.key;
56
- if (tool.parameters[updateableSecretParam] && !isSecretReference(tool.parameters[updateableSecretParam])) {
60
+ if (tool.parameters[updateableSecretParam] && !isSecretReference(tool.parameters[updateableSecretParam]) && tool.parameters[updateableSecretParam].length > 0) {
57
61
  toolResults.push({
58
62
  'Tool ID': tool.id,
59
63
  'Tool Type': tool.tool_type_id,
@@ -68,7 +72,7 @@ async function main(options) {
68
72
  const pipelineData = await getPipelineData(token, tool.id, region);
69
73
 
70
74
  pipelineData?.properties.forEach((prop) => {
71
- if (prop.type === 'secure' && !isSecretReference(prop.value)) {
75
+ if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0) {
72
76
  pipelineResults.push({
73
77
  'Pipeline ID': pipelineData.id,
74
78
  'Trigger Name': '-',
@@ -79,7 +83,7 @@ async function main(options) {
79
83
 
80
84
  pipelineData?.triggers.forEach((trigger) => {
81
85
  trigger.properties?.forEach((prop) => {
82
- if (prop.type === 'secure' && !isSecretReference(prop.value)) {
86
+ if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0) {
83
87
  pipelineResults.push({
84
88
  'Pipeline ID': pipelineData.id,
85
89
  'Trigger Name': trigger.name,
@@ -106,7 +106,7 @@ async function main(options) {
106
106
 
107
107
  // check for existing .tf files in output directory
108
108
  if (fs.existsSync(outputDir)) {
109
- let files = readdirSync(outputDir, { recursive: true });
109
+ let files = fs.readdirSync(outputDir, { recursive: true });
110
110
  files = files.filter((f) => f.endsWith('.tf'));
111
111
  if (files.length > 0) throw Error(`Output directory already has ${files.length} '.tf' files, please specify a different output directory`);
112
112
  }
@@ -237,7 +237,7 @@ async function main(options) {
237
237
  }, 5000);
238
238
 
239
239
  await initProviderFile(sourceRegion, TEMP_DIR);
240
- await runTerraformInit(TEMP_DIR);
240
+ await runTerraformInit(TEMP_DIR, verbosity);
241
241
 
242
242
  nonSecretRefs = await importTerraform(bearer, apiKey, sourceRegion, sourceToolchainId, targetToolchainName, policyIds, TEMP_DIR, isCompact, verbosity);
243
243
  };
@@ -249,7 +249,8 @@ async function main(options) {
249
249
  LOG_STAGES.import
250
250
  );
251
251
 
252
- if (nonSecretRefs.length > 0) logger.warn(`\nWarning! The following generated terraform resource contains a hashed secret, applying without changes may result in error(s):\n${nonSecretRefs.map((entry) => `- ${entry}\n`).join('')}`, '', true);
252
+ if (nonSecretRefs.length > 0) logger.warn(`\nWarning! The following generated terraform resource contains hashed secret(s) that cannot be migrated, applying without changes may result in error(s):`);
253
+ logger.table(nonSecretRefs);
253
254
 
254
255
  } catch (err) {
255
256
  if (err.message && err.stack) {
@@ -302,7 +303,8 @@ async function main(options) {
302
303
  'Running terraform init...',
303
304
  'Terraform successfully initialized',
304
305
  LOG_STAGES.tf,
305
- outputDir
306
+ outputDir,
307
+ verbosity
306
308
  );
307
309
 
308
310
  logger.info(`DRY_RUN: ${dryRun}, running terraform apply...`, LOG_STAGES.tf);
@@ -70,8 +70,15 @@ export async function importTerraform(token, apiKey, region, toolchainId, toolch
70
70
  if (isSecretReference(tool.parameters[key])) {
71
71
  additionalProps[block.name].push({ param: tfKey, value: tool.parameters[key] });
72
72
  } else {
73
- nonSecretRefs.push(block.name);
74
- if (required) additionalProps[block.name].push({ param: tfKey, value: `<${tfKey}>` });
73
+ const newFileName = SUPPORTED_TOOLS_MAP[tool.tool_type_id].split('ibm_')[1];
74
+ if (required) {
75
+ nonSecretRefs.push({
76
+ resource_name: block.name,
77
+ property_name: tfKey,
78
+ file_name: isCompact ? 'resources.tf' : `${newFileName}.tf`
79
+ });
80
+ additionalProps[block.name].push({ param: tfKey, value: `<${tfKey}>` });
81
+ }
75
82
  }
76
83
  });
77
84
  }
@@ -10,8 +10,11 @@
10
10
  import axios from 'axios';
11
11
  import axiosRetry from 'axios-retry';
12
12
 
13
+ import mocks from '../../test/data/mocks.js'
13
14
  import { logger, LOG_STAGES } from './logger.js';
14
15
 
16
+ const MOCK_ALL_REQUESTS = process.env.MOCK_ALL_REQUESTS === 'true' || 'false';
17
+
15
18
  axiosRetry(axios, {
16
19
  retries: 3,
17
20
  retryDelay: axiosRetry.exponentialDelay,
@@ -142,6 +145,10 @@ async function getToolchainsByName(bearer, accountId, toolchainName) {
142
145
  }
143
146
 
144
147
  async function getCdInstanceByRegion(bearer, accountId, region) {
148
+ if (MOCK_ALL_REQUESTS && process.env.MOCK_GET_CD_INSTANCE_BY_REGION_SCENARIO) {
149
+ return mocks.getCdInstanceByRegionResponses[process.env.MOCK_GET_CD_INSTANCE_BY_REGION_SCENARIO].data.items.length > 0;
150
+ }
151
+
145
152
  const apiBaseUrl = 'https://api.global-search-tagging.cloud.ibm.com/v3';
146
153
  const options = {
147
154
  url: apiBaseUrl + '/resources/search',
@@ -403,7 +410,7 @@ async function deleteToolchain(bearer, toolchainId, region) {
403
410
  };
404
411
  const response = await axios(options);
405
412
  switch (response.status) {
406
- case 200:
413
+ case 204:
407
414
  return toolchainId;
408
415
  default:
409
416
  throw Error(response.statusText);
@@ -15,7 +15,7 @@ import { parse as tfToJson } from '@cdktf/hcl2json'
15
15
  import { jsonToTf } from 'json-to-tf';
16
16
 
17
17
  import { validateToolchainId, validateGritUrl } from './validate.js';
18
- import { logger } from './logger.js';
18
+ import { logger, LOG_STAGES } from './logger.js';
19
19
  import { getRandChars, promptUserInput, replaceUrlRegion } from './utils.js';
20
20
 
21
21
  // promisify
@@ -300,12 +300,16 @@ async function setupTerraformFiles({ token, srcRegion, targetRegion, targetTag,
300
300
  return Promise.all(promises);
301
301
  }
302
302
 
303
- async function runTerraformPlanGenerate(dir, fileName) {
304
- return await execPromise(`terraform plan -generate-config-out="${fileName}"`, { cwd: dir });
303
+ async function runTerraformInit(dir, verbosity) {
304
+ logger.log('Running command \'terraform init\'', LOG_STAGES.tf);
305
+ const out = await execPromise('terraform init', { cwd: dir });
306
+ if (verbosity >= 2) logger.print(out, '\n');
307
+ logger.log('Command \'terraform init\' completed', LOG_STAGES.tf);
308
+ return out;
305
309
  }
306
310
 
307
- async function runTerraformInit(dir) {
308
- return await execPromise('terraform init', { cwd: dir });
311
+ async function runTerraformPlanGenerate(dir, fileName) {
312
+ return await execPromise(`terraform plan -generate-config-out="${fileName}"`, { cwd: dir });
309
313
  }
310
314
 
311
315
  // primarily used to get number of resources to be used
@@ -11,7 +11,7 @@ import { execSync } from 'child_process';
11
11
  import { logger, LOG_STAGES } from './logger.js'
12
12
  import { RESERVED_GRIT_PROJECT_NAMES, RESERVED_GRIT_GROUP_NAMES, RESERVED_GRIT_SUBGROUP_NAME, TERRAFORM_REQUIRED_VERSION, SECRET_KEYS_MAP } from '../../config.js';
13
13
  import { getToolchainsByName, getToolchainTools, getPipelineData, getAppConfigHealthcheck, getSecretsHealthcheck, getGitOAuth, getGritUserProject, getGritGroup, getGritGroupProject } from './requests.js';
14
- import { promptUserConfirmation, promptUserInput } from './utils.js';
14
+ import { promptUserConfirmation, promptUserInput, isSecretReference } from './utils.js';
15
15
 
16
16
 
17
17
  function validatePrereqsVersions() {
@@ -146,7 +146,6 @@ async function validateTools(token, tcId, region, skipPrompt) {
146
146
  const toolsWithHashedParams = [];
147
147
  const patTools = [];
148
148
  const classicPipelines = [];
149
- const secretPattern = /^hash:SHA3-512:[a-zA-Z0-9]{128}$/;
150
149
 
151
150
  for (const tool of allTools.tools) {
152
151
  const toolName = (tool.name || tool.parameters?.name || tool.parameters?.label || '').replace(/\s+/g, '+');
@@ -203,7 +202,7 @@ async function validateTools(token, tcId, region, skipPrompt) {
203
202
  url: toolUrl
204
203
  });
205
204
  }
206
- else if (['githubconsolidated', 'github_integrated', 'gitlab'].includes(tool.tool_type_id) && (tool.parameters?.auth_type === '' || tool.parameters?.auth_type === 'oauth')) { // Skip secret check iff it's GitHub/GitLab integration with OAuth
205
+ else if (['githubconsolidated', 'github_integrated', 'gitlab', 'hostedgit'].includes(tool.tool_type_id) && (tool.parameters?.auth_type === '' || tool.parameters?.auth_type === 'oauth')) { // Skip secret check iff it's GitHub/GitLab/GRIT integration with OAuth
207
206
  continue;
208
207
  }
209
208
  else {
@@ -212,20 +211,23 @@ async function validateTools(token, tcId, region, skipPrompt) {
212
211
  const pipelineData = await getPipelineData(token, tool.id, region);
213
212
 
214
213
  pipelineData.properties.forEach((prop) => {
215
- if (prop.type === 'secure' && secretPattern.test(prop.value)) secrets.push(['properties', prop.name].join('.').replace(/\s+/g, '+'));
214
+ if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0)
215
+ secrets.push(['properties', prop.name].join('.').replace(/\s+/g, '+'));
216
216
  });
217
217
 
218
218
  pipelineData.triggers.forEach((trigger) => {
219
- if ((trigger?.secret?.type === 'token_matches' || trigger?.secret?.type === 'digest_matches') && secretPattern.test(trigger.secret.value)) secrets.push([trigger.name, trigger.secret.key_name].join('.').replace(/\s+/g, '+'));
219
+ if ((trigger?.secret?.type === 'token_matches' || trigger?.secret?.type === 'digest_matches') && !isSecretReference(trigger.secret.value) && trigger.secret.value.length > 0)
220
+ secrets.push([trigger.name, trigger.secret.key_name].join('.').replace(/\s+/g, '+'));
220
221
  trigger.properties.forEach((prop) => {
221
- if (prop.type === 'secure' && secretPattern.test(prop.value)) secrets.push([trigger.name, 'properties', prop.name].join('.').replace(/\s+/g, '+'));
222
+ if (prop.type === 'secure' && !isSecretReference(prop.value) && prop.value.length > 0)
223
+ secrets.push([trigger.name, 'properties', prop.name].join('.').replace(/\s+/g, '+'));
222
224
  });
223
225
  });
224
226
  }
225
227
  else {
226
228
  const secretsToCheck = (SECRET_KEYS_MAP[tool.tool_type_id] || []).map((entry) => entry.key); // Check for secrets in the rest of the tools
227
229
  Object.entries(tool.parameters).forEach(([key, value]) => {
228
- if (secretPattern.test(value) && secretsToCheck.includes(key)) secrets.push(key);
230
+ if (!isSecretReference(value) && value.length > 0 && secretsToCheck.includes(key)) secrets.push(key);
229
231
  });
230
232
  }
231
233
  if (secrets.length > 0) {
@@ -260,7 +262,7 @@ async function validateTools(token, tcId, region, skipPrompt) {
260
262
  }
261
263
 
262
264
  if (toolsWithHashedParams.length > 0) {
263
- logger.warn('Warning! The following tools contain secrets that cannot be migrated, please use the \'check-secret\' command to export the secrets: \n', LOG_STAGES.setup, true);
265
+ logger.warn('Warning! The following tools contain secrets that cannot be migrated, please use the \'check-secrets\' command to export the secrets: \n', LOG_STAGES.setup, true);
264
266
  logger.table(toolsWithHashedParams);
265
267
  }
266
268
 
package/config.js CHANGED
@@ -126,90 +126,90 @@ Format:
126
126
  {
127
127
  key: str, // tool parameter key
128
128
  tfKey?: str, // terraform-equivalent key
129
- prereq?: { key: string, values: [string] }, // proceed only if tool parameter "prereq.key" is one of "values"
129
+ prereq?: { key: string, values: [string] }, // proceed only if tool parameter 'prereq.key' is one of 'values'
130
130
  required?: bool // is this key required for terraform?
131
131
  }
132
132
  ... which represents a secret/sensitive value
133
133
  */
134
134
  const SECRET_KEYS_MAP = {
135
- "artifactory": [
136
- { key: "token", tfKey: "token" }
135
+ 'artifactory': [
136
+ { key: 'token', tfKey: 'token' }
137
137
  ],
138
- "cloudobjectstorage": [
139
- { key: "cos_api_key", tfKey: "cos_api_key", prereq: { key: "auth_type", values: ["apikey"] } },
140
- { key: "hmac_access_key_id", tfKey: "hmac_access_key_id", prereq: { key: "auth_type", values: ["hmac"] } },
141
- { key: "hmac_secret_access_key", tfKey: "hmac_secret_access_key", prereq: { key: "auth_type", values: ["hmac"] } },
138
+ 'cloudobjectstorage': [
139
+ { key: 'cos_api_key', tfKey: 'cos_api_key', prereq: { key: 'auth_type', values: ['apikey'] } },
140
+ { key: 'hmac_access_key_id', tfKey: 'hmac_access_key_id', prereq: { key: 'auth_type', values: ['hmac'] } },
141
+ { key: 'hmac_secret_access_key', tfKey: 'hmac_secret_access_key', prereq: { key: 'auth_type', values: ['hmac'] } },
142
142
  ],
143
- "github_integrated": [
144
- { key: "api_token" } // no terraform equivalent
143
+ 'github_integrated': [
144
+ { key: 'api_token' } // no terraform equivalent
145
145
  ],
146
- "githubconsolidated": [
147
- { key: "api_token", tfKey: "api_token", prereq: { key: "auth_type", values: ["pat"] } },
146
+ 'githubconsolidated': [
147
+ { key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
148
148
  ],
149
- "gitlab": [
150
- { key: "api_token", tfKey: "api_token", prereq: { key: "auth_type", values: ["pat"] } },
149
+ 'gitlab': [
150
+ { key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
151
151
  ],
152
- "hashicorpvault": [
153
- { key: "token", tfKey: "token", prereq: { key: "authentication_method", values: ["github", "token"] } },
154
- { key: "role_id", tfKey: "role_id", prereq: { key: "authentication_method", values: ["approle"] } },
155
- { key: "secret_id", tfKey: "secret_id", prereq: { key: "authentication_method", values: ["approle"] } },
156
- { key: "password", tfKey: "password", prereq: { key: "authentication_method", values: ["userpass"] } },
152
+ 'hashicorpvault': [
153
+ { key: 'token', tfKey: 'token', prereq: { key: 'authentication_method', values: ['github', 'token'] } },
154
+ { key: 'role_id', tfKey: 'role_id', prereq: { key: 'authentication_method', values: ['approle'] } },
155
+ { key: 'secret_id', tfKey: 'secret_id', prereq: { key: 'authentication_method', values: ['approle'] } },
156
+ { key: 'password', tfKey: 'password', prereq: { key: 'authentication_method', values: ['userpass'] } },
157
157
  ],
158
- "hostedgit": [
159
- { key: "api_token", tfKey: "api_token", prereq: { key: "auth_type", values: ["pat"] } },
158
+ 'hostedgit': [
159
+ { key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
160
160
  ],
161
- "jenkins": [
162
- { key: "api_token", tfKey: "api_token" },
161
+ 'jenkins': [
162
+ { key: 'api_token', tfKey: 'api_token' },
163
163
  ],
164
- "jira": [
165
- { key: "password", tfKey: "api_token" },
164
+ 'jira': [
165
+ { key: 'password', tfKey: 'api_token' },
166
166
  ],
167
- "nexus": [
168
- { key: "token", tfKey: "token" },
167
+ 'nexus': [
168
+ { key: 'token', tfKey: 'token' },
169
169
  ],
170
- "pagerduty": [
171
- { key: "service_key", tfKey: "service_key", required: true },
170
+ 'pagerduty': [
171
+ { key: 'service_key', tfKey: 'service_key', required: true },
172
172
  ],
173
- "private_worker": [
174
- { key: "workerQueueCredentials", tfKey: "worker_queue_credentials", required: true },
173
+ 'private_worker': [
174
+ { key: 'workerQueueCredentials', tfKey: 'worker_queue_credentials', required: true },
175
175
  ],
176
- "saucelabs": [
177
- { key: "key", tfKey: "access_key", required: true },
176
+ 'saucelabs': [
177
+ { key: 'key', tfKey: 'access_key', required: true },
178
178
  ],
179
- "security_compliance": [
180
- { key: "scc_api_key", tfKey: "scc_api_key", prereq: { key: "use_profile_attachment", values: ["enabled"] } },
179
+ 'security_compliance': [
180
+ { key: 'scc_api_key', tfKey: 'scc_api_key', prereq: { key: 'use_profile_attachment', values: ['enabled'] } },
181
181
  ],
182
- "slack": [
183
- { key: "api_token", tfKey: "webhook", required: true },
182
+ 'slack': [
183
+ { key: 'api_token', tfKey: 'webhook', required: true },
184
184
  ],
185
- "sonarqube": [
186
- { key: "user_password", tfKey: "user_password" },
185
+ 'sonarqube': [
186
+ { key: 'user_password', tfKey: 'user_password' },
187
187
  ]
188
188
  };
189
189
 
190
190
  // maps tool parameter tool_type_id to terraform resource type
191
191
  const SUPPORTED_TOOLS_MAP = {
192
- "appconfig": "ibm_cd_toolchain_tool_appconfig",
193
- "artifactory": "ibm_cd_toolchain_tool_artifactory",
194
- "bitbucketgit": "ibm_cd_toolchain_tool_bitbucketgit",
195
- "private_worker": "ibm_cd_toolchain_tool_privateworker",
196
- "draservicebroker": "ibm_cd_toolchain_tool_devopsinsights",
197
- "eventnotifications": "ibm_cd_toolchain_tool_eventnotifications",
198
- "hostedgit": "ibm_cd_toolchain_tool_hostedgit",
199
- "githubconsolidated": "ibm_cd_toolchain_tool_githubconsolidated",
200
- "gitlab": "ibm_cd_toolchain_tool_gitlab",
201
- "hashicorpvault": "ibm_cd_toolchain_tool_hashicorpvault",
202
- "jenkins": "ibm_cd_toolchain_tool_jenkins",
203
- "jira": "ibm_cd_toolchain_tool_jira",
204
- "keyprotect": "ibm_cd_toolchain_tool_keyprotect",
205
- "nexus": "ibm_cd_toolchain_tool_nexus",
206
- "customtool": "ibm_cd_toolchain_tool_custom",
207
- "saucelabs": "ibm_cd_toolchain_tool_saucelabs",
208
- "secretsmanager": "ibm_cd_toolchain_tool_secretsmanager",
209
- "security_compliance": "ibm_cd_toolchain_tool_securitycompliance",
210
- "slack": "ibm_cd_toolchain_tool_slack",
211
- "sonarqube": "ibm_cd_toolchain_tool_sonarqube",
212
- "pipeline": "ibm_cd_toolchain_tool_pipeline"
192
+ 'appconfig': 'ibm_cd_toolchain_tool_appconfig',
193
+ 'artifactory': 'ibm_cd_toolchain_tool_artifactory',
194
+ 'bitbucketgit': 'ibm_cd_toolchain_tool_bitbucketgit',
195
+ 'private_worker': 'ibm_cd_toolchain_tool_privateworker',
196
+ 'draservicebroker': 'ibm_cd_toolchain_tool_devopsinsights',
197
+ 'eventnotifications': 'ibm_cd_toolchain_tool_eventnotifications',
198
+ 'hostedgit': 'ibm_cd_toolchain_tool_hostedgit',
199
+ 'githubconsolidated': 'ibm_cd_toolchain_tool_githubconsolidated',
200
+ 'gitlab': 'ibm_cd_toolchain_tool_gitlab',
201
+ 'hashicorpvault': 'ibm_cd_toolchain_tool_hashicorpvault',
202
+ 'jenkins': 'ibm_cd_toolchain_tool_jenkins',
203
+ 'jira': 'ibm_cd_toolchain_tool_jira',
204
+ 'keyprotect': 'ibm_cd_toolchain_tool_keyprotect',
205
+ 'nexus': 'ibm_cd_toolchain_tool_nexus',
206
+ 'customtool': 'ibm_cd_toolchain_tool_custom',
207
+ 'saucelabs': 'ibm_cd_toolchain_tool_saucelabs',
208
+ 'secretsmanager': 'ibm_cd_toolchain_tool_secretsmanager',
209
+ 'security_compliance': 'ibm_cd_toolchain_tool_securitycompliance',
210
+ 'slack': 'ibm_cd_toolchain_tool_slack',
211
+ 'sonarqube': 'ibm_cd_toolchain_tool_sonarqube',
212
+ 'pipeline': 'ibm_cd_toolchain_tool_pipeline'
213
213
  };
214
214
 
215
215
  const VAULT_REGEX = [
package/index.js CHANGED
@@ -10,11 +10,12 @@
10
10
 
11
11
  import { program } from 'commander';
12
12
  import * as commands from './cmd/index.js'
13
+ import packageJson from './package.json' with { type: "json" };
13
14
 
14
15
  program
15
- .name('index.js')
16
+ .name(process.env.npm_package_name ?? 'index.js')
16
17
  .description('Tools and utilities for the IBM Cloud Continuous Delivery service and resources.')
17
- .version('0.0.1')
18
+ .version(packageJson.version)
18
19
  .showHelpAfterError();
19
20
 
20
21
  for (let i in commands) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibm-cloud/cd-tools",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Tools and utilities for the IBM Cloud Continuous Delivery service and resources",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,7 @@
15
15
  "author": "IBM Corp.",
16
16
  "license": "Apache-2.0",
17
17
  "scripts": {
18
- "test": "mocha --require \"test/setup.js\" \"test/copy-toolchain/*.test.js\""
18
+ "test": "mocha --require \"test/setup.js\" --retries 3 --parallel \"test/copy-toolchain/*.test.js\""
19
19
  },
20
20
  "dependencies": {
21
21
  "@cdktf/hcl2json": "^0.21.0",
package/test/README.md CHANGED
@@ -30,11 +30,12 @@ Before running tests, ensure that you have completed the setup steps in the main
30
30
  ### Test Configuration
31
31
  You can customize the behavior of the tests by defining configuration properties in the `test/config/local.json` file.
32
32
 
33
- | Property | Type | Default | Description |
34
- | ------------------ | --------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
35
- | `TEST_DEBUG_MODE` | `boolean` | `false` | When set to `true`, files generated by test cases in `TEST_TEMP_DIR` and log files generated in `TEST_LOG_DIR` are preserved |
36
- | `TEST_TEMP_DIR` | `string` | `test/.tmp` | The directory to store temporary files generated by test cases |
37
- | `TEST_LOG_DIR` | `string` | `test/.logs` | The directory to store test run log files |
38
- | `IBMCLOUD_API_KEY` | `string` | `null` | The IBM Cloud API Key used to run the tests |
39
- | `LOG_DUMP` | `boolean` | `false` | When set to `true`, individual test case's process's log file generation is enabled |
40
- | `DISABLE_SPINNER` | `boolean` | `true` | When set to `true`, visual spinner is disabled across all test cases' processes |
33
+ | Property | Type | Default | Description |
34
+ | ------------------ | --------- | ------------------| ---------------------------------------------------------------------------------------------------------------------------- |
35
+ | `TEST_DEBUG_MODE` | `boolean` | `false` | When set to `true`, files generated by test cases in `TEST_TEMP_DIR` and log files generated in `TEST_LOG_DIR` are preserved |
36
+ | `TEST_TEMP_DIR` | `string` | `test/.tmp` | The directory to store temporary files generated by test cases |
37
+ | `TEST_LOG_DIR` | `string` | `test/.test-logs` | The directory to store test run log files |
38
+ | `IBMCLOUD_API_KEY` | `string` | `null` | The IBM Cloud API Key used to run the tests |
39
+ | `LOG_DUMP` | `boolean` | `false` | When set to `true`, individual test case's process's log file generation is enabled |
40
+ | `DISABLE_SPINNER` | `boolean` | `true` | When set to `true`, visual spinner is disabled across all test cases' processes |
41
+ | `VERBOSE_MODE` | `boolean` | `false` | When set to `true`, each test case's log output increases |
@@ -4,5 +4,6 @@
4
4
  "TEST_LOG_DIR": "test/.logs",
5
5
  "IBMCLOUD_API_KEY": "<YOUR IBMCLOUD API KEY>",
6
6
  "LOG_DUMP": true,
7
- "DISABLE_SPINNER": true
7
+ "DISABLE_SPINNER": true,
8
+ "VERBOSE_MODE": false
8
9
  }