@ibm-cloud/cd-tools 1.1.1 → 1.1.2

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.
@@ -16,8 +16,8 @@ import { Command, Option } from 'commander';
16
16
  import { parseEnvVar } from './utils/utils.js';
17
17
  import { logger, LOG_STAGES } from './utils/logger.js';
18
18
  import { setTerraformerEnv, setTerraformEnv, initProviderFile, runTerraformerImport, setupTerraformFiles, runTerraformInit, getNumResourcesPlanned, runTerraformApply, getNumResourcesCreated, getNewToolchainId } from './utils/terraform.js';
19
- import { getAccountId, getBearerToken, getResourceGroupId, getToolchain } from './utils/requests.js';
20
- import { validatePrereqsVersions, validateResourceGroupId, validateTag, validateToolchainId, validateToolchainName, validateTools, validateOAuth, warnDuplicateName, validateGritUrl } from './utils/validate.js';
19
+ import { getAccountId, getBearerToken, getResourceGroupIdAndName, getToolchain } from './utils/requests.js';
20
+ import { validatePrereqsVersions, validateTag, validateToolchainId, validateToolchainName, validateTools, validateOAuth, warnDuplicateName, validateGritUrl } from './utils/validate.js';
21
21
 
22
22
  import { COPY_TOOLCHAIN_DESC, MIGRATION_DOC_URL, TARGET_REGIONS, SOURCE_REGIONS } from '../config.js';
23
23
 
