@ibm-cloud/cd-tools 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/direct-transfer.js +101 -3
- package/package.json +1 -1
package/cmd/direct-transfer.js
CHANGED
|
@@ -207,6 +207,20 @@ class GitLabClient {
|
|
|
207
207
|
|
|
208
208
|
return all;
|
|
209
209
|
}
|
|
210
|
+
|
|
211
|
+
async getGroupByFullPath(fullPath) {
|
|
212
|
+
const encoded = encodeURIComponent(fullPath);
|
|
213
|
+
const resp = await this.client.get(`/groups/${encoded}`);
|
|
214
|
+
return resp.data;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async listBulkImports({ page = 1, perPage = 50 } = {}) {
|
|
218
|
+
const resp = await getWithRetry(this.client, `/bulk_imports`, { page, per_page: perPage });
|
|
219
|
+
return {
|
|
220
|
+
imports: resp.data || [],
|
|
221
|
+
nextPage: Number(resp.headers?.['x-next-page'] || 0),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
async function promptUser(name) {
|
|
@@ -361,6 +375,84 @@ function formatBulkImportProgressLine(importStatus, summary) {
|
|
|
361
375
|
return parts.join(' | ');
|
|
362
376
|
}
|
|
363
377
|
|
|
378
|
+
function buildGroupUrl(base, path) {
|
|
379
|
+
try {
|
|
380
|
+
return new URL(path.replace(/^\//, ''), base).toString();
|
|
381
|
+
} catch {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isGroupEntity(e) {
|
|
387
|
+
return e?.source_type === 'group_entity' || e?.entity_type === 'group_entity' || e?.entity_type === 'group';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function handleBulkImportConflict({destination, destUrl, sourceGroupFullPath, destinationGroupPath, importResErr}) {
|
|
391
|
+
const historyUrl = buildGroupImportHistoryUrl(destUrl);
|
|
392
|
+
const groupUrl = buildGroupUrl(destUrl, `/groups/${destinationGroupPath}`);
|
|
393
|
+
const fallback = () => {
|
|
394
|
+
console.log(`\nDestination group already exists.`);
|
|
395
|
+
if (groupUrl) console.log(`Group: ${groupUrl}`);
|
|
396
|
+
if (historyUrl) console.log(`Group import history: ${historyUrl}`);
|
|
397
|
+
process.exit(0);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
await destination.getGroupByFullPath(destinationGroupPath);
|
|
402
|
+
} catch {
|
|
403
|
+
fallback();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const IMPORT_PAGES = 3;
|
|
408
|
+
const ENTITY_PAGES = 2;
|
|
409
|
+
|
|
410
|
+
let page = 1;
|
|
411
|
+
for (let p = 0; p < IMPORT_PAGES; p++) {
|
|
412
|
+
const { imports, nextPage } = await destination.listBulkImports({ page, perPage: 50 });
|
|
413
|
+
|
|
414
|
+
for (const bi of imports) {
|
|
415
|
+
if (!bi?.id) continue;
|
|
416
|
+
|
|
417
|
+
const status = bi.status;
|
|
418
|
+
if (!['created', 'started', 'finished'].includes(status)) continue;
|
|
419
|
+
|
|
420
|
+
const entities = await destination.getBulkImportEntitiesAll(bi.id, { perPage: 100, maxPages: ENTITY_PAGES });
|
|
421
|
+
|
|
422
|
+
const matchesThisGroup = entities.some(e =>
|
|
423
|
+
isGroupEntity(e) &&
|
|
424
|
+
e.source_full_path === sourceGroupFullPath &&
|
|
425
|
+
(e.destination_full_path === destinationGroupPath || e.destination_slug === destinationGroupPath)
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (!matchesThisGroup) continue;
|
|
429
|
+
|
|
430
|
+
if (status === 'created' || status === 'started') {
|
|
431
|
+
console.log(`\nGroup is already in migration...`);
|
|
432
|
+
console.log(`Bulk import ID: ${bi.id}`);
|
|
433
|
+
if (groupUrl) console.log(`Migrated group: ${groupUrl}`);
|
|
434
|
+
if (historyUrl) console.log(`Group import history: ${historyUrl}`);
|
|
435
|
+
process.exit(0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
console.log(`\nConflict detected: ${importResErr}`);
|
|
439
|
+
console.log(`Please specify a new group name using -n, --new-name <n> when trying again`);
|
|
440
|
+
console.log(`\nGroup already migrated.`);
|
|
441
|
+
if (groupUrl) console.log(`Migrated group: ${groupUrl}`);
|
|
442
|
+
if (historyUrl) console.log(`Group import history: ${historyUrl}`);
|
|
443
|
+
process.exit(0);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (!nextPage) break;
|
|
447
|
+
page = nextPage;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
fallback();
|
|
451
|
+
} catch {
|
|
452
|
+
fallback();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
364
456
|
async function directTransfer(options) {
|
|
365
457
|
const sourceUrl = validateAndConvertRegion(options.sourceRegion);
|
|
366
458
|
const destUrl = validateAndConvertRegion(options.destRegion);
|
|
@@ -418,9 +510,13 @@ async function directTransfer(options) {
|
|
|
418
510
|
console.log(`Bulk import request succeeded!`);
|
|
419
511
|
console.log(`Bulk import initiated successfully (ID: ${importRes.data?.id})`);
|
|
420
512
|
} else if (importRes.conflict) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
513
|
+
await handleBulkImportConflict({
|
|
514
|
+
destination,
|
|
515
|
+
destUrl,
|
|
516
|
+
sourceGroupFullPath: sourceGroup.full_path,
|
|
517
|
+
destinationGroupPath,
|
|
518
|
+
importResErr: importRes.error
|
|
519
|
+
});
|
|
424
520
|
}
|
|
425
521
|
} catch (error) {
|
|
426
522
|
console.log(`Bulk import request failed - ${error.message}`);
|
|
@@ -506,6 +602,8 @@ async function directTransfer(options) {
|
|
|
506
602
|
console.log(`${e.source_type}: ${e.source_full_path} (${e.status})`);
|
|
507
603
|
});
|
|
508
604
|
}
|
|
605
|
+
const migratedGroupUrl = buildGroupUrl(destUrl, `/groups/${destinationGroupPath}`);
|
|
606
|
+
if (migratedGroupUrl) console.log(`\nMigrated group: ${migratedGroupUrl}`);
|
|
509
607
|
|
|
510
608
|
return 0;
|
|
511
609
|
} else {
|