@ibm-cloud/cd-tools 1.2.0 → 1.2.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,7 +16,7 @@ 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 { setTerraformEnv, initProviderFile, setupTerraformFiles, runTerraformInit, getNumResourcesPlanned, runTerraformApply, getNumResourcesCreated, getNewToolchainId } from './utils/terraform.js';
19
- import { getAccountId, getBearerToken, getIamAuthPolicies, getResourceGroupIdAndName, getToolchain } from './utils/requests.js';
19
+ import { getAccountId, getBearerToken, getCdInstanceByRegion, getIamAuthPolicies, getResourceGroupIdAndName, getToolchain } from './utils/requests.js';
20
20
  import { validatePrereqsVersions, validateTag, validateToolchainId, validateToolchainName, validateTools, validateOAuth, warnDuplicateName, validateGritUrl } from './utils/validate.js';
21
21
  import { importTerraform } from './utils/import-terraform.js';
22
22
 
@@ -26,11 +26,12 @@ process.on('exit', (code) => {
26
26
  if (code !== 0) logger.print(`Need help? Visit ${MIGRATION_DOC_URL} for more troubleshooting information.`);
27
27
  });
28
28
 
29
+ const TIME_SUFFIX = new Date().getTime();
29
30
  const LOGS_DIR = '.logs';
30
- const TEMP_DIR = '.migration-temp'
31
+ const TEMP_DIR = '.migration-temp-' + TIME_SUFFIX;
31
32
  const LOG_DUMP = process.env['LOG_DUMP'] === 'false' ? false : true; // when true or not specified, logs are also written to a log file in LOGS_DIR
32
33
  const DEBUG_MODE = process.env['DEBUG_MODE'] === 'true' ? true : false; // when true, temp folder is preserved
33
- const OUTPUT_DIR = 'output-' + new Date().getTime();
34
+ const OUTPUT_DIR = 'output-' + TIME_SUFFIX;
34
35
  const DRY_RUN = false; // when true, terraform apply does not run
35
36
 
36
37
 
