@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 +1 -1
- package/cmd/check-secrets.js +7 -3
- package/cmd/copy-toolchain.js +6 -4
- package/cmd/utils/import-terraform.js +9 -2
- package/cmd/utils/requests.js +8 -1
- package/cmd/utils/terraform.js +9 -5
- package/cmd/utils/validate.js +10 -8
- 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
|
@@ -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';
|