@ibm-cloud/cd-tools 1.6.0 → 1.7.0
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 +2 -2
- package/cmd/copy-toolchain.js +1 -4
- package/cmd/direct-transfer.js +160 -28
- package/cmd/utils/terraform.js +2 -1
- package/config.js +2 -2
- package/create-s2s-script.js +14 -5
- package/package.json +1 -1
- package/test/copy-toolchain/functionalities.test.js +13 -13
- package/test/copy-toolchain/input-validation.test.js +5 -2
- package/test/copy-toolchain/tf-import.test.js +2 -3
- package/test/copy-toolchain/tool-validation.test.js +0 -1
package/README.md
CHANGED
|
@@ -132,9 +132,9 @@ Copies a toolchain, including tool integrations and Tekton pipelines, to another
|
|
|
132
132
|
|
|
133
133
|
Examples:
|
|
134
134
|
export IBMCLOUD_API_KEY='...'
|
|
135
|
-
npx @ibm-cloud/cd-
|
|
135
|
+
npx @ibm-cloud/cd-tools copy-toolchain -c ${TOOLCHAIN_CRN} -r us-south
|
|
136
136
|
Copy a toolchain to the Dallas region with the same name, in the same resource group.
|
|
137
|
-
npx @ibm-cloud/cd-
|
|
137
|
+
npx @ibm-cloud/cd-tools copy-toolchain -c ${TOOLCHAIN_CRN} -r eu-de -n new-toolchain-name -g new-resource-group --apikey ${APIKEY}
|
|
138
138
|
Copy a toolchain to the Frankfurt region with the specified name and target resource group, using the given API key
|
|
139
139
|
|
|
140
140
|
Environment Variables:
|
package/cmd/copy-toolchain.js
CHANGED
|
@@ -322,10 +322,7 @@ async function main(options) {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
// create toolchain, which invokes script to create s2s if applicable
|
|
325
|
-
await runTerraformApply(true, outputDir, verbosity, `ibm_cd_toolchain.${toolchainTfName}`)
|
|
326
|
-
logger.error(err, LOG_STAGES.tf);
|
|
327
|
-
applyErrors = true;
|
|
328
|
-
});
|
|
325
|
+
await runTerraformApply(true, outputDir, verbosity, `ibm_cd_toolchain.${toolchainTfName}`);
|
|
329
326
|
|
|
330
327
|
// create the rest
|
|
331
328
|
await runTerraformApply(skipUserConfirmation, outputDir, verbosity).catch((err) => {
|
package/cmd/direct-transfer.js
CHANGED
|
@@ -7,15 +7,19 @@
|
|
|
7
7
|
* Contract with IBM Corp.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Command
|
|
10
|
+
import { Command } from 'commander';
|
|
11
11
|
import axios from 'axios';
|
|
12
12
|
import readline from 'readline/promises';
|
|
13
|
+
import { writeFile } from 'fs/promises';
|
|
13
14
|
import { TARGET_REGIONS, SOURCE_REGIONS } from '../config.js';
|
|
14
15
|
|
|
16
|
+
const HTTP_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes default
|
|
17
|
+
|
|
15
18
|
class GitLabClient {
|
|
16
19
|
constructor(baseURL, token) {
|
|
17
20
|
this.client = axios.create({
|
|
18
21
|
baseURL: baseURL.endsWith('/') ? `${baseURL}api/v4` : `${baseURL}/api/v4`,
|
|
22
|
+
timeout: HTTP_TIMEOUT_MS,
|
|
19
23
|
headers: {
|
|
20
24
|
'Authorization': `Bearer ${token}`,
|
|
21
25
|
'Content-Type': 'application/json'
|
|
@@ -23,24 +27,110 @@ class GitLabClient {
|
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
// List all projects in a group + all its subgroups using BFS.
|
|
31
|
+
async getGroupProjects(groupId, { maxProjects = 1000, maxRequests = 2000 } = {}) {
|
|
32
|
+
let requestCount = 0;
|
|
27
33
|
const projects = [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const toVisit = [groupId];
|
|
35
|
+
const visited = new Set();
|
|
36
|
+
|
|
37
|
+
console.log(
|
|
38
|
+
`[DEBUG] Starting BFS project listing from group ${groupId} (maxProjects=${maxProjects}, maxRequests=${maxRequests})`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
while (toVisit.length > 0) {
|
|
42
|
+
const currentGroupId = toVisit.shift();
|
|
43
|
+
if (visited.has(currentGroupId)) continue;
|
|
44
|
+
visited.add(currentGroupId);
|
|
45
|
+
|
|
46
|
+
console.log(`[DEBUG] Visiting group ${currentGroupId}. Remaining groups in queue: ${toVisit.length}`);
|
|
47
|
+
|
|
48
|
+
// List projects for THIS group (no include_subgroups!)
|
|
49
|
+
let projPage = 1;
|
|
50
|
+
let hasMoreProjects = true;
|
|
51
|
+
|
|
52
|
+
while (hasMoreProjects) {
|
|
53
|
+
if (requestCount >= maxRequests || projects.length >= maxProjects) {
|
|
54
|
+
console.warn(`[WARN] Stopping project traversal: requestCount=${requestCount}, projects=${projects.length}`);
|
|
55
|
+
return projects;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const projRes = await this.getWithRetry(
|
|
59
|
+
`/groups/${currentGroupId}/projects`,
|
|
60
|
+
{ page: projPage, per_page: 100 }
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
requestCount++;
|
|
64
|
+
const pageProjects = projRes.data || [];
|
|
65
|
+
if (pageProjects.length > 0) {
|
|
66
|
+
projects.push(...pageProjects);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
hasMoreProjects = pageProjects.length === 100;
|
|
70
|
+
projPage++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// List DIRECT subgroups and enqueue them
|
|
74
|
+
let subgroupPage = 1;
|
|
75
|
+
let hasMoreSubgroups = true;
|
|
76
|
+
|
|
77
|
+
while (hasMoreSubgroups) {
|
|
78
|
+
if (requestCount >= maxRequests) {
|
|
79
|
+
console.warn(
|
|
80
|
+
`[WARN] Stopping subgroup traversal: requestCount=${requestCount}`
|
|
81
|
+
);
|
|
82
|
+
return projects;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const subgroupRes = await this.getWithRetry(
|
|
86
|
+
`/groups/${currentGroupId}/subgroups`,
|
|
87
|
+
{ page: subgroupPage, per_page: 100 }
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
requestCount++;
|
|
91
|
+
const subgroups = subgroupRes.data || [];
|
|
92
|
+
|
|
93
|
+
if (subgroups.length > 0) {
|
|
94
|
+
for (const sg of subgroups) {
|
|
95
|
+
if (!visited.has(sg.id)) {
|
|
96
|
+
toVisit.push(sg.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
hasMoreSubgroups = subgroups.length === 100;
|
|
102
|
+
subgroupPage++;
|
|
103
|
+
}
|
|
39
104
|
}
|
|
40
|
-
|
|
105
|
+
|
|
106
|
+
console.log(`[DEBUG] Finished BFS project listing. Total projects=${projects.length}, total requests=${requestCount}`);
|
|
41
107
|
return projects;
|
|
42
108
|
}
|
|
43
|
-
|
|
109
|
+
|
|
110
|
+
// Helper: GET with retry for flaky 5xx/520 errors (Cloudflare / origin issues)
|
|
111
|
+
async getWithRetry(path, params = {}, { retries = 3, retryDelayMs = 2000 } = {}) {
|
|
112
|
+
let lastError;
|
|
113
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
114
|
+
try {
|
|
115
|
+
return await this.client.get(path, { params });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const status = error.response?.status;
|
|
118
|
+
|
|
119
|
+
if (attempt < retries && status && status >= 500) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`[WARN] GET ${path} failed with status ${status} (attempt ${attempt}/${retries}). Retrying...`
|
|
122
|
+
);
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, retryDelayMs * attempt));
|
|
124
|
+
lastError = error;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw error; // Non-5xx or out of retries: rethrow
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw lastError;
|
|
132
|
+
}
|
|
133
|
+
|
|
44
134
|
async getGroup(groupId) {
|
|
45
135
|
const response = await this.client.get(`/groups/${groupId}`);
|
|
46
136
|
return response.data;
|
|
@@ -126,7 +216,7 @@ async function promptUser(name) {
|
|
|
126
216
|
|
|
127
217
|
const answer = await rl.question(`Your new group name is ${name}. Are you sure? (Yes/No)`);
|
|
128
218
|
|
|
129
|
-
rl.close();
|
|
219
|
+
rl.close();
|
|
130
220
|
|
|
131
221
|
if (answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y') {
|
|
132
222
|
console.log("Proceeding...");
|
|
@@ -144,6 +234,39 @@ function validateAndConvertRegion(region) {
|
|
|
144
234
|
return `https://${region}.git.cloud.ibm.com/`;
|
|
145
235
|
}
|
|
146
236
|
|
|
237
|
+
// Build a mapping of: old http_url_to_repo -> new http_url_to_repo and old web_url -> new web_url
|
|
238
|
+
async function generateUrlMappingFile({sourceUrl, destUrl, sourceGroup, destinationGroupPath, sourceProjects}) {
|
|
239
|
+
const destBase = destUrl.endsWith('/') ? destUrl.slice(0, -1) : destUrl;
|
|
240
|
+
const urlMapping = {};
|
|
241
|
+
|
|
242
|
+
const groupPrefix = `${sourceGroup.full_path}/`;
|
|
243
|
+
|
|
244
|
+
for (const project of sourceProjects) {
|
|
245
|
+
const oldRepoUrl = project.http_url_to_repo; // ends with .git
|
|
246
|
+
|
|
247
|
+
// path_with_namespace is like "group/subgroup/project-1"
|
|
248
|
+
let relativePath;
|
|
249
|
+
if (project.path_with_namespace.startsWith(groupPrefix)) {
|
|
250
|
+
relativePath = project.path_with_namespace.slice(groupPrefix.length);
|
|
251
|
+
} else {
|
|
252
|
+
// Fallback if for some reason full_path is not a prefix
|
|
253
|
+
relativePath = project.path_with_namespace;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const newRepoUrl = `${destBase}/${destinationGroupPath}/${relativePath}.git`;
|
|
257
|
+
urlMapping[oldRepoUrl] = newRepoUrl;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const mappingFile = 'grit-url-map.json';
|
|
261
|
+
|
|
262
|
+
await writeFile(mappingFile, JSON.stringify(urlMapping, null, 2), {
|
|
263
|
+
encoding: 'utf8',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
console.log(`\nURL mapping JSON generated at: ${mappingFile}`);
|
|
267
|
+
console.log(`Total mapped projects: ${sourceProjects.length}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
147
270
|
async function directTransfer(options) {
|
|
148
271
|
const sourceUrl = validateAndConvertRegion(options.sourceRegion);
|
|
149
272
|
const destUrl = validateAndConvertRegion(options.destRegion);
|
|
@@ -168,6 +291,15 @@ async function directTransfer(options) {
|
|
|
168
291
|
await promptUser(options.newName);
|
|
169
292
|
}
|
|
170
293
|
|
|
294
|
+
// Generate URL mapping JSON before starting the migration
|
|
295
|
+
await generateUrlMappingFile({
|
|
296
|
+
sourceUrl,
|
|
297
|
+
destUrl,
|
|
298
|
+
sourceGroup,
|
|
299
|
+
destinationGroupPath,
|
|
300
|
+
sourceProjects,
|
|
301
|
+
});
|
|
302
|
+
|
|
171
303
|
let bulkImport = null;
|
|
172
304
|
|
|
173
305
|
const requestPayload = {
|
|
@@ -181,10 +313,10 @@ async function directTransfer(options) {
|
|
|
181
313
|
destination_slug: destinationGroupPath,
|
|
182
314
|
destination_namespace: ""
|
|
183
315
|
}]
|
|
184
|
-
}
|
|
316
|
+
};
|
|
185
317
|
|
|
186
318
|
let importRes = null;
|
|
187
|
-
|
|
319
|
+
|
|
188
320
|
try {
|
|
189
321
|
importRes = await destination.bulkImport(requestPayload);
|
|
190
322
|
if (importRes.success) {
|
|
@@ -192,9 +324,9 @@ async function directTransfer(options) {
|
|
|
192
324
|
console.log(`Bulk import request succeeded!`);
|
|
193
325
|
console.log(`Bulk import initiated successfully (ID: ${importRes.data?.id})`);
|
|
194
326
|
} else if (importRes.conflict) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
327
|
+
console.log(`Conflict detected: ${importRes.error}`);
|
|
328
|
+
console.log(`Please specify a new group name using -n, --new-name <n> when trying again`);
|
|
329
|
+
process.exit(0);
|
|
198
330
|
}
|
|
199
331
|
} catch (error) {
|
|
200
332
|
console.log(`Bulk import request failed - ${error.message}`);
|
|
@@ -204,11 +336,11 @@ async function directTransfer(options) {
|
|
|
204
336
|
console.log('\nPolling bulk import status (checking every 5 minute)...');
|
|
205
337
|
let importStatus = 'created';
|
|
206
338
|
let attempts = 0;
|
|
207
|
-
|
|
339
|
+
|
|
208
340
|
while (!['finished', 'failed', 'timeout'].includes(importStatus) && attempts < 60) {
|
|
209
341
|
if (attempts > 0) {
|
|
210
342
|
console.log(`Waiting 5 minute before next status check...`);
|
|
211
|
-
await new Promise(resolve => setTimeout(resolve, 5*60000));
|
|
343
|
+
await new Promise(resolve => setTimeout(resolve, 5 * 60000));
|
|
212
344
|
}
|
|
213
345
|
try {
|
|
214
346
|
const importDetails = await destination.getBulkImport(bulkImport.id);
|
|
@@ -230,7 +362,7 @@ async function directTransfer(options) {
|
|
|
230
362
|
}
|
|
231
363
|
attempts++;
|
|
232
364
|
}
|
|
233
|
-
|
|
365
|
+
|
|
234
366
|
if (attempts >= 60) {
|
|
235
367
|
console.error(`Bulk import either timed out or is still running in the background`);
|
|
236
368
|
process.exit(0);
|
|
@@ -239,20 +371,20 @@ async function directTransfer(options) {
|
|
|
239
371
|
const entities = await destination.getBulkImportEntities(bulkImport.id);
|
|
240
372
|
const finishedEntities = entities.filter(e => e.status === 'finished');
|
|
241
373
|
const failedEntities = entities.filter(e => e.status === 'failed');
|
|
242
|
-
|
|
374
|
+
|
|
243
375
|
if (importStatus === 'finished' && finishedEntities.length > 0) {
|
|
244
376
|
console.log(`\nGroup migration completed successfully!`);
|
|
245
377
|
console.log(`Migration Results:`);
|
|
246
378
|
console.log(`Successfully migrated: ${finishedEntities.length} entities`);
|
|
247
379
|
console.log(`Failed: ${failedEntities.length} entities`);
|
|
248
|
-
|
|
380
|
+
|
|
249
381
|
if (failedEntities.length > 0) {
|
|
250
382
|
console.log(`\nFailed entities:\n`);
|
|
251
383
|
failedEntities.forEach(e => {
|
|
252
384
|
console.log(`${e.source_type}: ${e.source_full_path} (${e.status})`);
|
|
253
385
|
});
|
|
254
386
|
}
|
|
255
|
-
|
|
387
|
+
|
|
256
388
|
return 0;
|
|
257
389
|
} else {
|
|
258
390
|
console.error('\nBulk import failed!');
|
|
@@ -282,7 +414,7 @@ const command = new Command('copy-project-group')
|
|
|
282
414
|
.showHelpAfterError()
|
|
283
415
|
.hook('preAction', cmd => cmd.showHelpAfterError(false)) // only show help during validation
|
|
284
416
|
.action(async (options) => {
|
|
285
|
-
await directTransfer(options);
|
|
417
|
+
await directTransfer(options);
|
|
286
418
|
});
|
|
287
419
|
|
|
288
420
|
export default command;
|
package/cmd/utils/terraform.js
CHANGED
|
@@ -192,7 +192,7 @@ async function setupTerraformFiles({ token, srcRegion, targetRegion, targetTag,
|
|
|
192
192
|
// prompt user
|
|
193
193
|
const validateGritUrlPrompt = async (str) => {
|
|
194
194
|
if (!str) {
|
|
195
|
-
logger.print('Skipping... (URL will remain unchanged in the
|
|
195
|
+
logger.print('Skipping... (URL will remain unchanged in the generated Terraform configuration)');
|
|
196
196
|
return '';
|
|
197
197
|
}
|
|
198
198
|
const newUrl = (GIT_BASE_URL || `https://${targetRegion}.git.cloud.ibm.com`) + `/${str}.git`;
|
|
@@ -492,6 +492,7 @@ function replaceDependsOn(str) {
|
|
|
492
492
|
function addS2sScriptToToolchainTf(str) {
|
|
493
493
|
const provisionerStr = (tfName) => `\n\n provisioner "local-exec" {
|
|
494
494
|
command = "node create-s2s-script.cjs"
|
|
495
|
+
on_failure = fail
|
|
495
496
|
environment = {
|
|
496
497
|
IBMCLOUD_API_KEY = var.ibmcloud_api_key
|
|
497
498
|
TARGET_TOOLCHAIN_ID = ibm_cd_toolchain.${tfName}.id
|
package/config.js
CHANGED
|
@@ -11,9 +11,9 @@ const COPY_TOOLCHAIN_DESC = `Copies a toolchain, including tool integrations and
|
|
|
11
11
|
|
|
12
12
|
Examples:
|
|
13
13
|
export IBMCLOUD_API_KEY='...'
|
|
14
|
-
npx @ibm-cloud/cd-
|
|
14
|
+
npx @ibm-cloud/cd-tools copy-toolchain -c \${TOOLCHAIN_CRN} -r us-south
|
|
15
15
|
Copy a toolchain to the Dallas region with the same name, in the same resource group.
|
|
16
|
-
npx @ibm-cloud/cd-
|
|
16
|
+
npx @ibm-cloud/cd-tools copy-toolchain -c \${TOOLCHAIN_CRN} -r eu-de -n new-toolchain-name -g new-resource-group --apikey \${APIKEY}
|
|
17
17
|
Copy a toolchain to the Frankfurt region with the specified name and target resource group, using the given API key
|
|
18
18
|
|
|
19
19
|
Environment Variables:
|
package/create-s2s-script.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('node:fs');
|
|
11
11
|
const { resolve } = require('node:path');
|
|
12
|
+
const { exit } = require('node:process');
|
|
12
13
|
|
|
13
14
|
const API_KEY = process.env['IBMCLOUD_API_KEY'];
|
|
14
15
|
if (!API_KEY) throw Error(`Missing 'IBMCLOUD_API_KEY'`);
|
|
@@ -103,21 +104,29 @@ async function createS2sAuthPolicy(bearer, item) {
|
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
if (!response.ok) {
|
|
106
|
-
|
|
107
|
+
return Promise.reject(`Failed to create service-to-service authorization policy for ${item['serviceId']} '${item['parameters']['label'] ?? item['parameters']['name']}' with status: ${response.status} ${response.statusText}`);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
console.log(`CREATING AUTH POLICY... ${response.status}, ${response.statusText}`);
|
|
110
111
|
} catch (error) {
|
|
111
|
-
|
|
112
|
+
return Promise.reject(error.message);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// main
|
|
116
117
|
|
|
117
|
-
getBearer().then((bearer) => {
|
|
118
|
+
getBearer().then(async (bearer) => {
|
|
118
119
|
const inputArr = JSON.parse(fs.readFileSync(resolve(INPUT_PATH)));
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
const promises = [];
|
|
122
|
+
inputArr.forEach((item) => {
|
|
123
|
+
promises.push(createS2sAuthPolicy(bearer, item));
|
|
122
124
|
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await Promise.all(promises);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error(e)
|
|
130
|
+
exit(1);
|
|
131
|
+
}
|
|
123
132
|
});
|
package/package.json
CHANGED
|
@@ -37,10 +37,19 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
37
37
|
const testCases = [
|
|
38
38
|
{
|
|
39
39
|
name: 'Terraform Version Verification',
|
|
40
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
40
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
41
41
|
expected: /✔ Terraform Version:/,
|
|
42
42
|
options: {
|
|
43
|
-
exitCondition:
|
|
43
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
44
|
+
timeout: 10000
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'CLI Version Verification',
|
|
49
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
50
|
+
expected: /✔ cd-tools Version:/,
|
|
51
|
+
options: {
|
|
52
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
44
53
|
timeout: 10000
|
|
45
54
|
}
|
|
46
55
|
},
|
|
@@ -56,10 +65,10 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
56
65
|
},
|
|
57
66
|
{
|
|
58
67
|
name: 'Log file is created successfully',
|
|
59
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
68
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
60
69
|
expected: null,
|
|
61
70
|
options: {
|
|
62
|
-
exitCondition:
|
|
71
|
+
exitCondition: `(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`,
|
|
63
72
|
timeout: 10000,
|
|
64
73
|
cwd: TEMP_DIR + '/' + 'log-file-is-created-successfully'
|
|
65
74
|
},
|
|
@@ -81,15 +90,6 @@ describe('copy-toolchain: Test functionalities', function () {
|
|
|
81
90
|
assert.isTrue(toolchainData.id === toolchainId, 'Was toolchain created successfully without any confirmations?');
|
|
82
91
|
}
|
|
83
92
|
},
|
|
84
|
-
{
|
|
85
|
-
name: 'Prompt User when toolchain name already exists in resource group',
|
|
86
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TARGET_REGIONS[0]],
|
|
87
|
-
expected: new RegExp(`Warning! A toolchain named \'${TEST_TOOLCHAINS['empty'].name}\' already exists in:[\\s\\S]*?Resource Group:[\\s\\S]*?${R2R_CLI_RG_ID}`),
|
|
88
|
-
options: {
|
|
89
|
-
exitCondition: '(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):',
|
|
90
|
-
timeout: 10000
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
93
|
{
|
|
94
94
|
name: 'Prompt User when toolchain name already exists in region',
|
|
95
95
|
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region, '-g', DEFAULT_RG_ID],
|
|
@@ -125,10 +125,13 @@ describe('copy-toolchain: Test user input handling', function () {
|
|
|
125
125
|
const invalidUserInputCases = [
|
|
126
126
|
{
|
|
127
127
|
name: 'Invalid Toolchain tag is provided',
|
|
128
|
-
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r',
|
|
128
|
+
cmd: [CLI_PATH, COMMAND, '-c', TEST_TOOLCHAINS['empty'].crn, '-r', TEST_TOOLCHAINS['empty'].region],
|
|
129
129
|
expected: /Provided tag is invalid/,
|
|
130
130
|
options: {
|
|
131
|
-
questionAnswerMap: {
|
|
131
|
+
questionAnswerMap: {
|
|
132
|
+
[`(Recommended) Edit the cloned toolchain's name [default: ${TEST_TOOLCHAINS['empty'].name}] (Ctrl-C to abort):`]: '',
|
|
133
|
+
'(Recommended) Add a tag to the cloned toolchain (Ctrl-C to abort):': mocks.invalidTag
|
|
134
|
+
},
|
|
132
135
|
exitCondition: 'Validation failed',
|
|
133
136
|
timeout: 10000
|
|
134
137
|
}
|
|
@@ -43,12 +43,12 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
43
43
|
ibm_cd_tekton_pipeline_trigger: 1,
|
|
44
44
|
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
45
45
|
ibm_cd_toolchain: 1,
|
|
46
|
+
ibm_cd_toolchain_tool_cos: 1,
|
|
46
47
|
ibm_cd_toolchain_tool_custom: 1,
|
|
47
48
|
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
48
49
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
49
50
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
50
51
|
ibm_cd_toolchain_tool_slack: 1,
|
|
51
|
-
ibm_iam_authorization_policy: 1
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
},
|
|
@@ -68,12 +68,12 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
68
68
|
ibm_cd_tekton_pipeline_trigger: 1,
|
|
69
69
|
ibm_cd_tekton_pipeline_trigger_property: 1,
|
|
70
70
|
ibm_cd_toolchain: 1,
|
|
71
|
+
ibm_cd_toolchain_tool_cos: 1,
|
|
71
72
|
ibm_cd_toolchain_tool_custom: 1,
|
|
72
73
|
ibm_cd_toolchain_tool_githubconsolidated: 1,
|
|
73
74
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
74
75
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
75
76
|
ibm_cd_toolchain_tool_slack: 1,
|
|
76
|
-
ibm_iam_authorization_policy: 1
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
},
|
|
@@ -99,7 +99,6 @@ describe('copy-toolchain: Test import-terraform output', function () {
|
|
|
99
99
|
ibm_cd_toolchain_tool_pipeline: 1,
|
|
100
100
|
ibm_cd_toolchain_tool_secretsmanager: 1,
|
|
101
101
|
ibm_cd_toolchain_tool_slack: 1,
|
|
102
|
-
ibm_iam_authorization_policy: 1
|
|
103
102
|
})
|
|
104
103
|
}
|
|
105
104
|
},
|
|
@@ -56,7 +56,6 @@ describe('copy-toolchain: Test tool validation', function () {
|
|
|
56
56
|
},
|
|
57
57
|
assertionFunc: (output) => {
|
|
58
58
|
expect(output).to.match(/Warning! The following tools contain secrets that cannot be migrated/);
|
|
59
|
-
expect(output).to.match(/cloudobjectstorage[\s\S]*?cos_api_key/);
|
|
60
59
|
expect(output).to.match(/slack[\s\S]*?api_token/);
|
|
61
60
|
expect(output).to.match(/pipeline[\s\S]*?properties.doi-ibmcloud-api-key/);
|
|
62
61
|
}
|