@ibm-cloud/cd-tools 1.2.3 → 1.2.5
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 +1 -1
- package/cmd/copy-toolchain.js +3 -2
- package/cmd/utils/requests.js +8 -1
- package/cmd/utils/terraform.js +9 -5
- package/config.js +59 -59
- package/index.js +3 -2
- package/package.json +2 -2
- package/test/README.md +9 -8
- package/test/config/local.template.json +2 -1
- package/test/copy-toolchain/functionalities.test.js +198 -0
- package/test/copy-toolchain/{validation.test.js → input-validation.test.js} +45 -15
- package/test/copy-toolchain/tf-import.test.js +117 -0
- package/test/copy-toolchain/tool-validation.test.js +107 -0
- package/test/data/mocks.js +21 -1
- package/test/data/test-toolchains.js +44 -6
- package/test/setup.js +22 -5
- package/test/utils/testUtils.js +109 -7
- package/test/copy-toolchain/import.test.js +0 -11
- package/test/copy-toolchain/terraform.test.js +0 -11
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:
|
|
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
|
|
package/cmd/copy-toolchain.js
CHANGED
|
@@ -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
|
};
|
|
@@ -303,7 +303,8 @@ async function main(options) {
|
|
|
303
303
|
'Running terraform init...',
|
|
304
304
|
'Terraform successfully initialized',
|
|
305
305
|
LOG_STAGES.tf,
|
|
306
|
-
outputDir
|
|
306
|
+
outputDir,
|
|
307
|
+
verbosity
|
|
307
308
|
);
|
|
308
309
|
|
|
309
310
|
logger.info(`DRY_RUN: ${dryRun}, running terraform apply...`, LOG_STAGES.tf);
|
package/cmd/utils/requests.js
CHANGED
|
@@ -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
|
|
413
|
+
case 204:
|
|
407
414
|
return toolchainId;
|
|
408
415
|
default:
|
|
409
416
|
throw Error(response.statusText);
|
package/cmd/utils/terraform.js
CHANGED
|
@@ -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
|
|
304
|
-
|
|
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
|
|
308
|
-
return await execPromise(
|
|
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
|
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
|
|
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
|
-
|
|
136
|
-
{ key:
|
|
135
|
+
'artifactory': [
|
|
136
|
+
{ key: 'token', tfKey: 'token' }
|
|
137
137
|
],
|
|
138
|
-
|
|
139
|
-
{ key:
|
|
140
|
-
{ key:
|
|
141
|
-
{ key:
|
|
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
|
-
|
|
144
|
-
{ key:
|
|
143
|
+
'github_integrated': [
|
|
144
|
+
{ key: 'api_token' } // no terraform equivalent
|
|
145
145
|
],
|
|
146
|
-
|
|
147
|
-
{ key:
|
|
146
|
+
'githubconsolidated': [
|
|
147
|
+
{ key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
|
|
148
148
|
],
|
|
149
|
-
|
|
150
|
-
{ key:
|
|
149
|
+
'gitlab': [
|
|
150
|
+
{ key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
|
|
151
151
|
],
|
|
152
|
-
|
|
153
|
-
{ key:
|
|
154
|
-
{ key:
|
|
155
|
-
{ key:
|
|
156
|
-
{ key:
|
|
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
|
-
|
|
159
|
-
{ key:
|
|
158
|
+
'hostedgit': [
|
|
159
|
+
{ key: 'api_token', tfKey: 'api_token', prereq: { key: 'auth_type', values: ['pat'] } },
|
|
160
160
|
],
|
|
161
|
-
|
|
162
|
-
{ key:
|
|
161
|
+
'jenkins': [
|
|
162
|
+
{ key: 'api_token', tfKey: 'api_token' },
|
|
163
163
|
],
|
|
164
|
-
|
|
165
|
-
{ key:
|
|
164
|
+
'jira': [
|
|
165
|
+
{ key: 'password', tfKey: 'api_token' },
|
|
166
166
|
],
|
|
167
|
-
|
|
168
|
-
{ key:
|
|
167
|
+
'nexus': [
|
|
168
|
+
{ key: 'token', tfKey: 'token' },
|
|
169
169
|
],
|
|
170
|
-
|
|
171
|
-
{ key:
|
|
170
|
+
'pagerduty': [
|
|
171
|
+
{ key: 'service_key', tfKey: 'service_key', required: true },
|
|
172
172
|
],
|
|
173
|
-
|
|
174
|
-
{ key:
|
|
173
|
+
'private_worker': [
|
|
174
|
+
{ key: 'workerQueueCredentials', tfKey: 'worker_queue_credentials', required: true },
|
|
175
175
|
],
|
|
176
|
-
|
|
177
|
-
{ key:
|
|
176
|
+
'saucelabs': [
|
|
177
|
+
{ key: 'key', tfKey: 'access_key', required: true },
|
|
178
178
|
],
|
|
179
|
-
|
|
180
|
-
{ key:
|
|
179
|
+
'security_compliance': [
|
|
180
|
+
{ key: 'scc_api_key', tfKey: 'scc_api_key', prereq: { key: 'use_profile_attachment', values: ['enabled'] } },
|
|
181
181
|
],
|
|
182
|
-
|
|
183
|
-
{ key:
|
|
182
|
+
'slack': [
|
|
183
|
+
{ key: 'api_token', tfKey: 'webhook', required: true },
|
|
184
184
|
],
|
|
185
|
-
|
|
186
|
-
{ key:
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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(
|
|
16
|
+
.name(packageJson.name)
|
|
16
17
|
.description('Tools and utilities for the IBM Cloud Continuous Delivery service and resources.')
|
|
17
|
-
.version(
|
|
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.
|
|
3
|
+
"version": "1.2.5",
|
|
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
|
|
34
|
-
| ------------------ | --------- |
|
|
35
|
-
| `TEST_DEBUG_MODE` | `boolean` | `false`
|
|
36
|
-
| `TEST_TEMP_DIR` | `string` | `test/.tmp`
|
|
37
|
-
| `TEST_LOG_DIR` | `string` | `test/.logs` | The directory to store test run log files |
|
|
38
|
-
| `IBMCLOUD_API_KEY` | `string` | `null`
|
|
39
|
-
| `LOG_DUMP` | `boolean` | `false`
|
|
40
|
-
| `DISABLE_SPINNER` | `boolean` | `true`
|
|
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 |
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed Materials - Property of IBM
|
|
3
|
+
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
+
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
+
* Contract with IBM Corp.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import nconf from 'nconf';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
|
|
14
|
+
import * as chai from 'chai';
|
|
15
|
+
chai.config.truncateThreshold = 0;
|
|
16
|
+
import { expect, assert } from 'chai';
|
|
17
|
+
|
|
18
|
+
import { assertPtyOutput, assertExecError, areFilesInDir, deleteCreatedToolchains, parseTcIdAndRegion } from '../utils/testUtils.js';
|
|
19
|
+
import { getBearerToken, getToolchain } from '../../cmd/utils/requests.js';
|
|
20
|
+
import { TEST_TOOLCHAINS, DEFAULT_RG_ID, R2R_CLI_RG_ID } from '../data/test-toolchains.js';
|
|
21
|
+
import { TARGET_REGIONS } from '../../config.js';
|
|
22
|
+
|
|
23
|
+
nconf.env('__');
|
|
24
|
+
nconf.file('local', 'test/config/local.json');
|
|
25
|
+
|
|
26
|
+
const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
|
|
27
|
+
const VERBOSE_MODE = nconf.get('VERBOSE_MODE');
|
|
28
|
+
const IBMCLOUD_API_KEY = nconf.get('IBMCLOUD_API_KEY');
|
|
29
|
+
|
|
30
|
+
const CLI_PATH = path.resolve('index.js');
|
|
31
|
+
const COMMAND = 'copy-toolchain';
|
|
32
|
+
|
|
33
|
+
const toolchainsToDelete = new Map();
|
|
34
|
+
after(async () => await deleteCreatedToolchains(toolchainsToDelete));
|
|
35
|
+
|
|
36
|
+
describe('copy-toolchain: Test functionalities', function () {
|
|
37
|
+
this.timeout('300s');
|
|
38
|
+
this.command = COMMAND;
|
|
39
|
+
const testCases = [
|
|
40
|
+
{
|
|
41
|
+
name: 'Terraform Version Verification',
|
|
42
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
43
|
+
expected: /✔ Terraform Version:/,
|
|
44
|
+
options: {
|
|
45
|
+
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
46
|
+
timeout: 10000
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Check if CD instance exists in target region',
|
|
51
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
52
|
+
expected: new RegExp(`Could not find a Continuous Delivery instance in the target region '${TARGET_REGIONS[0]}', please create one before proceeding.`),
|
|
53
|
+
options: {
|
|
54
|
+
exitCondition: `Could not find a Continuous Delivery instance in the target region '${TARGET_REGIONS[0]}', please create one before proceeding.`,
|
|
55
|
+
timeout: 10000,
|
|
56
|
+
env: { ...process.env, MOCK_ALL_REQUESTS: 'true', MOCK_GET_CD_INSTANCE_BY_REGION_SCENARIO: 'NOT_FOUND' }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Log file is created successfully',
|
|
61
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
62
|
+
expected: null,
|
|
63
|
+
options: {
|
|
64
|
+
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
65
|
+
timeout: 10000,
|
|
66
|
+
cwd: TEMP_DIR + '/' + 'log-file-is-created-successfully'
|
|
67
|
+
},
|
|
68
|
+
assertionFunc: () => areFilesInDir(TEMP_DIR + '/' + 'log-file-is-created-successfully', ['.log'])
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Force Flag bypasses all user prompts',
|
|
72
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-f'],
|
|
73
|
+
expected: null,
|
|
74
|
+
options: {
|
|
75
|
+
timeout: 120000,
|
|
76
|
+
},
|
|
77
|
+
assertionFunc: async (output) => {
|
|
78
|
+
// Should bypass everything and clone the toolchain
|
|
79
|
+
output.match(/See cloned toolchain:/);
|
|
80
|
+
const { toolchainId, region } = parseTcIdAndRegion(output);
|
|
81
|
+
const token = await getBearerToken(IBMCLOUD_API_KEY);
|
|
82
|
+
const toolchainData = await getToolchain(token, toolchainId, region);
|
|
83
|
+
assert.isTrue(toolchainData.id === toolchainId, 'Was toolchain created successfully without any confirmations?');
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Prompt User when toolchain name already exists in resource group',
|
|
88
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
89
|
+
expected: new RegExp(`Warning! A toolchain named \'${TEST_TOOLCHAINS['empty'].name}\' already exists in:[\\s\\S]*?Resource Group:[\\s\\S]*?${R2R_CLI_RG_ID}`),
|
|
90
|
+
options: {
|
|
91
|
+
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
92
|
+
timeout: 10000
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Prompt User when toolchain name already exists in region',
|
|
97
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-g', DEFAULT_RG_ID],
|
|
98
|
+
expected: new RegExp(`Warning! A toolchain named \'${TEST_TOOLCHAINS['empty'].name}\' already exists in:[\\s\\S]*?Region: ${TEST_TOOLCHAINS['empty'].region}`),
|
|
99
|
+
options: {
|
|
100
|
+
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
101
|
+
timeout: 10000
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Dry Run Flag does not clone a toolchain',
|
|
106
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-D'],
|
|
107
|
+
expected: null,
|
|
108
|
+
options: {
|
|
109
|
+
timeout: 100000,
|
|
110
|
+
questionAnswerMap: {
|
|
111
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
112
|
+
[`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]: '',
|
|
113
|
+
},
|
|
114
|
+
cwd: TEMP_DIR + '/' + 'dry-run-flag-does-not-clone-a-toolchain'
|
|
115
|
+
},
|
|
116
|
+
assertionFunc: (output) => {
|
|
117
|
+
expect(output).to.match(/DRY_RUN: true, skipping terraform apply/);
|
|
118
|
+
assert.isTrue(areFilesInDir(TEMP_DIR + '/' + 'dry-run-flag-does-not-clone-a-toolchain', ['cd_toolchain.tf', 'output.tf']));
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'Silent flag suppresses info, debug and log messages',
|
|
123
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-s'],
|
|
124
|
+
expected: null,
|
|
125
|
+
options: {
|
|
126
|
+
timeout: 100000,
|
|
127
|
+
questionAnswerMap: {
|
|
128
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
129
|
+
[`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]: '',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
assertionFunc: (output) => {
|
|
133
|
+
// finds any [INFO] level logs that matches '[INFO] ...' but not '[INFO] See cloned toolchain...'
|
|
134
|
+
expect(output).to.not.match(/^(?!.*\[INFO\]\s+See cloned toolchain).*\[INFO\].*$/m);
|
|
135
|
+
|
|
136
|
+
expect(output).to.not.match(/\[DEBUG\]/);
|
|
137
|
+
expect(output).to.not.match(/\[LOG\]/);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'Compact flag only generates one terraform file',
|
|
142
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['single-pl'].crn, '-r', TEST_TOOLCHAINS['single-pl'].region, '-D', '-f', '-C'],
|
|
143
|
+
expected: null,
|
|
144
|
+
options: {
|
|
145
|
+
timeout: 100000,
|
|
146
|
+
cwd: TEMP_DIR + '/' + 'compact-flag-only-generates-one-tf-file'
|
|
147
|
+
},
|
|
148
|
+
assertionFunc: () => {
|
|
149
|
+
// check only resources.tf is created
|
|
150
|
+
assert.isFalse(
|
|
151
|
+
areFilesInDir(TEMP_DIR + '/' + 'compact-flag-only-generates-one-tf-file', [
|
|
152
|
+
'cd_toolchain.tf',
|
|
153
|
+
'cd_toolchain_tool_pipeline.tf',
|
|
154
|
+
'cd_tekton_pipeline.tf',
|
|
155
|
+
])
|
|
156
|
+
);
|
|
157
|
+
assert.isTrue(areFilesInDir(TEMP_DIR + '/' + 'compact-flag-only-generates-one-tf-file', ['resources.tf']));
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'Prompt user when OAuth does not exist for Git tool in target region',
|
|
162
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['single-pl'].crn, '-r', TEST_TOOLCHAINS['single-pl'].region, '-D'],
|
|
163
|
+
expected: /Warning! The following git tool integration\(s\) are not authorized in the target region/,
|
|
164
|
+
options: {
|
|
165
|
+
timeout: 60000,
|
|
166
|
+
questionAnswerMap: {
|
|
167
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
168
|
+
[`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['single-pl'].name}] (Ctrl-C to abort):`]: '',
|
|
169
|
+
'Only \'yes\' will be accepted to proceed. (Ctrl-C to abort)': 'yes'
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const { name, cmd, expected, options, assertionFunc } of testCases) {
|
|
176
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
177
|
+
it(`${name}`, async () => {
|
|
178
|
+
const res = await assertPtyOutput(cmd, expected, options, assertionFunc);
|
|
179
|
+
if (res) toolchainsToDelete.set(res.toolchainId, res.region);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
it('Check for existing .tf files in output directory', async () => {
|
|
184
|
+
const testDir = path.resolve(TEMP_DIR, 'check-for-existing-tf-files-in-out-dir');
|
|
185
|
+
const tfFilePath = path.resolve(testDir, 'empty.tf');
|
|
186
|
+
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });
|
|
187
|
+
fs.writeFileSync(tfFilePath, '');
|
|
188
|
+
|
|
189
|
+
const cmd = [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0], '-d', testDir];
|
|
190
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
191
|
+
|
|
192
|
+
await assertExecError(
|
|
193
|
+
cmd,
|
|
194
|
+
/Output directory already has 1 '.tf' files, please specify a different output directory/,
|
|
195
|
+
{ cwd: testDir }
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -9,31 +9,32 @@
|
|
|
9
9
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import nconf from 'nconf';
|
|
12
|
+
import fs from 'node:fs';
|
|
12
13
|
|
|
13
14
|
import * as chai from 'chai';
|
|
15
|
+
import { expect } from 'chai';
|
|
14
16
|
chai.config.truncateThreshold = 0;
|
|
15
17
|
|
|
16
18
|
import mocks from '../data/mocks.js';
|
|
17
|
-
import {
|
|
19
|
+
import { assertExecError, assertPtyOutput } from '../utils/testUtils.js';
|
|
18
20
|
import { TEST_TOOLCHAINS } from '../data/test-toolchains.js';
|
|
19
21
|
import { TARGET_REGIONS } from '../../config.js';
|
|
20
22
|
|
|
21
23
|
nconf.env('__');
|
|
22
24
|
nconf.file('local', 'test/config/local.json');
|
|
23
25
|
|
|
26
|
+
const VERBOSE_MODE = nconf.get('VERBOSE_MODE');
|
|
27
|
+
const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
|
|
28
|
+
|
|
24
29
|
const CLI_PATH = path.resolve('index.js');
|
|
25
30
|
const COMMAND = 'copy-toolchain';
|
|
26
31
|
|
|
27
|
-
const toolchainsToDelete = new Map();
|
|
28
|
-
|
|
29
|
-
after(async () => await testSuiteCleanup(toolchainsToDelete));
|
|
30
32
|
|
|
31
33
|
describe('copy-toolchain: Test user input handling', function () {
|
|
32
|
-
this.timeout('
|
|
33
|
-
this.command =
|
|
34
|
+
this.timeout('120s');
|
|
35
|
+
this.command = COMMAND;
|
|
34
36
|
|
|
35
37
|
const validCrn = TEST_TOOLCHAINS['empty'].crn;
|
|
36
|
-
|
|
37
38
|
const invalidArgsCases = [
|
|
38
39
|
{
|
|
39
40
|
name: 'Toolchain CRN not specified',
|
|
@@ -87,23 +88,51 @@ describe('copy-toolchain: Test user input handling', function () {
|
|
|
87
88
|
cmd: [CLI_PATH, COMMAND, '-c', validCrn, '-r', TARGET_REGIONS[0], '-g', mocks.invalidRgId],
|
|
88
89
|
expected: /The resource group with provided ID or name was not found or is not accessible/,
|
|
89
90
|
},
|
|
91
|
+
{
|
|
92
|
+
name: 'Non-existent GRIT mapping file provided',
|
|
93
|
+
cmd: [CLI_PATH, COMMAND, '-c', validCrn, '-r', TARGET_REGIONS[0], '-G', 'non-existent.json'],
|
|
94
|
+
expected: /ENOENT: no such file or directory/
|
|
95
|
+
}
|
|
90
96
|
];
|
|
91
97
|
|
|
92
|
-
for (const { name, cmd, expected, options } of invalidArgsCases) {
|
|
98
|
+
for (const { name, cmd, expected, options, assertionFn } of invalidArgsCases) {
|
|
99
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
93
100
|
it(`Invalid args: ${name}`, async () => {
|
|
94
|
-
await
|
|
101
|
+
await assertExecError(cmd, expected, options, assertionFn);
|
|
95
102
|
});
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
it('Invalid GRIT URL mapping provided in file', async () => {
|
|
106
|
+
|
|
107
|
+
const gritTestDir = path.resolve(TEMP_DIR, 'invalid-grit-mapping-provided-in-file');
|
|
108
|
+
const gritMappingFilePath = path.resolve(gritTestDir, mocks.invalidGritFileName);
|
|
109
|
+
|
|
110
|
+
if (!fs.existsSync(gritTestDir)) fs.mkdirSync(gritTestDir, { recursive: true });
|
|
111
|
+
fs.writeFileSync(gritMappingFilePath, JSON.stringify(mocks.invalidGritMapping, null, 2));
|
|
112
|
+
|
|
113
|
+
const cmd = [CLI_PATH, COMMAND, '-c', validCrn, '-r', TARGET_REGIONS[0], '-G', mocks.invalidGritFileName];
|
|
114
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
115
|
+
|
|
116
|
+
await assertExecError(
|
|
117
|
+
cmd,
|
|
118
|
+
null,
|
|
119
|
+
{ cwd: gritTestDir },
|
|
120
|
+
(err) => {
|
|
121
|
+
expect(err).to.match(/Error: Provided full GRIT url is not valid/);
|
|
122
|
+
expect(err).to.match(/One or more invalid entries in GRIT mapping file, error count: 2/);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
98
127
|
const invalidUserInputCases = [
|
|
99
128
|
{
|
|
100
129
|
name: 'Invalid Toolchain tag is provided',
|
|
101
130
|
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
102
131
|
expected: /Provided tag is invalid/,
|
|
103
132
|
options: {
|
|
104
|
-
questionAnswerMap: { '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):'
|
|
133
|
+
questionAnswerMap: { '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': mocks.invalidTag },
|
|
105
134
|
exitCondition: 'Validation failed',
|
|
106
|
-
timeout:
|
|
135
|
+
timeout: 10000
|
|
107
136
|
}
|
|
108
137
|
},
|
|
109
138
|
{
|
|
@@ -111,16 +140,17 @@ describe('copy-toolchain: Test user input handling', function () {
|
|
|
111
140
|
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
112
141
|
expected: /Provided toolchain name is invalid/,
|
|
113
142
|
options: {
|
|
114
|
-
questionAnswerMap: { [`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]
|
|
143
|
+
questionAnswerMap: { [`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]: mocks.invalidTcName },
|
|
115
144
|
exitCondition: 'Validation failed',
|
|
116
|
-
timeout:
|
|
145
|
+
timeout: 10000
|
|
117
146
|
}
|
|
118
|
-
}
|
|
147
|
+
}
|
|
119
148
|
];
|
|
120
149
|
|
|
121
150
|
for (const { name, cmd, expected, options } of invalidUserInputCases) {
|
|
151
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
122
152
|
it(`Invalid user input in prompts: ${name}`, async () => {
|
|
123
|
-
await
|
|
153
|
+
await assertPtyOutput(cmd, expected, options);
|
|
124
154
|
});
|
|
125
155
|
}
|
|
126
156
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed Materials - Property of IBM
|
|
3
|
+
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
+
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
+
* Contract with IBM Corp.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import nconf from 'nconf';
|
|
12
|
+
|
|
13
|
+
import * as chai from 'chai';
|
|
14
|
+
chai.config.truncateThreshold = 0;
|
|
15
|
+
|
|
16
|
+
import { assertTfResourcesInDir, assertPtyOutput } from '../utils/testUtils.js';
|
|
17
|
+
import { TEST_TOOLCHAINS } from '../data/test-toolchains.js';
|
|
18
|
+
|
|
19
|
+
nconf.env('__');
|
|
20
|
+
nconf.file('local', 'test/config/local.json');
|
|
21
|
+
|
|
22
|
+
const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
|
|
23
|
+
const VERBOSE_MODE = nconf.get('VERBOSE_MODE');
|
|
24
|
+
|
|
25
|
+
const CLI_PATH = path.resolve('index.js');
|
|
26
|
+
const COMMAND = 'copy-toolchain';
|
|
27
|
+
|
|
28
|
+
describe('copy-toolchain: Test import-terraform output', function () {
|
|
29
|
+
this.timeout('300s');
|
|
30
|
+
this.command = COMMAND;
|
|
31
|
+
|
|
32
|
+
const testCases = [
|
|
33
|
+
{
|
|
34
|
+
name: 'Import 1PL-GHE-CC Toolchain',
|
|
35
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['1pl-ghe-cc'].crn, '-r', TEST_TOOLCHAINS['1pl-ghe-cc'].region, '-D', '-f'],
|
|
36
|
+
expected: null,
|
|
37
|
+
options: {
|
|
38
|
+
timeout: 100000,
|
|
39
|
+
cwd: TEMP_DIR + '/' + 'import-1pl-ghe-cc-toolchain'
|
|
40
|
+
},
|
|
41
|
+
assertionFunc: async () => {
|
|
42
|
+
await assertTfResourcesInDir(TEMP_DIR + '/' + 'import-1pl-ghe-cc-toolchain', {
|
|
43
|
+
ibm_cd_tekton_pipeline: 1,
|
|
44
|
+
ibm_cd_tekton_pipeline_definition: 1,
|
|
45
|
+
ibm_cd_tekton_pipeline_property: 1,
|
|
46
|
+
ibm_cd_tekton_pipeline_trigger: 1,
|
|
47
|
+
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
48
|
+
ibm_cd_toolchain: 1,
|
|
49
|
+
ibm_cd_toolchain_tool_custom: 1,
|
|
50
|
+
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
51
|
+
ibm_cd_toolchain_tool_pipeline: 1,
|
|
52
|
+
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
53
|
+
ibm_cd_toolchain_tool_slack: 1,
|
|
54
|
+
ibm_iam_authorization_policy: 1
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Import 1PL-GHE-CD Toolchain',
|
|
60
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['1pl-ghe-cd'].crn, '-r', TEST_TOOLCHAINS['1pl-ghe-cd'].region, '-D', '-f'],
|
|
61
|
+
expected: null,
|
|
62
|
+
options: {
|
|
63
|
+
timeout: 100000,
|
|
64
|
+
cwd: TEMP_DIR + '/' + 'import-1pl-ghe-cd-toolchain'
|
|
65
|
+
},
|
|
66
|
+
assertionFunc: async () => {
|
|
67
|
+
await assertTfResourcesInDir(TEMP_DIR + '/' + 'import-1pl-ghe-cd-toolchain', {
|
|
68
|
+
ibm_cd_tekton_pipeline: 1,
|
|
69
|
+
ibm_cd_tekton_pipeline_definition: 1,
|
|
70
|
+
ibm_cd_tekton_pipeline_property: 1,
|
|
71
|
+
ibm_cd_tekton_pipeline_trigger: 1,
|
|
72
|
+
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
73
|
+
ibm_cd_toolchain: 1,
|
|
74
|
+
ibm_cd_toolchain_tool_custom: 1,
|
|
75
|
+
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
76
|
+
ibm_cd_toolchain_tool_pipeline: 1,
|
|
77
|
+
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
78
|
+
ibm_cd_toolchain_tool_slack: 1,
|
|
79
|
+
ibm_iam_authorization_policy: 1
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Import DevSecOps-GRIT-CI Toolchain',
|
|
85
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['devsecops-grit-ci'].crn, '-r', TEST_TOOLCHAINS['devsecops-grit-ci'].region, '-D', '-f'],
|
|
86
|
+
expected: null,
|
|
87
|
+
options: {
|
|
88
|
+
timeout: 100000,
|
|
89
|
+
cwd: TEMP_DIR + '/' + 'import-devsecops-grit-ci-toolchain'
|
|
90
|
+
},
|
|
91
|
+
assertionFunc: async () => {
|
|
92
|
+
await assertTfResourcesInDir(TEMP_DIR + '/' + 'import-devsecops-grit-ci-toolchain', {
|
|
93
|
+
ibm_cd_tekton_pipeline: 1,
|
|
94
|
+
ibm_cd_tekton_pipeline_definition: 1,
|
|
95
|
+
ibm_cd_tekton_pipeline_property: 1,
|
|
96
|
+
ibm_cd_tekton_pipeline_trigger: 1,
|
|
97
|
+
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
98
|
+
ibm_cd_toolchain: 1,
|
|
99
|
+
ibm_cd_toolchain_tool_custom: 1,
|
|
100
|
+
ibm_cd_toolchain_tool_devopsinsights: 1,
|
|
101
|
+
ibm_cd_toolchain_tool_hostedgit: 1,
|
|
102
|
+
ibm_cd_toolchain_tool_pipeline: 1,
|
|
103
|
+
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
104
|
+
ibm_cd_toolchain_tool_slack: 1,
|
|
105
|
+
ibm_iam_authorization_policy: 1
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const { name, cmd, expected, options, assertionFunc } of testCases) {
|
|
112
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
113
|
+
it(`${name}`, async () => {
|
|
114
|
+
await assertPtyOutput(cmd, expected, options, assertionFunc);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed Materials - Property of IBM
|
|
3
|
+
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
+
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
+
* Contract with IBM Corp.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import nconf from 'nconf';
|
|
12
|
+
|
|
13
|
+
import * as chai from 'chai';
|
|
14
|
+
chai.config.truncateThreshold = 0;
|
|
15
|
+
import { expect } from 'chai';
|
|
16
|
+
|
|
17
|
+
import { assertPtyOutput, deleteCreatedToolchains } from '../utils/testUtils.js';
|
|
18
|
+
import { TEST_TOOLCHAINS } from '../data/test-toolchains.js';
|
|
19
|
+
import { TARGET_REGIONS } from '../../config.js';
|
|
20
|
+
|
|
21
|
+
nconf.env('__');
|
|
22
|
+
nconf.file('local', 'test/config/local.json');
|
|
23
|
+
|
|
24
|
+
const VERBOSE_MODE = nconf.get('VERBOSE_MODE');
|
|
25
|
+
|
|
26
|
+
const CLI_PATH = path.resolve('index.js');
|
|
27
|
+
const COMMAND = 'copy-toolchain';
|
|
28
|
+
|
|
29
|
+
const toolchainsToDelete = new Map();
|
|
30
|
+
after(async () => await deleteCreatedToolchains(toolchainsToDelete));
|
|
31
|
+
|
|
32
|
+
describe('copy-toolchain: Test tool validation', function () {
|
|
33
|
+
this.timeout('300s');
|
|
34
|
+
this.command = COMMAND;
|
|
35
|
+
const testCases = [
|
|
36
|
+
{
|
|
37
|
+
name: 'Misconfigured tool identified',
|
|
38
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['1pl-ghe-cd'].crn, '-r', TARGET_REGIONS[10]],
|
|
39
|
+
expected: /slack[\s\S]*?misconfigured/,
|
|
40
|
+
options: {
|
|
41
|
+
exitCondition: 'Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?',
|
|
42
|
+
questionAnswerMap: {
|
|
43
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
44
|
+
},
|
|
45
|
+
timeout: 30000
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Tools with plain text secrets identified',
|
|
50
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['1pl-ghe-cd'].crn, '-r', TARGET_REGIONS[10]],
|
|
51
|
+
expected: null,
|
|
52
|
+
options: {
|
|
53
|
+
exitCondition: 'Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?',
|
|
54
|
+
questionAnswerMap: {
|
|
55
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
56
|
+
},
|
|
57
|
+
timeout: 30000
|
|
58
|
+
},
|
|
59
|
+
assertionFunc: (output) => {
|
|
60
|
+
expect(output).to.match(/Warning! The following tools contain secrets that cannot be migrated/);
|
|
61
|
+
expect(output).to.match(/cloudobjectstorage[\s\S]*?cos_api_key/);
|
|
62
|
+
expect(output).to.match(/slack[\s\S]*?api_token/);
|
|
63
|
+
expect(output).to.match(/pipeline[\s\S]*?properties.doi-ibmcloud-api-key/);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Classic pipelines are identified',
|
|
68
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['misconfigured'].crn, '-r', TARGET_REGIONS[10]],
|
|
69
|
+
expected: /Warning! Classic pipelines are currently not supported in migration/,
|
|
70
|
+
options: {
|
|
71
|
+
exitCondition: 'Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?',
|
|
72
|
+
questionAnswerMap: {
|
|
73
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
74
|
+
},
|
|
75
|
+
timeout: 30000
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'Git tools using PAT are identified',
|
|
80
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['misconfigured'].crn, '-r', TARGET_REGIONS[10]],
|
|
81
|
+
expected: null,
|
|
82
|
+
options: {
|
|
83
|
+
exitCondition: 'Caution: The above tool(s) will not be properly configured post migration. Do you want to proceed?',
|
|
84
|
+
questionAnswerMap: {
|
|
85
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': '',
|
|
86
|
+
},
|
|
87
|
+
timeout: 30000
|
|
88
|
+
},
|
|
89
|
+
assertionFunc: (output) => {
|
|
90
|
+
expect(output).to.match(/Warning! The following GRIT integration\(s\) are using auth_type "pat", please switch to auth_type "oauth" before proceeding/);
|
|
91
|
+
expect(output).to.match(/hostedgit/);
|
|
92
|
+
expect(output).to.match(/Warning! The following tools contain secrets that cannot be migrated/);
|
|
93
|
+
expect(output).to.match(/githubconsolidated[\s\S]*?api_token/);
|
|
94
|
+
expect(output).to.match(/github_integrated[\s\S]*?api_token/);
|
|
95
|
+
expect(output).to.match(/gitlab[\s\S]*?api_token/);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const { name, cmd, expected, options, assertionFunc } of testCases) {
|
|
101
|
+
if (VERBOSE_MODE) cmd.push('-v');
|
|
102
|
+
it(`${name}`, async () => {
|
|
103
|
+
const res = await assertPtyOutput(cmd, expected, options, assertionFunc);
|
|
104
|
+
if (res) toolchainsToDelete.set(res.toolchainId, res.region);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
package/test/data/mocks.js
CHANGED
|
@@ -19,11 +19,31 @@ const invalidRgId = 'invalid#RgId';
|
|
|
19
19
|
|
|
20
20
|
const invalidRgName = 'invalid#Rg@Name';
|
|
21
21
|
|
|
22
|
+
const invalidGritMapping = {
|
|
23
|
+
'ca-tor.git.cloud.ibm.com/fake-user/fake-repo': 'eu-gb.git.cloud.ibm.com/fake-user/fake-repo',
|
|
24
|
+
'ibm.com/fake-user/fake-repo': 'ibm.com/fake-user/fake-repo'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const invalidGritFileName = 'invalid-mapping.json';
|
|
28
|
+
|
|
29
|
+
const getCdInstanceByRegionResponses = {
|
|
30
|
+
'NOT_FOUND': {
|
|
31
|
+
status: 200,
|
|
32
|
+
data: {
|
|
33
|
+
items: [],
|
|
34
|
+
limit: 10
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
22
39
|
export default {
|
|
23
40
|
invalidCrn,
|
|
24
41
|
invalidRegion,
|
|
25
42
|
invalidTcName,
|
|
26
43
|
invalidTag,
|
|
27
44
|
invalidRgId,
|
|
28
|
-
invalidRgName
|
|
45
|
+
invalidRgName,
|
|
46
|
+
invalidGritMapping,
|
|
47
|
+
invalidGritFileName,
|
|
48
|
+
getCdInstanceByRegionResponses
|
|
29
49
|
};
|
|
@@ -9,13 +9,51 @@
|
|
|
9
9
|
|
|
10
10
|
export const TEST_TOOLCHAINS = {
|
|
11
11
|
'empty': {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
name: 'KEEP-EMPTY-TOOLCHAIN',
|
|
13
|
+
crn: 'crn:v1:bluemix:public:toolchain:ca-tor:a/9e8559fac61ee9fc74d3e595fa75d147:0100aa9f-1e57-41d8-b4c7-5d84178d59bb::',
|
|
14
|
+
region: 'ca-tor',
|
|
15
15
|
},
|
|
16
16
|
'misconfigured': {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
name: 'KEEP-MISCONFIGURED-TOOLCHAIN',
|
|
18
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:0ccfaa70-ca90-47db-8246-f4ecfc6ad8f3::',
|
|
19
|
+
region: 'eu-es'
|
|
20
|
+
},
|
|
21
|
+
'1pl-ghe-cc': {
|
|
22
|
+
name: 'KEEP-1PL-GHE-CC',
|
|
23
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:9d51bb6b-f659-4ab7-9bc4-2eae1d61f4e7::',
|
|
24
|
+
region: 'eu-es'
|
|
25
|
+
},
|
|
26
|
+
'1pl-ghe-cd': {
|
|
27
|
+
name: 'KEEP-1PL-GHE-CD',
|
|
28
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:6a70313f-a927-4b0e-8471-70f17330998d::',
|
|
29
|
+
region: 'eu-es'
|
|
30
|
+
},
|
|
31
|
+
'1pl-ghe-ci': {
|
|
32
|
+
name: 'KEEP-1PL-GHE-CI',
|
|
33
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:6b8e27ae-5924-4a38-8819-f405366cb900::',
|
|
34
|
+
region: 'eu-es'
|
|
35
|
+
},
|
|
36
|
+
'devsecops-grit-cc': {
|
|
37
|
+
name: 'KEEP-DevSecOps-GRIT-CC',
|
|
38
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:920f6a94-4c1b-412b-b95c-baf823958744::',
|
|
39
|
+
region: 'eu-es'
|
|
40
|
+
},
|
|
41
|
+
'devsecops-grit-cd': {
|
|
42
|
+
name: 'KEEP-DevSecOps-GRIT-CD',
|
|
43
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:8618565f-08fa-4cac-9250-029cac7b41ba::',
|
|
44
|
+
region: 'eu-es'
|
|
45
|
+
},
|
|
46
|
+
'devsecops-grit-ci': {
|
|
47
|
+
name: 'KEEP-DevSecOps-GRIT-CI',
|
|
48
|
+
crn: 'crn:v1:bluemix:public:toolchain:eu-es:a/9e8559fac61ee9fc74d3e595fa75d147:cdc271bc-cc07-4a85-beb2-895e033319b0::',
|
|
49
|
+
region: 'eu-es'
|
|
50
|
+
},
|
|
51
|
+
'single-pl': {
|
|
52
|
+
name: 'KEEP-SINGLE-PIPELINE-TOOLCHAIN',
|
|
53
|
+
crn: 'crn:v1:bluemix:public:toolchain:us-east:a/9e8559fac61ee9fc74d3e595fa75d147:5ef88780-1e0f-4cda-94c7-f78909cc1140::',
|
|
54
|
+
region: 'us-east'
|
|
20
55
|
}
|
|
21
56
|
};
|
|
57
|
+
|
|
58
|
+
export const DEFAULT_RG_ID = '63b47433992f4295bc490852cbf1cb55';
|
|
59
|
+
export const R2R_CLI_RG_ID = 'f64e5eb8cfee406a983803bd79aa6c93';
|
package/test/setup.js
CHANGED
|
@@ -23,10 +23,23 @@ const TEMP_DIR = resolve(nconf.get('TEST_TEMP_DIR'));
|
|
|
23
23
|
const LOG_DIR = resolve(nconf.get('TEST_LOG_DIR'));
|
|
24
24
|
const DEBUG_MODE = nconf.get('TEST_DEBUG_MODE');
|
|
25
25
|
|
|
26
|
+
function sleep(ms) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
setTimeout(resolve, ms);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export const mochaHooks = {
|
|
27
|
-
beforeAll() {
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
async beforeAll() {
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
try {
|
|
36
|
+
fs.rmSync(TEMP_DIR, { recursive: true, force: true });
|
|
37
|
+
fs.rmSync(LOG_DIR, { recursive: true, force: true });
|
|
38
|
+
} catch {}
|
|
39
|
+
if (!fs.existsSync(TEMP_DIR) && !fs.existsSync(LOG_DIR)) break;
|
|
40
|
+
await sleep(1000);
|
|
41
|
+
}
|
|
42
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
30
43
|
},
|
|
31
44
|
beforeEach() {
|
|
32
45
|
if (DEBUG_MODE === true && LOG_DIR) {
|
|
@@ -35,10 +48,14 @@ export const mochaHooks = {
|
|
|
35
48
|
resolve(LOG_DIR, this.currentTest.parent.command, testTitle + '.log') :
|
|
36
49
|
resolve(LOG_DIR, testTitle + '.log');
|
|
37
50
|
logger.createLogStream(logFile);
|
|
51
|
+
// Adding logging for log stream creation and closing, because there's cases of missing test log files when running tests in parallel, most likely because of the singleton logger,
|
|
52
|
+
// causing some sort of race condition happening
|
|
53
|
+
// console.info(`Created test log stream for test case '${this.currentTest.title}'`);
|
|
38
54
|
}
|
|
39
55
|
},
|
|
40
|
-
afterEach() {
|
|
41
|
-
logger.close();
|
|
56
|
+
async afterEach() {
|
|
57
|
+
await logger.close();
|
|
58
|
+
// console.info(`Closed test log stream for test case '${this.currentTest.title}'`);
|
|
42
59
|
},
|
|
43
60
|
afterAll() {
|
|
44
61
|
if (fs.existsSync(TEMP_DIR) && DEBUG_MODE === false) fs.rmSync(TEMP_DIR, { recursive: true });
|
package/test/utils/testUtils.js
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { promisify } from 'util';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
11
13
|
import child_process from 'child_process';
|
|
12
14
|
import stripAnsi from 'strip-ansi';
|
|
13
15
|
import pty from 'node-pty';
|
|
16
|
+
import { parse as tfToJson } from '@cdktf/hcl2json'
|
|
14
17
|
import nconf from 'nconf';
|
|
15
|
-
import { expect } from 'chai';
|
|
18
|
+
import { expect, assert } from 'chai';
|
|
16
19
|
|
|
17
20
|
import { getBearerToken, deleteToolchain } from '../../cmd/utils/requests.js';
|
|
18
21
|
import { logger } from '../../cmd/utils/logger.js';
|
|
@@ -20,15 +23,53 @@ import { logger } from '../../cmd/utils/logger.js';
|
|
|
20
23
|
nconf.env('__');
|
|
21
24
|
nconf.file('local', 'test/config/local.json');
|
|
22
25
|
|
|
26
|
+
const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
|
|
23
27
|
const IBMCLOUD_API_KEY = nconf.get('IBMCLOUD_API_KEY');
|
|
24
28
|
|
|
25
29
|
function cleanOutput(data) {
|
|
26
30
|
if (typeof data === 'string') return stripAnsi(data).replace(/\r/g, '').trim();
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
function searchDirectory(currentPath) {
|
|
34
|
+
const foundFiles = [];
|
|
35
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
foundFiles.push(...searchDirectory(fullPath));
|
|
40
|
+
} else {
|
|
41
|
+
foundFiles.push(path.join(currentPath, entry.name));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return foundFiles;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseTcIdAndRegion(output) {
|
|
48
|
+
const pattern = /See cloned toolchain: https:\/\/cloud\.ibm\.com\/devops\/toolchains\/([a-zA-Z0-9-]+)\?env_id=ibm\:yp\:([a-zA-Z0-9-]+)/;
|
|
49
|
+
const match = output.match(pattern);
|
|
50
|
+
|
|
51
|
+
if (match) {
|
|
52
|
+
const toolchainId = match[1];
|
|
53
|
+
const region = match[2];
|
|
54
|
+
return { toolchainId, region };
|
|
55
|
+
} else {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
export async function execCommand(fullCommand, options) {
|
|
30
61
|
const commandStr = `node ${fullCommand.join(' ')}`;
|
|
31
62
|
const execPromise = promisify(child_process.exec);
|
|
63
|
+
|
|
64
|
+
if (!options) {
|
|
65
|
+
options = { cwd: TEMP_DIR }
|
|
66
|
+
} else {
|
|
67
|
+
options.cwd ??= TEMP_DIR;
|
|
68
|
+
if (!fs.existsSync(options.cwd)) {
|
|
69
|
+
fs.mkdirSync(options.cwd, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
32
73
|
try {
|
|
33
74
|
const { stdout, stderr } = await execPromise(commandStr, options);
|
|
34
75
|
if (stderr) {
|
|
@@ -51,12 +92,14 @@ export async function execCommand(fullCommand, options) {
|
|
|
51
92
|
export function runPtyProcess(fullCommand, options) {
|
|
52
93
|
const {
|
|
53
94
|
timeout = 0,
|
|
54
|
-
cwd =
|
|
95
|
+
cwd = TEMP_DIR,
|
|
55
96
|
env = process.env,
|
|
56
97
|
questionAnswerMap = {},
|
|
57
98
|
exitCondition = '',
|
|
58
99
|
} = options;
|
|
59
100
|
|
|
101
|
+
if (!fs.existsSync(cwd)) fs.mkdirSync(cwd, { recursive: true });
|
|
102
|
+
|
|
60
103
|
return new Promise((resolve, reject) => {
|
|
61
104
|
try {
|
|
62
105
|
const ptyProcess = pty.spawn('node', fullCommand, {
|
|
@@ -106,7 +149,7 @@ export function runPtyProcess(fullCommand, options) {
|
|
|
106
149
|
});
|
|
107
150
|
}
|
|
108
151
|
|
|
109
|
-
export async function
|
|
152
|
+
export async function deleteCreatedToolchains(toolchainsToDelete) {
|
|
110
153
|
if (toolchainsToDelete && typeof toolchainsToDelete === 'object' && toolchainsToDelete.size > 0) {
|
|
111
154
|
const token = await getBearerToken(IBMCLOUD_API_KEY);
|
|
112
155
|
const deletePromises = [...toolchainsToDelete.entries()].map(([id, region]) => deleteToolchain(token, id, region));
|
|
@@ -114,24 +157,83 @@ export async function testSuiteCleanup(toolchainsToDelete) {
|
|
|
114
157
|
}
|
|
115
158
|
}
|
|
116
159
|
|
|
117
|
-
export async function
|
|
160
|
+
export async function assertExecError(fullCommand, expectedMessage, options, assertionFn) {
|
|
118
161
|
try {
|
|
119
162
|
const output = await execCommand(fullCommand, options);
|
|
120
163
|
logger.dump(output);
|
|
121
164
|
throw new Error('Expected command to fail but it succeeded');
|
|
122
165
|
} catch (e) {
|
|
123
166
|
logger.dump(e.message);
|
|
124
|
-
|
|
167
|
+
if (assertionFn) {
|
|
168
|
+
const res = assertionFn(e.message);
|
|
169
|
+
if (res instanceof Promise) await res;
|
|
170
|
+
} else if (expectedMessage) {
|
|
171
|
+
expect(e.message).to.match(expectedMessage);
|
|
172
|
+
} else {
|
|
173
|
+
assert.fail('No assertion function or expected message provided.');
|
|
174
|
+
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
|
|
128
|
-
export async function
|
|
178
|
+
export async function assertPtyOutput(fullCommand, expectedMessage, options, assertionFn) {
|
|
129
179
|
try {
|
|
130
180
|
const output = await runPtyProcess(fullCommand, options);
|
|
131
181
|
logger.dump(output);
|
|
132
|
-
|
|
182
|
+
if (assertionFn) {
|
|
183
|
+
const res = assertionFn(output);
|
|
184
|
+
if (res instanceof Promise) await res;
|
|
185
|
+
} else if (expectedMessage) {
|
|
186
|
+
expect(output).to.match(expectedMessage);
|
|
187
|
+
} else {
|
|
188
|
+
assert.fail('No assertion function or expected message provided.');
|
|
189
|
+
}
|
|
190
|
+
return parseTcIdAndRegion(output);
|
|
133
191
|
} catch (e) {
|
|
134
192
|
logger.dump(e.message);
|
|
135
193
|
throw (e);
|
|
136
194
|
}
|
|
137
195
|
}
|
|
196
|
+
|
|
197
|
+
export function areFilesInDir(dirPath, filePatterns) {
|
|
198
|
+
const foundFiles = searchDirectory(dirPath);
|
|
199
|
+
for (const pattern of filePatterns) {
|
|
200
|
+
const regex = new RegExp(pattern);
|
|
201
|
+
if (!foundFiles.some(file => regex.test(file))) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function assertTfResourcesInDir(dirPath, expectedResourcesMap) {
|
|
209
|
+
const resourceCounter = {};
|
|
210
|
+
|
|
211
|
+
const foundFiles = searchDirectory(dirPath);
|
|
212
|
+
const allResources = [];
|
|
213
|
+
for (const file of foundFiles) {
|
|
214
|
+
if (!file.endsWith('.tf')) continue;
|
|
215
|
+
const fileName = path.basename(file);
|
|
216
|
+
const tfFile = fs.readFileSync(file, 'utf8');
|
|
217
|
+
const tfFileObject = await tfToJson(fileName, tfFile);
|
|
218
|
+
if (tfFileObject.resource) allResources.push(tfFileObject.resource);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const resourceMap of allResources) {
|
|
222
|
+
for (const resourceType of Object.keys(resourceMap)) {
|
|
223
|
+
resourceCounter[resourceType] = (resourceCounter[resourceType] || 0) + 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Check if all expected resources are present
|
|
227
|
+
for (const [resourceType, expectedCount] of Object.entries(expectedResourcesMap)) {
|
|
228
|
+
if (resourceCounter[resourceType] !== expectedCount) {
|
|
229
|
+
assert.fail(`Expected ${expectedCount} ${resourceType} resource(s) but found ${resourceCounter[resourceType] || 0}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Check if there are unexpected resources
|
|
233
|
+
for (const [resourceType, count] of Object.entries(resourceCounter)) {
|
|
234
|
+
if (!(resourceType in expectedResourcesMap)) {
|
|
235
|
+
assert.fail(`Unexpected ${resourceType} resource found. (Count: ${count})`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
assert.ok(true, 'Directory contains all expected resources');
|
|
239
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Licensed Materials - Property of IBM
|
|
3
|
-
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
-
*
|
|
5
|
-
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
-
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
-
* Contract with IBM Corp.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
describe('copy-toolchain: Test import-terraform output', function () {
|
|
11
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Licensed Materials - Property of IBM
|
|
3
|
-
* (c) Copyright IBM Corporation 2025. All Rights Reserved.
|
|
4
|
-
*
|
|
5
|
-
* Note to U.S. Government Users Restricted Rights:
|
|
6
|
-
* Use, duplication or disclosure restricted by GSA ADP Schedule
|
|
7
|
-
* Contract with IBM Corp.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
describe('copy-toolchain: Test Terraform output', function () {
|
|
11
|
-
});
|