@@ -100,6 +101,16 @@ async function main(options) {
100
101
  bearer = await getBearerToken(apiKey);
101
102
  const accountId = await getAccountId(bearer, apiKey);
102
103
 
104
+ // check for continuous delivery instance in target region
105
+ if (!await getCdInstanceByRegion(bearer, accountId, targetRegion)) throw Error(`Could not find a Continuous Delivery instance in the target region '${targetRegion}', please create one before proceeding.`);
106
+
107
+ // check for existing .tf files in output directory
108
+ if (fs.existsSync(outputDir)) {
109
+ let files = readdirSync(outputDir, { recursive: true });
110
+ files = files.filter((f) => f.endsWith('.tf'));
111
+ if (files.length > 0) throw Error(`Output directory already has ${files.length} '.tf' files, please specify a different output directory`);
112
+ }
113
+
103
114
  if (options.gritMappingFile) {
104
115
  gritMapping = JSON.parse(fs.readFileSync(resolve(options.gritMappingFile)));
105
116
  const gritPromises = [];
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import fs from 'node:fs';
11
- import { promisify } from 'node:util';
12
11
 
13
12
  import { parse as tfToJson } from '@cdktf/hcl2json'
14
13
  import { jsonToTf } from 'json-to-tf';
@@ -19,8 +18,6 @@ import { getRandChars, isSecretReference, normalizeName } from './utils.js';
19
18
 
20
19
  import { SECRET_KEYS_MAP, SUPPORTED_TOOLS_MAP } from '../../config.js';
21
20
 
22
- const writeFilePromise = promisify(fs.writeFile);
23
-
24
21
  export async function importTerraform(token, apiKey, region, toolchainId, toolchainName, policyIds, dir, isCompact, verbosity) {
25
22
  // STEP 1/2: set up terraform file with import blocks
26
23
  const importBlocks = []; // an array of objects representing import blocks, used in importBlocksToTf
@@ -150,7 +147,7 @@ export async function importTerraform(token, apiKey, region, toolchainId, toolch
150
147
  }
151
148
  }
152
149
 
153
- await importBlocksToTf(importBlocks, dir);
150
+ importBlocksToTf(importBlocks, dir);
154
151
 
155
152
  if (!fs.existsSync(`${dir}/generated`)) fs.mkdirSync(`${dir}/generated`);
156
153
 
@@ -296,7 +293,7 @@ export async function importTerraform(token, apiKey, region, toolchainId, toolch
296
293
  }
297
294
  } else {
298
295
  const generatedFileNew = jsonToTf(JSON.stringify(newTfFileObj));
299
- fs.writeFileSync(`${dir}/generated/generated.tf`, generatedFileNew);
296
+ fs.writeFileSync(`${dir}/generated/resources.tf`, generatedFileNew);
300
297
  }
301
298
 
302
299
  // remove draft
@@ -318,7 +315,7 @@ function importBlock(id, name, resourceType) {
318
315
  }
319
316
 
320
317
  // importBlocks array to tf file
321
- async function importBlocksToTf(blocks, dir) {
318
+ function importBlocksToTf(blocks, dir) {
322
319
  let fileContent = '';
323
320
 
324
321
  blocks.forEach((block) => {
@@ -329,5 +326,5 @@ async function importBlocksToTf(blocks, dir) {
329
326
  fileContent += template;
330
327
  });
331
328
 
332
- return await writeFilePromise(`${dir}/import.tf`, fileContent);
329
+ return fs.writeFileSync(`${dir}/import.tf`, fileContent);
333
330
  }
@@ -141,6 +141,31 @@ async function getToolchainsByName(bearer, accountId, toolchainName) {
141
141
  }
142
142
  }
143
143
 
144
+ async function getCdInstanceByRegion(bearer, accountId, region) {
145
+ const apiBaseUrl = 'https://api.global-search-tagging.cloud.ibm.com/v3';
146
+ const options = {
147
+ url: apiBaseUrl + '/resources/search',
148
+ method: 'POST',
149
+ headers: {
150
+ 'Authorization': `Bearer ${bearer}`,
151
+ 'Content-Type': 'application/json',
152
+ },
153
+ data: {
154
+ 'query': `service_name:continuous-delivery AND region:"${region}" AND doc.state:ACTIVE`,
155
+ 'fields': ['doc.resource_group_id', 'doc.region_id']
156
+ },
157
+ params: { account_id: accountId },
158
+ validateStatus: () => true
159
+ };
160
+ const response = await axios(options);
161
+ switch (response.status) {
162
+ case 200:
163
+ return response.data.items.length > 0;
164
+ default:
165
+ throw Error('Get CD instance failed');
166
+ }
167
+ }
168
+
144
169
  async function getToolchainTools(bearer, toolchainId, region) {
145
170
  const apiBaseUrl = `https://api.${region}.devops.cloud.ibm.com/toolchain/v2`;
146
171
  const options = {
@@ -388,6 +413,7 @@ async function deleteToolchain(bearer, toolchainId, region) {
388
413
  export {
389
414
  getBearerToken,
390
415
  getAccountId,
416
+ getCdInstanceByRegion,
391
417
  getToolchain,
392
418
  getToolchainsByName,
393
419
  getToolchainTools,
@@ -9,7 +9,6 @@
9
9
 
10
10
  import child_process from 'node:child_process';
11
11
  import fs from 'node:fs';
12
- import { randomInt } from 'node:crypto';
13
12
  import { promisify } from 'node:util';
14
13
 
15
14
  import { parse as tfToJson } from '@cdktf/hcl2json'
@@ -302,7 +301,7 @@ async function setupTerraformFiles({ token, srcRegion, targetRegion, targetTag,
302
301
  }
303
302
 
304
303
  async function runTerraformPlanGenerate(dir, fileName) {
305
- return await execPromise(`terraform plan -generate-config-out=${fileName}`, { cwd: dir });
304
+ return await execPromise(`terraform plan -generate-config-out="${fileName}"`, { cwd: dir });
306
305
  }
307
306
 
308
307
  async function runTerraformInit(dir) {
@@ -372,7 +372,7 @@ async function validateOAuth(token, tools, targetRegion, skipPrompt) {
372
372
 
373
373
  let hasFailedLink = false;
374
374
 
375
- logger.print('Authorize using the following links: \n');
375
+ if (oauthLinks.length > 0) logger.print('Authorize using the following links:\n');
376
376
  oauthLinks.forEach((o) => {
377
377
  if (o.link === 'Could not get OAuth link') hasFailedLink = true;
378
378
  logger.print(`${o.type}: \x1b[36m${o.link}\x1b[0m\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibm-cloud/cd-tools",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Tools and utilities for the IBM Cloud Continuous Delivery service and resources",
5
5
  "repository": {
6
6
  "type": "git",