@@ -84,6 +84,7 @@ async function main(options) {
84
84
  let targetToolchainName = options.name;
85
85
  let targetTag = options.tag;
86
86
  let targetRgId;
87
+ let targetRgName;
87
88
  let apiKey = options.apikey;
88
89
  let moreTfResources = {};
89
90
  let gritMapping = {};
@@ -137,17 +138,11 @@ async function main(options) {
137
138
  exit(1);
138
139
  }
139
140
 
140
- if (!targetRg) {
141
- // reuse resource group if not provided
142
- targetRgId = sourceToolchainData['resource_group_id'];
143
- } else {
144
- targetRgId = await getResourceGroupId(bearer, accountId, targetRg);
145
- validateResourceGroupId(targetRgId);
146
- }
141
+ ({ id: targetRgId, name: targetRgName } = await getResourceGroupIdAndName(bearer, accountId, targetRg || sourceToolchainData['resource_group_id']));
147
142
 
148
143
  // reuse name if not provided
149
144
  if (!targetToolchainName) targetToolchainName = sourceToolchainData['name'];
150
- [targetToolchainName, targetTag] = await warnDuplicateName(bearer, accountId, targetToolchainName, sourceRegion, targetRegion, targetRgId, targetTag, skipUserConfirmation);
145
+ [targetToolchainName, targetTag] = await warnDuplicateName(bearer, accountId, targetToolchainName, sourceRegion, targetRegion, targetRgId, targetRgName, targetTag, skipUserConfirmation);
151
146
 
152
147
  const allTools = await logger.withSpinner(validateTools,
153
148
  'Validating Toolchain Tool(s)...',
@@ -185,7 +185,7 @@ async function getPipelineData(bearer, pipelineId, region) {
185
185
  }
186
186
 
187
187
  // takes in resource group ID or name
188
- async function getResourceGroupId(bearer, accountId, resourceGroup) {
188
+ async function getResourceGroupIdAndName(bearer, accountId, resourceGroup) {
189
189
  const apiBaseUrl = 'https://api.global-search-tagging.cloud.ibm.com/v3';
190
190
  const options = {
191
191
  url: apiBaseUrl + '/resources/search',
@@ -196,7 +196,7 @@ async function getResourceGroupId(bearer, accountId, resourceGroup) {
196
196
  },
197
197
  data: {
198
198
  'query': `type:resource-group AND (name:${resourceGroup} OR doc.id:${resourceGroup}) AND doc.state:ACTIVE`,
199
- 'fields': ['doc.id']
199
+ 'fields': ['doc.id', 'doc.name']
200
200
  },
201
201
  params: { account_id: accountId },
202
202
  validateStatus: () => true
@@ -205,7 +205,7 @@ async function getResourceGroupId(bearer, accountId, resourceGroup) {
205
205
  switch (response.status) {
206
206
  case 200:
207
207
  if (response.data.items.length != 1) throw Error('The resource group with provided ID or name was not found or is not accessible');
208
- return response.data.items[0].doc.id;
208
+ return { id: response.data.items[0].doc.id, name: response.data.items[0].doc.name };
209
209
  default:
210
210
  throw Error('The resource group with provided ID or name was not found or is not accessible');
211
211
  }
@@ -349,11 +349,11 @@ export {
349
349
  getToolchainsByName,
350
350
  getToolchainTools,
351
351
  getPipelineData,
352
- getResourceGroupId,
352
+ getResourceGroupIdAndName,
353
353
  getAppConfigHealthcheck,
354
354
  getSecretsHealthcheck,
355
355
  getGitOAuth,
356
356
  getGritUserProject,
357
357
  getGritGroup,
358
358
  getGritGroupProject
359
- }
359
+ }
@@ -25,7 +25,7 @@ export async function promptUserConfirmation(question, expectedAns, exitMsg) {
25
25
  output: process.stdout
26
26
  });
27
27
 
28
- const fullPrompt = question + `\n\nOnly '${expectedAns}' will be accepted to proceed.\n\nEnter a value: `;
28
+ const fullPrompt = question + `\n\nOnly '${expectedAns}' will be accepted to proceed. (Ctrl-C to abort)\n\nEnter a value: `;
29
29
  const answer = await rl.question(fullPrompt);
30
30
 
31
31
  logger.dump(fullPrompt + '\n' + answer + '\n');
@@ -38,7 +38,7 @@ export async function promptUserConfirmation(question, expectedAns, exitMsg) {
38
38
  }
39
39
 
40
40
  rl.close();
41
- logger.print('\n');
41
+ logger.print();
42
42
  }
43
43
 
44
44
  export async function promptUserInput(question, initialInput, validationFn) {
@@ -75,6 +75,7 @@ export async function promptUserInput(question, initialInput, validationFn) {
75
75
  }
76
76
 
77
77
  rl.close();
78
+ logger.print();
78
79
  return answer.trim();
79
80
  }
80
81
 
@@ -97,12 +98,12 @@ export function replaceUrlRegion(inputUrl, srcRegion, targetRegion) {
97
98
  *
98
99
  * @param {String} crn - The crn to decompose.
99
100
  **/
100
- export function decomposeCrn (crn) {
101
+ export function decomposeCrn(crn) {
101
102
  const crnParts = crn.split(':');
102
103
 
103
104
  // Remove the 'a/' segment.
104
105
  let accountId = crnParts[6];
105
- if(accountId) {
106
+ if (accountId) {
106
107
  accountId = accountId.split('/')[1];
107
108
  }
108
109
 
@@ -123,6 +124,6 @@ export function decomposeCrn (crn) {
123
124
  *
124
125
  * @param {String} value - The value to verify.
125
126
  **/
126
- export function isSecretReference (value) {
127
+ export function isSecretReference(value) {
127
128
  return !!(VAULT_REGEX.find(r => r.test(value)));
128
- };
129
+ };
@@ -9,29 +9,11 @@
9
9
 
10
10
  import { execSync } from 'child_process';
11
11
  import { logger, LOG_STAGES } from './logger.js'
12
- import { RESERVED_GRIT_PROJECT_NAMES, RESERVED_GRIT_GROUP_NAMES, RESERVED_GRIT_SUBGROUP_NAME, TERRAFORM_REQUIRED_VERSION, TERRAFORMER_REQUIRED_VERSION } from '../../config.js';
12
+ import { RESERVED_GRIT_PROJECT_NAMES, RESERVED_GRIT_GROUP_NAMES, RESERVED_GRIT_SUBGROUP_NAME, TERRAFORM_REQUIRED_VERSION, TERRAFORMER_REQUIRED_VERSION, UPDATEABLE_SECRET_PROPERTIES_BY_TOOL_TYPE } from '../../config.js';
13
13
  import { getToolchainsByName, getToolchainTools, getPipelineData, getAppConfigHealthcheck, getSecretsHealthcheck, getGitOAuth, getGritUserProject, getGritGroup, getGritGroupProject } from './requests.js';
14
14
  import { promptUserConfirmation, promptUserInput } from './utils.js';
15
15
 
16
16
 
17
- const SECRETS_MAP = {
18
- 'artifactory': ['token'],
19
- 'hashicorpvault': ['token', 'role_id', 'secret_id', 'password'],
20
- 'jenkins': ['webhook_url', 'api_token'],
21
- 'jira': ['api_token'],
22
- 'nexus': ['token'],
23
- 'pagerduty': ['service_key'],
24
- 'privateworker': ['worker_queue_credentials'],
25
- 'saucelabs': ['access_key'],
26
- 'securitycompliance': ['scc_api_key'],
27
- 'slack': ['webhook'],
28
- 'sonarqube': ['user_password'],
29
- 'gitlab': ['api_token'],
30
- 'githubconsolidated': ['api_token'],
31
- 'github_integrated': ['api_token']
32
- };
33
-
34
-
35
17
  function validatePrereqsVersions() {
36
18
  const compareVersions = (verInstalled, verRequired) => {
37
19
  const installedSplit = verInstalled.split('.').map(Number);
@@ -95,18 +77,6 @@ function validateToolchainName(tcName) {
95
77
  throw Error('Provided toolchain name is invalid');
96
78
  }
97
79
 
98
- function validateResourceGroupId(rgId) {
99
- if (typeof rgId != 'string') throw Error('Provided resource group ID is not a string');
100
- const trimmed = rgId.trim();
101
-
102
- // pattern from api docs
103
- const pattern = /^[0-9a-f]{32}$/;
104
- if (pattern.test(trimmed)) {
105
- return trimmed;
106
- }
107
- throw Error('Provided resource group ID is invalid');
108
- }
109
-
110
80
  function validateTag(tag) {
111
81
  if (typeof tag != 'string') throw Error('Provided resource group ID is not a string');
112
82
  const trimmed = tag.trim();
@@ -120,7 +90,7 @@ function validateTag(tag) {
120
90
  }
121
91
 
122
92
 
123
- async function warnDuplicateName(token, accountId, tcName, srcRegion, targetRegion, targetResourceGroupId, targetTag, skipPrompt) {
93
+ async function warnDuplicateName(token, accountId, tcName, srcRegion, targetRegion, targetResourceGroupId, targetResourceGroupName, targetTag, skipPrompt) {
124
94
  const toolchains = await getToolchainsByName(token, accountId, tcName);
125
95
 
126
96
  let hasSameRegion = false;
@@ -145,19 +115,19 @@ async function warnDuplicateName(token, accountId, tcName, srcRegion, targetRegi
145
115
 
146
116
  if (hasBoth) {
147
117
  // warning! prompt user to cancel, rename (e.g. add a suffix) or continue
148
- logger.warn('Warning! You have a toolchain with the same name within the target region and resource group! \n', LOG_STAGES.setup, true);
118
+ logger.warn(`\nWarning! A toolchain named '${tcName}' already exists in:\n - Region: ${targetRegion}\n - Resource Group: ${targetResourceGroupName} (${targetResourceGroupId})`, '', true);
149
119
 
150
120
  if (!skipPrompt) {
151
- newTcName = await promptUserInput('(Recommended) Change the cloned toolchain\'s name:\n', tcName, validateToolchainName);
121
+ newTcName = await promptUserInput(`\n(Recommended) Edit the cloned toolchain's name [default: ${tcName}] (Ctrl-C to abort):\n`, tcName, validateToolchainName);
152
122
  }
153
123
  } else {
154
124
  if (hasSameRegion) {
155
125
  // soft warning of confusion
156
- logger.warn('Warning! You have a toolchain with the same name within the target region!\n', LOG_STAGES.setup, true);
126
+ logger.warn(`\nWarning! A toolchain named '${tcName}' already exists in:\n - Region: ${targetRegion}`, '', true);
157
127
  }
158
128
  if (hasSameResourceGroup) {
159
129
  // soft warning of confusion
160
- logger.warn('Warning! You have a toolchain with the same name within the target resource group!\n', LOG_STAGES.setup, true);
130
+ logger.warn(`\nWarning! A toolchain named '${tcName}' already exists in:\n - Resource Group: ${targetResourceGroupName} (${targetResourceGroupId})`, '', true);
161
131
  }
162
132
  }
163
133
 
@@ -169,7 +139,7 @@ async function warnDuplicateName(token, accountId, tcName, srcRegion, targetRegi
169
139
  if (str.trim() === '') return null;
170
140
  return validateTag(str);
171
141
  }
172
- newTag = await promptUserInput('(Recommended) Add a tag to the cloned toolchain:\n', `cloned-from-${srcRegion}`, validateTagOrEmpty);
142
+ newTag = await promptUserInput('\n(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):\n', `cloned-from-${srcRegion}`, validateTagOrEmpty);
173
143
  }
174
144
  }
175
145
  return [newTcName, newTag];
@@ -264,7 +234,7 @@ async function validateTools(token, tcId, region, skipPrompt) {
264
234
  });
265
235
  }
266
236
  else {
267
- const secretsToCheck = SECRETS_MAP[tool.tool_type_id] || []; // Check for secrets in the rest of the tools
237
+ const secretsToCheck = UPDATEABLE_SECRET_PROPERTIES_BY_TOOL_TYPE[tool.tool_type_id] || []; // Check for secrets in the rest of the tools
268
238
  Object.entries(tool.parameters).forEach(([key, value]) => {
269
239
  if (secretPattern.test(value) && secretsToCheck.includes(key)) secrets.push(key);
270
240
  });
@@ -279,14 +249,17 @@ async function validateTools(token, tcId, region, skipPrompt) {
279
249
  }
280
250
  }
281
251
  }
282
- const invalid = nonConfiguredTools.length > 0 || patTools.length > 0 || classicPipelines.length > 0 || toolsWithHashedParams.length > 0;
252
+ const hasInvalidConfig = nonConfiguredTools.length > 0 || patTools.length > 0 || toolsWithHashedParams.length > 0;
283
253
 
284
- // Manually fail and reset spinner to prevent duplicate spinners
285
- if (invalid) {
286
- logger.failSpinner('Invalid tools found!');
287
- logger.resetSpinner();
254
+ if (classicPipelines.length > 0) {
255
+ logger.failSpinner('Unsupported tools found!');
256
+ logger.warn('Warning! Classic pipelines are currently not supported in migration:\n', LOG_STAGES.setup, true);
257
+ logger.table(classicPipelines);
288
258
  }
289
259
 
260
+ if (hasInvalidConfig) logger.failSpinner('Configuration problems found!');
261
+ if (classicPipelines.length > 0 || hasInvalidConfig) logger.resetSpinner(); // Manually reset spinner to prevent duplicate spinners
262
+
290
263
  if (nonConfiguredTools.length > 0) {
291
264
  logger.warn('Warning! The following tool(s) are not in configured state in toolchain, please reconfigure them before proceeding: \n', LOG_STAGES.setup, true);
292
265
  logger.table(nonConfiguredTools);
@@ -297,17 +270,13 @@ async function validateTools(token, tcId, region, skipPrompt) {
297
270
  logger.table(patTools);
298
271
  }
299
272
 
300
- if (classicPipelines.length > 0) {
301
- logger.warn('Warning! Classic pipelines are currently not supported in migration:\n', LOG_STAGES.setup, true);
302
- logger.table(classicPipelines);
303
- }
304
-
305
273
  if (toolsWithHashedParams.length > 0) {
306
274
  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);
307
275
  logger.table(toolsWithHashedParams);
308
276
  }
309
277
 
310
- if (!skipPrompt && invalid) await promptUserConfirmation('Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?', 'yes', 'Toolchain migration cancelled.');
278
+ if (!skipPrompt && (classicPipelines.length > 0 || hasInvalidConfig))
279
+ await promptUserConfirmation('Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?', 'yes', 'Toolchain migration cancelled.');
311
280
 
312
281
  return allTools.tools;
313
282
  }
@@ -414,7 +383,7 @@ async function validateOAuth(token, tools, targetRegion, skipPrompt) {
414
383
 
415
384
  logger.print('Authorize using the following links: \n');
416
385
  oauthLinks.forEach((o) => {
417
- logger.print(`${o.type}: \x1b[34m${o.link}\x1b[0m\n`);
386
+ logger.print(`${o.type}: \x1b[36m${o.link}\x1b[0m\n`);
418
387
  });
419
388
 
420
389
  if (!skipPrompt) await promptUserConfirmation('Caution: The above git tool integration(s) will not be properly configured post migration. Do you want to proceed?', 'yes', 'Toolchain migration cancelled.');
@@ -494,7 +463,6 @@ export {
494
463
  validatePrereqsVersions,
495
464
  validateToolchainId,
496
465
  validateToolchainName,
497
- validateResourceGroupId,
498
466
  validateTag,
499
467
  validateTools,
500
468
  validateOAuth,
package/config.js CHANGED
@@ -163,7 +163,8 @@ const UPDATEABLE_SECRET_PROPERTIES_BY_TOOL_TYPE = {
163
163
  "service_key"
164
164
  ],
165
165
  "private_worker": [
166
- "workerQueueCredentials"
166
+ "workerQueueCredentials",
167
+ "worker_queue_credentials"
167
168
  ],
168
169
  "saucelabs": [
169
170
  "key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibm-cloud/cd-tools",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Tools and utilities for the IBM Cloud Continuous Delivery service and resources",
5
5
  "repository": {
6
6
  "type": "git",