@pagerduty/backstage-plugin-backend 0.11.0 → 0.12.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/CHANGELOG.md +11 -0
- package/dist/apis/pagerduty.cjs.js +285 -0
- package/dist/apis/pagerduty.cjs.js.map +1 -1
- package/dist/auth/auth.cjs.js +5 -4
- package/dist/auth/auth.cjs.js.map +1 -1
- package/dist/controllers/mappings-controller.cjs.js +388 -0
- package/dist/controllers/mappings-controller.cjs.js.map +1 -0
- package/dist/db/PagerDutyBackendDatabase.cjs.js +16 -0
- package/dist/db/PagerDutyBackendDatabase.cjs.js.map +1 -1
- package/dist/index.cjs.js +0 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/service/router.cjs.js +197 -46
- package/dist/service/router.cjs.js.map +1 -1
- package/dist/services/dataLoader.cjs.js +2 -1
- package/dist/services/dataLoader.cjs.js.map +1 -1
- package/dist/services/pagerduty/index.cjs.js +11 -0
- package/dist/services/pagerduty/index.cjs.js.map +1 -0
- package/dist/utils/catalog-entity.cjs.js +50 -0
- package/dist/utils/catalog-entity.cjs.js.map +1 -0
- package/dist/utils/normalization.cjs.js +3 -2
- package/dist/utils/normalization.cjs.js.map +1 -1
- package/package.json +6 -6
|
@@ -8,6 +8,8 @@ var backstagePluginCommon = require('@pagerduty/backstage-plugin-common');
|
|
|
8
8
|
var auth = require('../auth/auth.cjs.js');
|
|
9
9
|
var express = require('express');
|
|
10
10
|
var Router = require('express-promise-router');
|
|
11
|
+
var mappingsController = require('../controllers/mappings-controller.cjs.js');
|
|
12
|
+
var catalogEntity = require('../utils/catalog-entity.cjs.js');
|
|
11
13
|
|
|
12
14
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
13
15
|
|
|
@@ -32,36 +34,6 @@ function _interopNamespaceCompat(e) {
|
|
|
32
34
|
var express__namespace = /*#__PURE__*/_interopNamespaceCompat(express);
|
|
33
35
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
34
36
|
|
|
35
|
-
async function createComponentEntitiesReferenceDict({
|
|
36
|
-
items: componentEntities
|
|
37
|
-
}) {
|
|
38
|
-
const componentEntitiesDict = {};
|
|
39
|
-
await Promise.all(
|
|
40
|
-
componentEntities.map(async (entity) => {
|
|
41
|
-
const serviceId = entity.metadata.annotations?.["pagerduty.com/service-id"];
|
|
42
|
-
const integrationKey = entity.metadata.annotations?.["pagerduty.com/integration-key"];
|
|
43
|
-
const account = entity.metadata.annotations?.["pagerduty.com/account"];
|
|
44
|
-
if (serviceId !== void 0 && serviceId !== "") {
|
|
45
|
-
componentEntitiesDict[serviceId] = {
|
|
46
|
-
ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
|
|
47
|
-
name: entity.metadata.name
|
|
48
|
-
};
|
|
49
|
-
} else if (integrationKey !== void 0 && integrationKey !== "") {
|
|
50
|
-
const service = await pagerduty.getServiceByIntegrationKey(
|
|
51
|
-
integrationKey,
|
|
52
|
-
account
|
|
53
|
-
).catch(() => void 0);
|
|
54
|
-
if (service !== void 0) {
|
|
55
|
-
componentEntitiesDict[service.id] = {
|
|
56
|
-
ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
|
|
57
|
-
name: entity.metadata.name
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
);
|
|
63
|
-
return componentEntitiesDict;
|
|
64
|
-
}
|
|
65
37
|
async function buildEntityMappingsResponse(entityMappings, componentEntitiesDict, componentEntities, pagerDutyServices) {
|
|
66
38
|
const result = {
|
|
67
39
|
mappings: []
|
|
@@ -185,6 +157,9 @@ async function createRouter(options) {
|
|
|
185
157
|
if (!auth$1) {
|
|
186
158
|
auth$1 = backendCommon.createLegacyAuthAdapters(options).auth;
|
|
187
159
|
}
|
|
160
|
+
if (!catalogApi) {
|
|
161
|
+
throw new Error("Catalog API is required to start the PagerDuty plugin backend");
|
|
162
|
+
}
|
|
188
163
|
await auth.loadAuthConfig(config, logger);
|
|
189
164
|
pagerduty.loadPagerDutyEndpointsFromConfig(config, logger);
|
|
190
165
|
const router = Router__default.default();
|
|
@@ -439,6 +414,128 @@ async function createRouter(options) {
|
|
|
439
414
|
response.status(error.status).json({
|
|
440
415
|
errors: [`${error.message}`]
|
|
441
416
|
});
|
|
417
|
+
} else {
|
|
418
|
+
logger.error(
|
|
419
|
+
`Unexpected error occurred while processing request: ${error}`
|
|
420
|
+
);
|
|
421
|
+
response.status(500).json({
|
|
422
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
router.post("/mapping/entities/bulk", async (request, response) => {
|
|
428
|
+
try {
|
|
429
|
+
const { mappings } = request.body;
|
|
430
|
+
if (!Array.isArray(mappings)) {
|
|
431
|
+
response.status(400).json({
|
|
432
|
+
error: "Bad Request: 'mappings' must be an array"
|
|
433
|
+
});
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const existingMappings = await store.getAllEntityMappings();
|
|
437
|
+
const existingServiceIds = new Set(
|
|
438
|
+
existingMappings.map((m) => m.serviceId)
|
|
439
|
+
);
|
|
440
|
+
const newMappings = [];
|
|
441
|
+
const skipped = [];
|
|
442
|
+
const errors = [];
|
|
443
|
+
for (const entity of mappings) {
|
|
444
|
+
if (!entity.serviceId) {
|
|
445
|
+
errors.push({
|
|
446
|
+
entityRef: entity.entityRef,
|
|
447
|
+
error: "Missing serviceId"
|
|
448
|
+
});
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (existingServiceIds.has(entity.serviceId)) {
|
|
452
|
+
skipped.push(entity);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (entity.entityRef !== "" && (entity.integrationKey === "" || entity.integrationKey === void 0)) {
|
|
456
|
+
try {
|
|
457
|
+
const backstageVendorId = "PRO19CT";
|
|
458
|
+
const service = await pagerduty.getServiceById(
|
|
459
|
+
entity.serviceId,
|
|
460
|
+
entity.account
|
|
461
|
+
);
|
|
462
|
+
const backstageIntegration = service.integrations?.find(
|
|
463
|
+
(integration) => integration.vendor?.id === backstageVendorId
|
|
464
|
+
);
|
|
465
|
+
if (!backstageIntegration) {
|
|
466
|
+
const integrationKey = await pagerduty.createServiceIntegration({
|
|
467
|
+
serviceId: entity.serviceId,
|
|
468
|
+
vendorId: backstageVendorId,
|
|
469
|
+
account: entity.account
|
|
470
|
+
});
|
|
471
|
+
entity.integrationKey = integrationKey;
|
|
472
|
+
} else {
|
|
473
|
+
entity.integrationKey = backstageIntegration.integration_key;
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
errors.push({
|
|
477
|
+
entityRef: entity.entityRef,
|
|
478
|
+
serviceId: entity.serviceId,
|
|
479
|
+
error: error instanceof Error ? `Failed to create integration: ${error.message}` : "Failed to create integration"
|
|
480
|
+
});
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
newMappings.push(entity);
|
|
485
|
+
}
|
|
486
|
+
let insertedIds = [];
|
|
487
|
+
if (newMappings.length > 0) {
|
|
488
|
+
try {
|
|
489
|
+
insertedIds = await store.bulkInsertEntityMappings(newMappings);
|
|
490
|
+
await Promise.all(
|
|
491
|
+
newMappings.map(async (entity) => {
|
|
492
|
+
if (entity.entityRef !== "") {
|
|
493
|
+
await catalogApi?.refreshEntity(entity.entityRef);
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
logger.error(`Bulk insert failed: ${error}`);
|
|
499
|
+
response.status(500).json({
|
|
500
|
+
errors: ["Bulk insert failed"]
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const results = newMappings.map((entity, index) => ({
|
|
506
|
+
id: insertedIds[index],
|
|
507
|
+
entityRef: entity.entityRef,
|
|
508
|
+
integrationKey: entity.integrationKey,
|
|
509
|
+
serviceId: entity.serviceId,
|
|
510
|
+
status: entity.status,
|
|
511
|
+
account: entity.account
|
|
512
|
+
}));
|
|
513
|
+
response.json({
|
|
514
|
+
success: results,
|
|
515
|
+
skipped: skipped.map((entity) => ({
|
|
516
|
+
entityRef: entity.entityRef,
|
|
517
|
+
serviceId: entity.serviceId,
|
|
518
|
+
reason: "Mapping already exists for this service ID"
|
|
519
|
+
})),
|
|
520
|
+
errors,
|
|
521
|
+
total: mappings.length,
|
|
522
|
+
successCount: results.length,
|
|
523
|
+
skippedCount: skipped.length,
|
|
524
|
+
errorCount: errors.length
|
|
525
|
+
});
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (error instanceof backstagePluginCommon.HttpError) {
|
|
528
|
+
logger.error(
|
|
529
|
+
`Error occurred while processing bulk mappings: ${error.message}`
|
|
530
|
+
);
|
|
531
|
+
response.status(error.status).json({
|
|
532
|
+
errors: [`${error.message}`]
|
|
533
|
+
});
|
|
534
|
+
} else {
|
|
535
|
+
logger.error(`Unexpected error: ${error}`);
|
|
536
|
+
response.status(500).json({
|
|
537
|
+
errors: ["Internal server error"]
|
|
538
|
+
});
|
|
442
539
|
}
|
|
443
540
|
}
|
|
444
541
|
});
|
|
@@ -450,7 +547,7 @@ async function createRouter(options) {
|
|
|
450
547
|
kind: "Component"
|
|
451
548
|
}
|
|
452
549
|
});
|
|
453
|
-
const componentEntitiesDict = await createComponentEntitiesReferenceDict(componentEntities);
|
|
550
|
+
const componentEntitiesDict = await catalogEntity.createComponentEntitiesReferenceDict(componentEntities);
|
|
454
551
|
const pagerDutyServices = await pagerduty.getAllServices();
|
|
455
552
|
const result = await buildEntityMappingsResponse(
|
|
456
553
|
entityMappings,
|
|
@@ -467,6 +564,7 @@ async function createRouter(options) {
|
|
|
467
564
|
}
|
|
468
565
|
}
|
|
469
566
|
});
|
|
567
|
+
router.post("/mapping/entities", mappingsController.getMappingEntities(store, catalogApi));
|
|
470
568
|
router.get(
|
|
471
569
|
"/mapping/entity/:type/:namespace/:name",
|
|
472
570
|
async (request, response) => {
|
|
@@ -529,6 +627,7 @@ async function createRouter(options) {
|
|
|
529
627
|
router.post("/mapping/entity/auto-match", async (request, response) => {
|
|
530
628
|
try {
|
|
531
629
|
const threshold = request.body.threshold ?? 100;
|
|
630
|
+
const account = request.body.account;
|
|
532
631
|
if (typeof threshold !== "number" || threshold < 0 || threshold > 100) {
|
|
533
632
|
response.status(400).json({
|
|
534
633
|
error: "Invalid threshold. Must be a number between 0 and 100."
|
|
@@ -540,15 +639,16 @@ async function createRouter(options) {
|
|
|
540
639
|
const { pdServices, bsComponents } = await dataLoader.loadBothSources({
|
|
541
640
|
catalogApi
|
|
542
641
|
});
|
|
642
|
+
const filteredPdServices = account ? pdServices.filter((service) => service.account === account) : pdServices;
|
|
543
643
|
const loadTime = Date.now() - loadStartTime;
|
|
544
644
|
const matchStartTime = Date.now();
|
|
545
645
|
const matchingConfig = { threshold };
|
|
546
|
-
let matches = matchingEngine.findMatches(
|
|
646
|
+
let matches = matchingEngine.findMatches(filteredPdServices, bsComponents, matchingConfig);
|
|
547
647
|
if (bestOnly) {
|
|
548
648
|
matches = matchingEngine.filterToBestMatchPerService(matches);
|
|
549
649
|
}
|
|
550
650
|
const matchTime = Date.now() - matchStartTime;
|
|
551
|
-
const totalComparisons =
|
|
651
|
+
const totalComparisons = filteredPdServices.length * bsComponents.length;
|
|
552
652
|
const exactMatches = matches.filter((m) => m.score === 100).length;
|
|
553
653
|
const highConfidence = matches.filter(
|
|
554
654
|
(m) => m.score >= 90 && m.score < 100
|
|
@@ -567,7 +667,8 @@ async function createRouter(options) {
|
|
|
567
667
|
pagerDutyService: {
|
|
568
668
|
serviceId: m.pagerDutyService.sourceId,
|
|
569
669
|
name: m.pagerDutyService.rawName,
|
|
570
|
-
team: m.pagerDutyService.teamName
|
|
670
|
+
team: m.pagerDutyService.teamName,
|
|
671
|
+
account: m.pagerDutyService.account
|
|
571
672
|
},
|
|
572
673
|
backstageComponent: {
|
|
573
674
|
entityRef: m.backstageComponent.sourceId,
|
|
@@ -579,7 +680,7 @@ async function createRouter(options) {
|
|
|
579
680
|
scoreBreakdown: m.scoreBreakdown
|
|
580
681
|
})),
|
|
581
682
|
statistics: {
|
|
582
|
-
totalPagerDutyServices:
|
|
683
|
+
totalPagerDutyServices: filteredPdServices.length,
|
|
583
684
|
totalBackstageComponents: bsComponents.length,
|
|
584
685
|
totalPossibleComparisons: totalComparisons,
|
|
585
686
|
matchesFound: matches.length,
|
|
@@ -685,31 +786,65 @@ async function createRouter(options) {
|
|
|
685
786
|
}
|
|
686
787
|
}
|
|
687
788
|
});
|
|
789
|
+
router.get("/teams", async (request, response) => {
|
|
790
|
+
try {
|
|
791
|
+
const account = request.query.account;
|
|
792
|
+
const teams = await pagerduty.getAllTeams(account);
|
|
793
|
+
response.json(teams);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (error instanceof backstagePluginCommon.HttpError) {
|
|
796
|
+
response.status(error.status).json({
|
|
797
|
+
errors: [`${error.message}`]
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
688
802
|
router.get("/services", async (request, response) => {
|
|
689
803
|
try {
|
|
690
|
-
const integrationKey = request.query.integration_key
|
|
691
|
-
const
|
|
692
|
-
|
|
804
|
+
const integrationKey = request.query.integration_key;
|
|
805
|
+
const teamId = request.query.team_id;
|
|
806
|
+
const query = request.query.query;
|
|
807
|
+
const limit = request.query.limit ? parseInt(request.query.limit, 10) : void 0;
|
|
808
|
+
const account = request.query.account;
|
|
809
|
+
if (integrationKey) {
|
|
693
810
|
const service = await pagerduty.getServiceByIntegrationKey(
|
|
694
811
|
integrationKey,
|
|
695
|
-
account
|
|
812
|
+
account || ""
|
|
696
813
|
);
|
|
697
814
|
const serviceResponse = {
|
|
698
815
|
service
|
|
699
816
|
};
|
|
700
817
|
response.json(serviceResponse);
|
|
701
|
-
|
|
702
|
-
const services = await pagerduty.getAllServices();
|
|
703
|
-
const servicesResponse = {
|
|
704
|
-
services
|
|
705
|
-
};
|
|
706
|
-
response.json(servicesResponse);
|
|
818
|
+
return;
|
|
707
819
|
}
|
|
820
|
+
if (teamId || query || limit) {
|
|
821
|
+
const teamIdsArray = teamId ? [teamId] : void 0;
|
|
822
|
+
const services2 = await pagerduty.getFilteredServices(
|
|
823
|
+
teamIdsArray,
|
|
824
|
+
query,
|
|
825
|
+
limit || 100,
|
|
826
|
+
account
|
|
827
|
+
);
|
|
828
|
+
response.json(services2);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const services = await pagerduty.getAllServices();
|
|
832
|
+
const servicesResponse = {
|
|
833
|
+
services
|
|
834
|
+
};
|
|
835
|
+
response.json(servicesResponse);
|
|
708
836
|
} catch (error) {
|
|
709
837
|
if (error instanceof backstagePluginCommon.HttpError) {
|
|
710
838
|
response.status(error.status).json({
|
|
711
839
|
errors: [`${error.message}`]
|
|
712
840
|
});
|
|
841
|
+
} else {
|
|
842
|
+
logger.error(
|
|
843
|
+
`Unexpected error occurred while processing request: ${error}`
|
|
844
|
+
);
|
|
845
|
+
response.status(500).json({
|
|
846
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
847
|
+
});
|
|
713
848
|
}
|
|
714
849
|
}
|
|
715
850
|
});
|
|
@@ -815,6 +950,23 @@ async function createRouter(options) {
|
|
|
815
950
|
}
|
|
816
951
|
}
|
|
817
952
|
});
|
|
953
|
+
router.get("/accounts", async (_, response) => {
|
|
954
|
+
try {
|
|
955
|
+
const accountsConfig = config.getOptional("pagerDuty.accounts");
|
|
956
|
+
if (accountsConfig && accountsConfig.length > 0) {
|
|
957
|
+
const accounts = accountsConfig.map((account) => ({
|
|
958
|
+
id: account.id,
|
|
959
|
+
isDefault: account.isDefault || false
|
|
960
|
+
}));
|
|
961
|
+
response.status(200).json({ accounts });
|
|
962
|
+
} else {
|
|
963
|
+
response.status(200).json({ accounts: [] });
|
|
964
|
+
}
|
|
965
|
+
} catch (error) {
|
|
966
|
+
logger.error(`Failed to get accounts: ${error}`);
|
|
967
|
+
response.status(500).json({ error: "Failed to get accounts" });
|
|
968
|
+
}
|
|
969
|
+
});
|
|
818
970
|
router.get("/health", async (_, response) => {
|
|
819
971
|
response.status(200).json({ status: "ok" });
|
|
820
972
|
});
|
|
@@ -823,6 +975,5 @@ async function createRouter(options) {
|
|
|
823
975
|
}
|
|
824
976
|
|
|
825
977
|
exports.buildEntityMappingsResponse = buildEntityMappingsResponse;
|
|
826
|
-
exports.createComponentEntitiesReferenceDict = createComponentEntitiesReferenceDict;
|
|
827
978
|
exports.createRouter = createRouter;
|
|
828
979
|
//# sourceMappingURL=router.cjs.js.map
|