@pagerduty/backstage-plugin-backend 0.9.6 → 0.9.8-alpha.1

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/dist/index.cjs.js CHANGED
@@ -2,1647 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var backendCommon = require('@backstage/backend-common');
6
- var fetch$1 = require('node-fetch');
7
- var backstagePluginCommon = require('@pagerduty/backstage-plugin-common');
8
- var luxon = require('luxon');
9
- var express = require('express');
10
- var Router = require('express-promise-router');
11
- var backendPluginApi = require('@backstage/backend-plugin-api');
12
- var uuid = require('uuid');
13
- var catalogClient = require('@backstage/catalog-client');
5
+ var router = require('./service/router.cjs.js');
6
+ var plugin = require('./plugin.cjs.js');
14
7
 
15
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
16
8
 
17
- function _interopNamespaceCompat(e) {
18
- if (e && typeof e === 'object' && 'default' in e) return e;
19
- var n = Object.create(null);
20
- if (e) {
21
- Object.keys(e).forEach(function (k) {
22
- if (k !== 'default') {
23
- var d = Object.getOwnPropertyDescriptor(e, k);
24
- Object.defineProperty(n, k, d.get ? d : {
25
- enumerable: true,
26
- get: function () { return e[k]; }
27
- });
28
- }
29
- });
30
- }
31
- n.default = e;
32
- return Object.freeze(n);
33
- }
34
9
 
35
- var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch$1);
36
- var express__namespace = /*#__PURE__*/_interopNamespaceCompat(express);
37
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
38
-
39
- let authPersistence;
40
- let isLegacyConfig$1 = false;
41
- async function checkForOAuthToken(tokenId) {
42
- if (authPersistence.accountTokens[tokenId]?.authToken !== "" && authPersistence.accountTokens[tokenId]?.authToken.includes("Bearer")) {
43
- if (authPersistence.accountTokens[tokenId].authTokenExpiryDate > Date.now()) {
44
- return true;
45
- }
46
- authPersistence.logger.info("OAuth token expired, renewing");
47
- await loadAuthConfig(authPersistence.config, authPersistence.logger);
48
- return authPersistence.accountTokens[tokenId].authTokenExpiryDate > Date.now();
49
- }
50
- return false;
51
- }
52
- async function getAuthToken(accountId) {
53
- if (!authPersistence?.accountTokens) {
54
- await loadAuthConfig(authPersistence.config, authPersistence.logger);
55
- }
56
- if (isLegacyConfig$1 && authPersistence.accountTokens.default.authToken !== "" && (await checkForOAuthToken("default") || authPersistence.accountTokens.default.authToken.includes("Token"))) {
57
- return authPersistence.accountTokens.default.authToken;
58
- }
59
- const key = accountId && accountId !== "" ? accountId : authPersistence.defaultAccount ?? "";
60
- if (authPersistence.accountTokens[key]?.authToken !== "" && (await checkForOAuthToken(key) || authPersistence.accountTokens[key]?.authToken.includes("Token"))) {
61
- return authPersistence.accountTokens[key].authToken;
62
- }
63
- return "";
64
- }
65
- async function loadAuthConfig(config, logger) {
66
- try {
67
- const defaultAccountId = "default";
68
- authPersistence = {
69
- config,
70
- logger,
71
- accountTokens: {}
72
- };
73
- if (!config.getOptional("pagerDuty.accounts")) {
74
- isLegacyConfig$1 = true;
75
- logger.warn("No PagerDuty accounts configuration found in config file. Reverting to legacy configuration.");
76
- if (!config.getOptionalString("pagerDuty.apiToken")) {
77
- logger.warn("No PagerDuty API token found in config file. Trying OAuth token instead...");
78
- if (!config.getOptional("pagerDuty.oauth")) {
79
- logger.error("No PagerDuty OAuth configuration found in config file.");
80
- } else if (!config.getOptionalString("pagerDuty.oauth.clientId") || !config.getOptionalString("pagerDuty.oauth.clientSecret") || !config.getOptionalString("pagerDuty.oauth.subDomain")) {
81
- logger.error("Missing required PagerDuty OAuth parameters in config file. 'clientId', 'clientSecret', and 'subDomain' are required. 'region' is optional.");
82
- } else {
83
- const tokenInfo = await getOAuthToken(
84
- config.getString("pagerDuty.oauth.clientId"),
85
- config.getString("pagerDuty.oauth.clientSecret"),
86
- config.getString("pagerDuty.oauth.subDomain"),
87
- config.getOptionalString("pagerDuty.oauth.region") ?? "us"
88
- );
89
- authPersistence.accountTokens[defaultAccountId] = tokenInfo;
90
- logger.info("PagerDuty OAuth configuration loaded successfully.");
91
- }
92
- } else {
93
- authPersistence.accountTokens[defaultAccountId] = {
94
- authToken: `Token token=${config.getString("pagerDuty.apiToken")}`,
95
- authTokenExpiryDate: Date.now() + 36e5 * 24 * 365 * 2
96
- // 2 years
97
- };
98
- logger.info("PagerDuty API token loaded successfully.");
99
- }
100
- } else {
101
- logger.info("New PagerDuty accounts configuration found in config file.");
102
- isLegacyConfig$1 = false;
103
- const accounts = config.getOptional("pagerDuty.accounts") || [];
104
- if (accounts && accounts?.length === 1) {
105
- logger.info("Only one account found in config file. Setting it as default.");
106
- authPersistence.defaultAccount = accounts[0].id;
107
- }
108
- await Promise.all(accounts.map(async (account) => {
109
- const maskedAccountId = maskString(account.id);
110
- if (account.isDefault && !authPersistence.defaultAccount) {
111
- logger.info(`Default account found in config file. Setting it as default.`);
112
- authPersistence.defaultAccount = account.id;
113
- }
114
- if (!account.apiToken) {
115
- logger.warn("No PagerDuty API token found in config file. Trying OAuth token instead...");
116
- if (!account.oauth) {
117
- logger.error("No PagerDuty OAuth configuration found in config file.");
118
- } else if (!account.oauth.clientId || !account.oauth.clientSecret || !account.oauth.subDomain) {
119
- logger.error("Missing required PagerDuty OAuth parameters in config file. 'clientId', 'clientSecret', and 'subDomain' are required. 'region' is optional.");
120
- } else {
121
- const tokenInfo = await getOAuthToken(
122
- account.oauth.clientId,
123
- account.oauth.clientSecret,
124
- account.oauth.subDomain,
125
- account.oauth.region ?? "us"
126
- );
127
- authPersistence.accountTokens[account.id] = tokenInfo;
128
- logger.info(`PagerDuty OAuth configuration loaded successfully for account ${maskedAccountId}.`);
129
- }
130
- } else {
131
- authPersistence.accountTokens[account.id] = {
132
- authToken: `Token token=${account.apiToken}`,
133
- authTokenExpiryDate: Date.now() + 36e5 * 24 * 365 * 2
134
- // 2 years
135
- };
136
- logger.info(`PagerDuty API token loaded successfully for account ${maskedAccountId}.`);
137
- }
138
- }));
139
- if (!authPersistence.defaultAccount) {
140
- logger.error("No default account found in config file. One account must be marked as default.");
141
- }
142
- }
143
- } catch (error) {
144
- logger.error(`Unable to retrieve valid PagerDuty AUTH configuration from config file: ${error}`);
145
- }
146
- }
147
- async function getOAuthToken(clientId, clientSecret, subDomain, region) {
148
- if (!clientId || !clientSecret || !subDomain) {
149
- throw new Error("Missing required PagerDuty OAuth parameters.");
150
- }
151
- const scopes = `
152
- abilities.read
153
- analytics.read
154
- change_events.read
155
- escalation_policies.read
156
- incidents.read
157
- oncalls.read
158
- schedules.read
159
- services.read
160
- services.write
161
- standards.read
162
- teams.read
163
- users.read
164
- vendors.read
165
- `;
166
- const urlencoded = new URLSearchParams();
167
- urlencoded.append("grant_type", "client_credentials");
168
- urlencoded.append("client_id", clientId);
169
- urlencoded.append("client_secret", clientSecret);
170
- urlencoded.append("scope", `as_account-${region}.${subDomain} ${scopes}`);
171
- let response;
172
- const options = {
173
- method: "POST",
174
- headers: {
175
- "Content-Type": "application/x-www-form-urlencoded"
176
- },
177
- body: urlencoded
178
- };
179
- const baseUrl = "https://identity.pagerduty.com/oauth/token";
180
- try {
181
- response = await fetch(baseUrl, options);
182
- } catch (error) {
183
- throw new Error(`Failed to retrieve oauth token: ${error}`);
184
- }
185
- switch (response.status) {
186
- case 400:
187
- throw new backstagePluginCommon.HttpError("Failed to retrieve valid token. Bad Request - Invalid arguments provided.", 400);
188
- case 401:
189
- throw new backstagePluginCommon.HttpError("Failed to retrieve valid token. Forbidden - Invalid credentials provided.", 401);
190
- }
191
- const authResponse = await response.json();
192
- const result = {
193
- authToken: `Bearer ${authResponse.access_token}`,
194
- authTokenExpiryDate: Date.now() + authResponse.expires_in * 1e3
195
- };
196
- return result;
197
- }
198
- function maskString(str) {
199
- return str[0] + "*".repeat(str.length - 2) + str.slice(-1);
200
- }
201
-
202
- const EndpointConfig = {};
203
- let fallbackEndpointConfig;
204
- let isLegacyConfig = false;
205
- function setFallbackEndpointConfig(account) {
206
- fallbackEndpointConfig = {
207
- eventsBaseUrl: account.eventsBaseUrl ?? "https://events.pagerduty.com/v2",
208
- apiBaseUrl: account.apiBaseUrl ?? "https://api.pagerduty.com"
209
- };
210
- }
211
- function insertEndpointConfig(account) {
212
- EndpointConfig[account.id] = {
213
- eventsBaseUrl: account.eventsBaseUrl ?? "https://events.pagerduty.com/v2",
214
- apiBaseUrl: account.apiBaseUrl ?? "https://api.pagerduty.com"
215
- };
216
- }
217
- function loadPagerDutyEndpointsFromConfig(config, logger) {
218
- if (config.getOptional("pagerDuty.accounts")) {
219
- logger.debug(`New accounts configuration detected. Loading PagerDuty endpoints from config.`);
220
- isLegacyConfig = false;
221
- const accounts = config.getOptional("pagerDuty.accounts");
222
- if (accounts?.length === 1) {
223
- logger.debug(`Single account configuration detected. Loading PagerDuty endpoints from config to 'default'.`);
224
- EndpointConfig.default = {
225
- eventsBaseUrl: accounts[0].eventsBaseUrl !== void 0 ? accounts[0].eventsBaseUrl : "https://events.pagerduty.com/v2",
226
- apiBaseUrl: accounts[0].apiBaseUrl !== void 0 ? accounts[0].apiBaseUrl : "https://api.pagerduty.com"
227
- };
228
- } else {
229
- logger.debug(`Multiple account configuration detected. Loading PagerDuty endpoints from config.`);
230
- accounts?.forEach((account) => {
231
- if (account.isDefault) {
232
- setFallbackEndpointConfig(account);
233
- }
234
- insertEndpointConfig(account);
235
- });
236
- }
237
- } else {
238
- logger.debug(`Loading legacy PagerDuty endpoints from config.`);
239
- isLegacyConfig = true;
240
- EndpointConfig.default = {
241
- eventsBaseUrl: config.getOptionalString("pagerDuty.eventsBaseUrl") !== void 0 ? config.getString("pagerDuty.eventsBaseUrl") : "https://events.pagerduty.com/v2",
242
- apiBaseUrl: config.getOptionalString("pagerDuty.apiBaseUrl") !== void 0 ? config.getString("pagerDuty.apiBaseUrl") : "https://api.pagerduty.com"
243
- };
244
- }
245
- }
246
- function getApiBaseUrl(account) {
247
- if (isLegacyConfig === true) {
248
- return EndpointConfig.default.apiBaseUrl;
249
- }
250
- if (account) {
251
- return EndpointConfig[account].apiBaseUrl;
252
- }
253
- return fallbackEndpointConfig.apiBaseUrl;
254
- }
255
- async function addServiceRelationsToService(serviceRelations, account) {
256
- let response;
257
- const options = {
258
- method: "POST",
259
- headers: {
260
- Authorization: await getAuthToken(account),
261
- "Accept": "application/vnd.pagerduty+json;version=2",
262
- "Content-Type": "application/json"
263
- },
264
- body: JSON.stringify({
265
- relationships: serviceRelations
266
- })
267
- };
268
- const apiBaseUrl = getApiBaseUrl(account);
269
- const baseUrl = `${apiBaseUrl}/service_dependencies/associate`;
270
- try {
271
- response = await fetchWithRetries(baseUrl, options);
272
- } catch (error) {
273
- throw new Error(`Failed to retrieve service dependencies: ${error}`);
274
- }
275
- if (response.status >= 500) {
276
- throw new backstagePluginCommon.HttpError(`Failed to add service dependencies. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
277
- }
278
- switch (response.status) {
279
- case 400:
280
- throw new backstagePluginCommon.HttpError("Failed to add service dependencies. Caller provided invalid arguments. Please review the response for error details. Retrying with the same arguments will not work.", 400);
281
- case 401:
282
- throw new backstagePluginCommon.HttpError("Failed to add service dependencies. Caller did not supply credentials or did not provide the correct credentials. If you are using an API key, it may be invalid or your Authorization header may be malformed.", 401);
283
- case 403:
284
- throw new backstagePluginCommon.HttpError("Failed to add service dependencies. Caller is not authorized to view the requested resource. While your authentication is valid, the authenticated user or token does not have permission to perform this action.", 403);
285
- case 404:
286
- throw new backstagePluginCommon.HttpError("Failed to add service dependencies. The requested resource was not found.", 404);
287
- }
288
- let result;
289
- try {
290
- result = await response.json();
291
- return result.relationships;
292
- } catch (error) {
293
- throw new backstagePluginCommon.HttpError(`Failed to parse service dependency information: ${error}`, 500);
294
- }
295
- }
296
- async function removeServiceRelationsFromService(serviceRelations, account) {
297
- let response;
298
- const options = {
299
- method: "POST",
300
- headers: {
301
- Authorization: await getAuthToken(account),
302
- "Accept": "application/vnd.pagerduty+json;version=2",
303
- "Content-Type": "application/json"
304
- },
305
- body: JSON.stringify({
306
- relationships: serviceRelations
307
- })
308
- };
309
- const apiBaseUrl = getApiBaseUrl(account);
310
- const baseUrl = `${apiBaseUrl}/service_dependencies/disassociate`;
311
- try {
312
- response = await fetchWithRetries(`${baseUrl}`, options);
313
- } catch (error) {
314
- throw new Error(`Failed to retrieve service dependencies: ${error}`);
315
- }
316
- if (response.status >= 500) {
317
- throw new backstagePluginCommon.HttpError(`Failed to remove service dependencies. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
318
- }
319
- switch (response.status) {
320
- case 400:
321
- throw new backstagePluginCommon.HttpError("Failed to remove service dependencies. Caller provided invalid arguments. Please review the response for error details. Retrying with the same arguments will not work.", 400);
322
- case 401:
323
- throw new backstagePluginCommon.HttpError("Failed to remove service dependencies. Caller did not supply credentials or did not provide the correct credentials. If you are using an API key, it may be invalid or your Authorization header may be malformed.", 401);
324
- case 403:
325
- throw new backstagePluginCommon.HttpError("Failed to remove service dependencies. Caller is not authorized to view the requested resource. While your authentication is valid, the authenticated user or token does not have permission to perform this action.", 403);
326
- case 404:
327
- throw new backstagePluginCommon.HttpError("Failed to remove service dependencies. The requested resource was not found.", 404);
328
- }
329
- let result;
330
- try {
331
- result = await response.json();
332
- return result.relationships;
333
- } catch (error) {
334
- throw new backstagePluginCommon.HttpError(`Failed to parse service dependency information: ${error}`, 500);
335
- }
336
- }
337
- async function getServiceRelationshipsById(serviceId, account) {
338
- let response;
339
- const options = {
340
- method: "GET",
341
- headers: {
342
- Authorization: await getAuthToken(account),
343
- "Accept": "application/vnd.pagerduty+json;version=2",
344
- "Content-Type": "application/json"
345
- }
346
- };
347
- const apiBaseUrl = getApiBaseUrl(account);
348
- const baseUrl = `${apiBaseUrl}/service_dependencies/technical_services/${serviceId}`;
349
- try {
350
- response = await fetchWithRetries(baseUrl, options);
351
- } catch (error) {
352
- throw new Error(`Failed to retrieve service dependencies: ${error}`);
353
- }
354
- if (response.status >= 500) {
355
- throw new backstagePluginCommon.HttpError(`Failed to list service dependencies. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
356
- }
357
- switch (response.status) {
358
- case 400:
359
- throw new backstagePluginCommon.HttpError("Failed to list service dependencies. Caller provided invalid arguments. Please review the response for error details. Retrying with the same arguments will not work.", 400);
360
- case 401:
361
- throw new backstagePluginCommon.HttpError("Failed to list service dependencies. Caller did not supply credentials or did not provide the correct credentials. If you are using an API key, it may be invalid or your Authorization header may be malformed.", 401);
362
- case 403:
363
- throw new backstagePluginCommon.HttpError("Failed to list service dependencies. Caller is not authorized to view the requested resource. While your authentication is valid, the authenticated user or token does not have permission to perform this action.", 403);
364
- case 404:
365
- throw new backstagePluginCommon.HttpError("Failed to list service dependencies. The requested resource was not found.", 404);
366
- }
367
- let result;
368
- try {
369
- result = await response.json();
370
- return result.relationships;
371
- } catch (error) {
372
- throw new backstagePluginCommon.HttpError(`Failed to parse service dependency information: ${error}`, 500);
373
- }
374
- }
375
- async function getEscalationPolicies(offset, limit, account) {
376
- let response;
377
- const params = `total=true&sort_by=name&offset=${offset}&limit=${limit}`;
378
- const options = {
379
- method: "GET",
380
- headers: {
381
- Authorization: await getAuthToken(account),
382
- "Accept": "application/vnd.pagerduty+json;version=2",
383
- "Content-Type": "application/json"
384
- }
385
- };
386
- const apiBaseUrl = getApiBaseUrl(account);
387
- const baseUrl = `${apiBaseUrl}/escalation_policies`;
388
- try {
389
- response = await fetchWithRetries(`${baseUrl}?${params}`, options);
390
- } catch (error) {
391
- throw new Error(`Failed to retrieve escalation policies: ${error}`);
392
- }
393
- if (response.status >= 500) {
394
- throw new backstagePluginCommon.HttpError(`Failed to list escalation policies. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
395
- }
396
- switch (response.status) {
397
- case 400:
398
- throw new backstagePluginCommon.HttpError("Failed to list escalation policies. Caller provided invalid arguments.", 400);
399
- case 401:
400
- throw new backstagePluginCommon.HttpError("Failed to list escalation policies. Caller did not supply credentials or did not provide the correct credentials.", 401);
401
- case 403:
402
- throw new backstagePluginCommon.HttpError("Failed to list escalation policies. Caller is not authorized to view the requested resource.", 403);
403
- case 429:
404
- throw new backstagePluginCommon.HttpError("Failed to list escalation policies. Rate limit exceeded.", 429);
405
- }
406
- let result;
407
- try {
408
- result = await response.json();
409
- return [result.more ?? false, result.escalation_policies];
410
- } catch (error) {
411
- throw new backstagePluginCommon.HttpError(`Failed to parse escalation policy information: ${error}`, 500);
412
- }
413
- }
414
- async function getAllEscalationPolicies() {
415
- const limit = 50;
416
- let offset = 0;
417
- let moreResults = false;
418
- let results = [];
419
- await Promise.all(
420
- Object.keys(EndpointConfig).map(async (account) => {
421
- try {
422
- offset = 0;
423
- do {
424
- const res = await getEscalationPolicies(offset, limit, account);
425
- res[1].forEach((policy) => {
426
- policy.account = account;
427
- });
428
- results = results.concat(res[1]);
429
- if (res[0] === true) {
430
- moreResults = true;
431
- offset += limit;
432
- } else {
433
- moreResults = false;
434
- }
435
- } while (moreResults === true);
436
- } catch (error) {
437
- if (error instanceof backstagePluginCommon.HttpError) {
438
- throw error;
439
- } else {
440
- throw new backstagePluginCommon.HttpError(`${error}`, 500);
441
- }
442
- }
443
- })
444
- );
445
- return results;
446
- }
447
- async function getOncallUsers(escalationPolicy, account) {
448
- let response;
449
- const params = `time_zone=UTC&include[]=users&escalation_policy_ids[]=${escalationPolicy}`;
450
- const options = {
451
- method: "GET",
452
- headers: {
453
- Authorization: await getAuthToken(account),
454
- "Accept": "application/vnd.pagerduty+json;version=2",
455
- "Content-Type": "application/json"
456
- }
457
- };
458
- const apiBaseUrl = getApiBaseUrl(account);
459
- const baseUrl = `${apiBaseUrl}/oncalls`;
460
- try {
461
- response = await fetchWithRetries(`${baseUrl}?${params}`, options);
462
- } catch (error) {
463
- throw new Error(`Failed to retrieve oncalls: ${error}`);
464
- }
465
- if (response.status >= 500) {
466
- throw new backstagePluginCommon.HttpError(`Failed to list oncalls. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
467
- }
468
- switch (response.status) {
469
- case 400:
470
- throw new backstagePluginCommon.HttpError("Failed to list oncalls. Caller provided invalid arguments.", 400);
471
- case 401:
472
- throw new backstagePluginCommon.HttpError("Failed to list oncalls. Caller did not supply credentials or did not provide the correct credentials.", 401);
473
- case 403:
474
- throw new backstagePluginCommon.HttpError("Failed to list oncalls. Caller is not authorized to view the requested resource.", 403);
475
- case 429:
476
- throw new backstagePluginCommon.HttpError("Failed to list oncalls. Rate limit exceeded.", 429);
477
- }
478
- let result;
479
- let usersItem;
480
- try {
481
- result = await response.json();
482
- if (result.oncalls.length !== 0) {
483
- const oncallsSorted = [...result.oncalls].sort((a, b) => {
484
- return a.escalation_level - b.escalation_level;
485
- });
486
- const oncallsFiltered = oncallsSorted.filter((oncall) => {
487
- return oncall.escalation_level === oncallsSorted[0].escalation_level;
488
- });
489
- usersItem = [...oncallsFiltered].sort((a, b) => a.user.name > b.user.name ? 1 : -1).map((oncall) => oncall.user);
490
- const uniqueUsers = /* @__PURE__ */ new Map();
491
- usersItem.forEach((user) => {
492
- uniqueUsers.set(user.id, user);
493
- });
494
- usersItem.length = 0;
495
- uniqueUsers.forEach((user) => {
496
- usersItem.push(user);
497
- });
498
- return usersItem;
499
- }
500
- return [];
501
- } catch (error) {
502
- throw new backstagePluginCommon.HttpError(`Failed to parse oncall information: ${error}`, 500);
503
- }
504
- }
505
- async function getServiceById(serviceId, account) {
506
- let response;
507
- const params = `time_zone=UTC&include[]=integrations&include[]=escalation_policies`;
508
- const token = await getAuthToken(account);
509
- const options = {
510
- method: "GET",
511
- headers: {
512
- Authorization: token,
513
- "Accept": "application/vnd.pagerduty+json;version=2",
514
- "Content-Type": "application/json"
515
- }
516
- };
517
- const apiBaseUrl = getApiBaseUrl(account);
518
- const baseUrl = `${apiBaseUrl}/services`;
519
- try {
520
- response = await fetchWithRetries(`${baseUrl}/${serviceId}?${params}`, options);
521
- } catch (error) {
522
- throw new Error(`Failed to retrieve service: ${error}`);
523
- }
524
- if (response.status >= 500) {
525
- throw new backstagePluginCommon.HttpError(`Failed to get service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
526
- }
527
- switch (response.status) {
528
- case 400:
529
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller provided invalid arguments.", 400);
530
- case 401:
531
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller did not supply credentials or did not provide the correct credentials.", 401);
532
- case 403:
533
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller is not authorized to view the requested resource.", 403);
534
- case 404:
535
- throw new backstagePluginCommon.HttpError("Failed to get service. The requested resource was not found.", 404);
536
- }
537
- let result;
538
- try {
539
- result = await response.json();
540
- return result.service;
541
- } catch (error) {
542
- throw new backstagePluginCommon.HttpError(`Failed to parse service information: ${error}`, 500);
543
- }
544
- }
545
- async function getServiceByIntegrationKey(integrationKey, account) {
546
- let response;
547
- const params = `query=${integrationKey}&time_zone=UTC&include[]=integrations&include[]=escalation_policies`;
548
- const token = await getAuthToken(account);
549
- const options = {
550
- method: "GET",
551
- headers: {
552
- Authorization: token,
553
- "Accept": "application/vnd.pagerduty+json;version=2",
554
- "Content-Type": "application/json"
555
- }
556
- };
557
- const apiBaseUrl = getApiBaseUrl(account);
558
- const baseUrl = `${apiBaseUrl}/services`;
559
- try {
560
- response = await fetchWithRetries(`${baseUrl}?${params}`, options);
561
- } catch (error) {
562
- throw new Error(`Failed to retrieve service: ${error}`);
563
- }
564
- if (response.status >= 500) {
565
- throw new backstagePluginCommon.HttpError(`Failed to get service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
566
- }
567
- switch (response.status) {
568
- case 400:
569
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller provided invalid arguments.", 400);
570
- case 401:
571
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller did not supply credentials or did not provide the correct credentials.", 401);
572
- case 403:
573
- throw new backstagePluginCommon.HttpError("Failed to get service. Caller is not authorized to view the requested resource.", 403);
574
- case 404:
575
- throw new backstagePluginCommon.HttpError("Failed to get service. The requested resource was not found.", 404);
576
- }
577
- let result;
578
- try {
579
- result = await response.json();
580
- } catch (error) {
581
- throw new backstagePluginCommon.HttpError(`Failed to parse service information: ${error}`, 500);
582
- }
583
- if (result.services.length === 0) {
584
- throw new backstagePluginCommon.HttpError(`Failed to get service. The requested resource was not found.`, 404);
585
- }
586
- return result.services[0];
587
- }
588
- async function getAllServices() {
589
- const allServices = [];
590
- await Promise.all(
591
- Object.entries(EndpointConfig).map(async ([account, _]) => {
592
- let response;
593
- const params = `time_zone=UTC&include[]=integrations&include[]=escalation_policies&include[]=teams&total=true`;
594
- const token = await getAuthToken(account);
595
- const options = {
596
- method: "GET",
597
- headers: {
598
- Authorization: token,
599
- "Accept": "application/vnd.pagerduty+json;version=2",
600
- "Content-Type": "application/json"
601
- }
602
- };
603
- const apiBaseUrl = getApiBaseUrl(account);
604
- const baseUrl = `${apiBaseUrl}/services`;
605
- let offset = 0;
606
- const limit = 50;
607
- let result;
608
- try {
609
- do {
610
- const paginatedUrl = `${baseUrl}?${params}&offset=${offset}&limit=${limit}`;
611
- response = await fetchWithRetries(paginatedUrl, options);
612
- if (response.status >= 500) {
613
- throw new backstagePluginCommon.HttpError(`Failed to get services. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
614
- }
615
- switch (response.status) {
616
- case 400:
617
- throw new backstagePluginCommon.HttpError("Failed to get services. Caller provided invalid arguments.", 400);
618
- case 401:
619
- throw new backstagePluginCommon.HttpError("Failed to get services. Caller did not supply credentials or did not provide the correct credentials.", 401);
620
- case 403:
621
- throw new backstagePluginCommon.HttpError("Failed to get services. Caller is not authorized to view the requested resource.", 403);
622
- default:
623
- break;
624
- }
625
- result = await response.json();
626
- result.services.forEach((service) => {
627
- service.account = account;
628
- });
629
- allServices.push(...result.services);
630
- offset += limit;
631
- } while (offset < result.total);
632
- } catch (error) {
633
- throw error;
634
- }
635
- })
636
- );
637
- return allServices;
638
- }
639
- async function getChangeEvents(serviceId, account) {
640
- let response;
641
- const params = `limit=5&time_zone=UTC&sort_by=timestamp`;
642
- const options = {
643
- method: "GET",
644
- headers: {
645
- Authorization: await getAuthToken(account),
646
- "Accept": "application/vnd.pagerduty+json;version=2",
647
- "Content-Type": "application/json"
648
- }
649
- };
650
- const apiBaseUrl = getApiBaseUrl(account);
651
- const baseUrl = `${apiBaseUrl}/services`;
652
- try {
653
- response = await fetchWithRetries(`${baseUrl}/${serviceId}/change_events?${params}`, options);
654
- } catch (error) {
655
- throw new Error(`Failed to retrieve change events for service: ${error}`);
656
- }
657
- if (response.status >= 500) {
658
- throw new backstagePluginCommon.HttpError(`Failed to get change events for service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
659
- }
660
- switch (response.status) {
661
- case 400:
662
- throw new backstagePluginCommon.HttpError("Failed to get change events for service. Caller provided invalid arguments.", 400);
663
- case 401:
664
- throw new backstagePluginCommon.HttpError("Failed to get change events for service. Caller did not supply credentials or did not provide the correct credentials.", 401);
665
- case 403:
666
- throw new backstagePluginCommon.HttpError("Failed to get change events for service. Caller is not authorized to view the requested resource.", 403);
667
- case 404:
668
- throw new backstagePluginCommon.HttpError("Failed to get change events for service. The requested resource was not found.", 404);
669
- }
670
- let result;
671
- try {
672
- result = await response.json();
673
- return result.change_events;
674
- } catch (error) {
675
- throw new backstagePluginCommon.HttpError(`Failed to parse change events information: ${error}`, 500);
676
- }
677
- }
678
- async function getIncidents(serviceId, account) {
679
- let response;
680
- const params = `time_zone=UTC&sort_by=created_at&statuses[]=triggered&statuses[]=acknowledged&service_ids[]=${serviceId}`;
681
- const options = {
682
- method: "GET",
683
- headers: {
684
- Authorization: await getAuthToken(account),
685
- "Accept": "application/vnd.pagerduty+json;version=2",
686
- "Content-Type": "application/json"
687
- }
688
- };
689
- const apiBaseUrl = getApiBaseUrl(account);
690
- const baseUrl = `${apiBaseUrl}/incidents`;
691
- try {
692
- response = await fetchWithRetries(`${baseUrl}?${params}`, options);
693
- } catch (error) {
694
- throw new Error(`Failed to retrieve incidents for service: ${error}`);
695
- }
696
- if (response.status >= 500) {
697
- throw new backstagePluginCommon.HttpError(`Failed to get incidents for service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
698
- }
699
- switch (response.status) {
700
- case 400:
701
- throw new backstagePluginCommon.HttpError("Failed to get incidents for service. Caller provided invalid arguments.", 400);
702
- case 401:
703
- throw new backstagePluginCommon.HttpError("Failed to get incidents for service. Caller did not supply credentials or did not provide the correct credentials.", 401);
704
- case 402:
705
- throw new backstagePluginCommon.HttpError("Failed to get incidents for service. Account does not have the abilities to perform the action. Please review the response for the required abilities.", 402);
706
- case 403:
707
- throw new backstagePluginCommon.HttpError("Failed to get incidents for service. Caller is not authorized to view the requested resource.", 403);
708
- case 429:
709
- throw new backstagePluginCommon.HttpError("Failed to get incidents for service. Too many requests have been made, the rate limit has been reached.", 429);
710
- }
711
- let result;
712
- try {
713
- result = await response.json();
714
- return result.incidents;
715
- } catch (error) {
716
- throw new backstagePluginCommon.HttpError(`Failed to parse incidents information: ${error}`, 500);
717
- }
718
- }
719
- async function getServiceStandards(serviceId, account) {
720
- let response;
721
- const options = {
722
- method: "GET",
723
- headers: {
724
- Authorization: await getAuthToken(account),
725
- "Accept": "application/vnd.pagerduty+json;version=2",
726
- "Content-Type": "application/json"
727
- }
728
- };
729
- const apiBaseUrl = getApiBaseUrl(account);
730
- const baseUrl = `${apiBaseUrl}/standards/scores/technical_services/${serviceId}`;
731
- try {
732
- response = await fetchWithRetries(baseUrl, options);
733
- } catch (error) {
734
- throw new Error(`Failed to retrieve service standards for service: ${error}`);
735
- }
736
- if (response.status >= 500) {
737
- throw new backstagePluginCommon.HttpError(`Failed to get service standards for service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
738
- }
739
- switch (response.status) {
740
- case 401:
741
- throw new backstagePluginCommon.HttpError("Failed to get service standards for service. Caller did not supply credentials or did not provide the correct credentials.", 401);
742
- case 403:
743
- throw new backstagePluginCommon.HttpError("Failed to get service standards for service. Caller is not authorized to view the requested resource.", 403);
744
- case 429:
745
- throw new backstagePluginCommon.HttpError("Failed to get service standards for service. Too many requests have been made, the rate limit has been reached.", 429);
746
- }
747
- try {
748
- const result = await response.json();
749
- return result;
750
- } catch (error) {
751
- throw new backstagePluginCommon.HttpError(`Failed to parse service standards information: ${error}`, 500);
752
- }
753
- }
754
- async function getServiceMetrics(serviceId, account) {
755
- let response;
756
- const endDate = luxon.DateTime.now();
757
- const startDate = endDate.minus({ days: 30 });
758
- const body = JSON.stringify({
759
- filters: {
760
- created_at_start: startDate.toISO(),
761
- created_at_end: endDate.toISO(),
762
- service_ids: [
763
- serviceId
764
- ]
765
- }
766
- });
767
- const options = {
768
- method: "POST",
769
- headers: {
770
- Authorization: await getAuthToken(account),
771
- "Accept": "application/vnd.pagerduty+json;version=2",
772
- "Content-Type": "application/json"
773
- },
774
- body
775
- };
776
- const apiBaseUrl = getApiBaseUrl(account);
777
- const baseUrl = `${apiBaseUrl}/analytics/metrics/incidents/services`;
778
- try {
779
- response = await fetchWithRetries(baseUrl, options);
780
- } catch (error) {
781
- throw new Error(`Failed to retrieve service metrics for service: ${error}`);
782
- }
783
- if (response.status >= 500) {
784
- throw new backstagePluginCommon.HttpError(`Failed to get service metrics for service. PagerDuty API returned a server error. Retrying with the same arguments will not work.`, response.status);
785
- }
786
- switch (response.status) {
787
- case 400:
788
- throw new backstagePluginCommon.HttpError("Failed to get service metrics for service. Caller provided invalid arguments. Please review the response for error details. Retrying with the same arguments will not work.", 400);
789
- case 429:
790
- throw new backstagePluginCommon.HttpError("Failed to get service metrics for service. Too many requests have been made, the rate limit has been reached.", 429);
791
- }
792
- try {
793
- const result = await response.json();
794
- return result.data;
795
- } catch (error) {
796
- throw new backstagePluginCommon.HttpError(`Failed to parse service metrics information: ${error}`, 500);
797
- }
798
- }
799
- async function createServiceIntegration({ serviceId, vendorId, account }) {
800
- let response;
801
- const apiBaseUrl = getApiBaseUrl(account);
802
- const baseUrl = `${apiBaseUrl}/services`;
803
- const token = await getAuthToken(account);
804
- const options = {
805
- method: "POST",
806
- body: JSON.stringify({
807
- integration: {
808
- name: "Backstage",
809
- service: {
810
- id: serviceId,
811
- type: "service_reference"
812
- },
813
- vendor: {
814
- id: vendorId,
815
- type: "vendor_reference"
816
- }
817
- }
818
- }),
819
- headers: {
820
- Authorization: token,
821
- "Accept": "application/vnd.pagerduty+json;version=2",
822
- "Content-Type": "application/json"
823
- }
824
- };
825
- try {
826
- response = await fetchWithRetries(`${baseUrl}/${serviceId}/integrations`, options);
827
- } catch (error) {
828
- throw new Error(`Failed to create service integration: ${error}`);
829
- }
830
- if (response.status >= 500) {
831
- throw new Error(`Failed to create service integration. PagerDuty API returned a server error. Retrying with the same arguments will not work.`);
832
- }
833
- switch (response.status) {
834
- case 400:
835
- throw new Error(`Failed to create service integration. Caller provided invalid arguments.`);
836
- case 401:
837
- throw new Error(`Failed to create service integration. Caller did not supply credentials or did not provide the correct credentials.`);
838
- case 403:
839
- throw new Error(`Failed to create service integration. Caller is not authorized to view the requested resource.`);
840
- case 429:
841
- throw new Error(`Failed to create service integration. Rate limit exceeded.`);
842
- }
843
- let result;
844
- try {
845
- result = await response.json();
846
- return result.integration.integration_key ?? "";
847
- } catch (error) {
848
- throw new Error(`Failed to parse service information: ${error}`);
849
- }
850
- }
851
- async function fetchWithRetries(url, options) {
852
- let response;
853
- let error = new Error();
854
- const maxRetries = 5;
855
- const delay = 1e3;
856
- let factor = 2;
857
- for (let i = 0; i < maxRetries; i++) {
858
- try {
859
- response = await fetch__default.default(url, options);
860
- return response;
861
- } catch (e) {
862
- error = e;
863
- }
864
- const timeout = delay * factor;
865
- await new Promise((resolve) => setTimeout(resolve, timeout));
866
- factor *= 2;
867
- }
868
- throw new Error(`Failed to fetch data after ${maxRetries} retries. Last error: ${error}`);
869
- }
870
-
871
- async function createComponentEntitiesReferenceDict({ items: componentEntities }) {
872
- const componentEntitiesDict = {};
873
- await Promise.all(componentEntities.map(async (entity) => {
874
- const serviceId = entity.metadata.annotations?.["pagerduty.com/service-id"];
875
- const integrationKey = entity.metadata.annotations?.["pagerduty.com/integration-key"];
876
- const account = entity.metadata.annotations?.["pagerduty.com/account"];
877
- if (serviceId !== void 0 && serviceId !== "") {
878
- componentEntitiesDict[serviceId] = {
879
- ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
880
- name: entity.metadata.name
881
- };
882
- } else if (integrationKey !== void 0 && integrationKey !== "") {
883
- const service = await getServiceByIntegrationKey(integrationKey, account).catch(() => void 0);
884
- if (service !== void 0) {
885
- componentEntitiesDict[service.id] = {
886
- ref: `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase(),
887
- name: entity.metadata.name
888
- };
889
- }
890
- }
891
- }));
892
- return componentEntitiesDict;
893
- }
894
- async function buildEntityMappingsResponse(entityMappings, componentEntitiesDict, componentEntities, pagerDutyServices) {
895
- const result = {
896
- mappings: []
897
- };
898
- pagerDutyServices.forEach((service) => {
899
- const entityRef = componentEntitiesDict[service.id]?.ref;
900
- const entityName = componentEntitiesDict[service.id]?.name;
901
- const entityMapping = entityMappings.find((mapping) => mapping.serviceId === service.id);
902
- if (entityMapping) {
903
- if (entityRef === void 0) {
904
- if (entityMapping.entityRef === "" || entityMapping.entityRef === void 0) {
905
- result.mappings.push({
906
- entityRef: "",
907
- entityName: "",
908
- integrationKey: entityMapping.integrationKey,
909
- serviceId: entityMapping.serviceId,
910
- status: "NotMapped",
911
- serviceName: service.name,
912
- team: service.teams?.[0]?.name ?? "",
913
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
914
- serviceUrl: service.html_url,
915
- account: service.account
916
- });
917
- } else {
918
- const entityRefName = componentEntities.items.find((entity) => `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase() === entityMapping.entityRef)?.metadata.name ?? "";
919
- result.mappings.push({
920
- entityRef: entityMapping.entityRef,
921
- entityName: entityRefName,
922
- serviceId: entityMapping.serviceId,
923
- integrationKey: entityMapping.integrationKey,
924
- status: "OutOfSync",
925
- serviceName: service.name,
926
- team: service.teams?.[0]?.name ?? "",
927
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
928
- serviceUrl: service.html_url,
929
- account: service.account
930
- });
931
- }
932
- } else if (entityRef !== entityMapping.entityRef) {
933
- const entityRefName = componentEntities.items.find((entity) => `${entity.kind}:${entity.metadata.namespace}/${entity.metadata.name}`.toLowerCase() === entityMapping.entityRef)?.metadata.name ?? "";
934
- result.mappings.push({
935
- entityRef: entityMapping.entityRef !== "" ? entityMapping.entityRef : "",
936
- entityName: entityMapping.entityRef !== "" ? entityRefName : "",
937
- serviceId: entityMapping.serviceId,
938
- integrationKey: entityMapping.integrationKey,
939
- status: "OutOfSync",
940
- serviceName: service.name,
941
- team: service.teams?.[0]?.name ?? "",
942
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
943
- serviceUrl: service.html_url,
944
- account: service.account
945
- });
946
- } else if (entityRef === entityMapping.entityRef) {
947
- result.mappings.push({
948
- entityRef: entityMapping.entityRef !== "" ? entityMapping.entityRef : "",
949
- entityName: entityMapping.entityRef !== "" ? entityName : "",
950
- serviceId: entityMapping.serviceId,
951
- integrationKey: entityMapping.integrationKey,
952
- status: "InSync",
953
- serviceName: service.name,
954
- team: service.teams?.[0]?.name ?? "",
955
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
956
- serviceUrl: service.html_url,
957
- account: service.account
958
- });
959
- }
960
- } else {
961
- const backstageVendorId = "PRO19CT";
962
- const backstageIntegrationKey = service.integrations?.find((integration) => integration.vendor?.id === backstageVendorId)?.integration_key ?? "";
963
- if (entityRef !== void 0) {
964
- result.mappings.push({
965
- entityRef,
966
- entityName,
967
- serviceId: service.id,
968
- integrationKey: backstageIntegrationKey,
969
- status: "InSync",
970
- serviceName: service.name,
971
- team: service.teams?.[0]?.name ?? "",
972
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
973
- serviceUrl: service.html_url,
974
- account: service.account
975
- });
976
- } else {
977
- result.mappings.push({
978
- entityRef: "",
979
- entityName: "",
980
- serviceId: service.id,
981
- integrationKey: backstageIntegrationKey,
982
- status: "NotMapped",
983
- serviceName: service.name,
984
- team: service.teams?.[0]?.name ?? "",
985
- escalationPolicy: service.escalation_policy !== void 0 ? service.escalation_policy.name : "",
986
- serviceUrl: service.html_url,
987
- account: service.account
988
- });
989
- }
990
- }
991
- });
992
- const sortedResult = result.mappings.sort((a, b) => {
993
- if (a.serviceName < b.serviceName) {
994
- return -1;
995
- } else if (a.serviceName > b.serviceName) {
996
- return 1;
997
- }
998
- return 0;
999
- });
1000
- result.mappings = sortedResult;
1001
- return result;
1002
- }
1003
- async function createRouter(options) {
1004
- const { logger, config, store, catalogApi } = options;
1005
- let { auth } = options;
1006
- if (!auth) {
1007
- auth = backendCommon.createLegacyAuthAdapters(options).auth;
1008
- }
1009
- await loadAuthConfig(config, logger);
1010
- loadPagerDutyEndpointsFromConfig(config, logger);
1011
- const router = Router__default.default();
1012
- router.use(express__namespace.json());
1013
- router.delete("/dependencies/service/:serviceId", async (request, response) => {
1014
- try {
1015
- const serviceId = request.params.serviceId || "";
1016
- const account = request.query.account || "";
1017
- if (serviceId === "") {
1018
- response.status(400).json("Bad Request: ':serviceId' must be provided as part of the path");
1019
- return;
1020
- }
1021
- const dependencies = Object.keys(request.body).length === 0 ? [] : request.body;
1022
- if (!dependencies || dependencies.length === 0) {
1023
- response.status(400).json("Bad Request: 'dependencies' must be provided as part of the request body");
1024
- return;
1025
- }
1026
- const serviceRelations = [];
1027
- dependencies.forEach(async (dependency) => {
1028
- serviceRelations.push({
1029
- supporting_service: {
1030
- id: dependency,
1031
- type: "service"
1032
- },
1033
- dependent_service: {
1034
- id: serviceId,
1035
- type: "service"
1036
- }
1037
- });
1038
- });
1039
- await removeServiceRelationsFromService(serviceRelations, account);
1040
- response.sendStatus(200);
1041
- } catch (error) {
1042
- if (error instanceof backstagePluginCommon.HttpError) {
1043
- logger.error(`Error occurred while processing request: ${error.message}`);
1044
- response.status(error.status).json({
1045
- errors: [
1046
- `${error.message}`
1047
- ]
1048
- });
1049
- }
1050
- }
1051
- });
1052
- router.post("/dependencies/service/:serviceId", async (request, response) => {
1053
- try {
1054
- const serviceId = request.params.serviceId || "";
1055
- const account = request.query.account || "";
1056
- if (serviceId === "") {
1057
- response.status(400).json("Bad Request: ':serviceId' must be provided as part of the path");
1058
- return;
1059
- }
1060
- const dependencies = Object.keys(request.body).length === 0 ? [] : request.body;
1061
- if (!dependencies || dependencies.length === 0) {
1062
- response.status(400).json("Bad Request: 'dependencies' must be provided as part of the request body");
1063
- return;
1064
- }
1065
- const serviceRelations = [];
1066
- dependencies.forEach(async (dependency) => {
1067
- serviceRelations.push({
1068
- supporting_service: {
1069
- id: dependency,
1070
- type: "service"
1071
- },
1072
- dependent_service: {
1073
- id: serviceId,
1074
- type: "service"
1075
- }
1076
- });
1077
- });
1078
- await addServiceRelationsToService(serviceRelations, account);
1079
- response.sendStatus(200);
1080
- } catch (error) {
1081
- if (error instanceof backstagePluginCommon.HttpError) {
1082
- logger.error(`Error occurred while processing request: ${error.message}`);
1083
- response.status(error.status).json({
1084
- errors: [
1085
- `${error.message}`
1086
- ]
1087
- });
1088
- }
1089
- }
1090
- });
1091
- router.get("/dependencies/service/:serviceId", async (request, response) => {
1092
- try {
1093
- const serviceId = request.params.serviceId;
1094
- const account = request.query.account || "";
1095
- if (serviceId) {
1096
- const serviceRelationships = await getServiceRelationshipsById(serviceId, account);
1097
- if (serviceRelationships) {
1098
- response.json({
1099
- relationships: serviceRelationships
1100
- });
1101
- }
1102
- } else {
1103
- response.status(400).json("Bad Request: ':serviceId' must be provided as part of the path");
1104
- }
1105
- } catch (error) {
1106
- if (error instanceof backstagePluginCommon.HttpError) {
1107
- response.status(error.status).json({
1108
- errors: [
1109
- `${error.message}`
1110
- ]
1111
- });
1112
- }
1113
- }
1114
- });
1115
- router.get("/catalog/entity/:type/:namespace/:name", async (request, response) => {
1116
- const type = request.params.type;
1117
- const namespace = request.params.namespace;
1118
- const name = request.params.name;
1119
- try {
1120
- if (type && namespace && name) {
1121
- const entityRef = `${type}:${namespace}/${name}`.toLowerCase();
1122
- const foundEntity = await catalogApi?.getEntityByRef(entityRef);
1123
- if (foundEntity) {
1124
- response.json(foundEntity.metadata.annotations?.["pagerduty.com/service-id"]);
1125
- } else {
1126
- response.status(404);
1127
- }
1128
- } else {
1129
- response.status(400).json("Bad Request: ':entityRef' must be provided as part of the path");
1130
- }
1131
- } catch (error) {
1132
- if (error instanceof backstagePluginCommon.HttpError) {
1133
- response.status(error.status).json({
1134
- errors: [
1135
- `${error.message}`
1136
- ]
1137
- });
1138
- }
1139
- }
1140
- });
1141
- router.post("/settings", async (request, response) => {
1142
- try {
1143
- const settings = request.body;
1144
- await Promise.all(settings.map(async (setting) => {
1145
- if (setting.id === void 0 || setting.value === void 0) {
1146
- response.status(400).json("Bad Request: 'id' and 'value' are required");
1147
- return;
1148
- }
1149
- if (!isValidSetting(setting.value)) {
1150
- response.status(400).json("Bad Request: 'value' is invalid. Valid options are 'backstage', 'pagerduty', 'both' or 'disabled'");
1151
- return;
1152
- }
1153
- await store.updateSetting(setting);
1154
- }));
1155
- response.sendStatus(200);
1156
- } catch (error) {
1157
- if (error instanceof backstagePluginCommon.HttpError) {
1158
- logger.error(`Error occurred while processing request: ${error.message}`);
1159
- response.status(error.status).json({
1160
- errors: [
1161
- `${error.message}`
1162
- ]
1163
- });
1164
- }
1165
- }
1166
- });
1167
- router.get("/settings/:settingId", async (request, response) => {
1168
- try {
1169
- const settingId = request.params.settingId;
1170
- const setting = await store.findSetting(settingId);
1171
- if (!setting) {
1172
- response.status(404).json({});
1173
- return;
1174
- }
1175
- response.json(setting);
1176
- } catch (error) {
1177
- if (error instanceof backstagePluginCommon.HttpError) {
1178
- response.status(error.status).json({
1179
- errors: [
1180
- `${error.message}`
1181
- ]
1182
- });
1183
- }
1184
- }
1185
- });
1186
- function isValidSetting(value) {
1187
- if (value === "backstage" || value === "pagerduty" || value === "both" || value === "disabled") {
1188
- return true;
1189
- }
1190
- return false;
1191
- }
1192
- router.post("/mapping/entity", async (request, response) => {
1193
- try {
1194
- const entity = request.body;
1195
- if (!entity.serviceId) {
1196
- response.status(400).json("Bad Request: 'serviceId' must be provided as part of the request body");
1197
- return;
1198
- }
1199
- const entityMappings = await store.getAllEntityMappings();
1200
- const oldMapping = entityMappings.find((mapping) => mapping.serviceId === entity.serviceId);
1201
- if (entity.entityRef !== "" && (entity.integrationKey === "" || entity.integrationKey === void 0)) {
1202
- const backstageVendorId = "PRO19CT";
1203
- const service = await getServiceById(entity.serviceId, entity.account);
1204
- const backstageIntegration = service.integrations?.find((integration) => integration.vendor?.id === backstageVendorId);
1205
- if (!backstageIntegration) {
1206
- const integrationKey = await createServiceIntegration({
1207
- serviceId: entity.serviceId,
1208
- vendorId: backstageVendorId,
1209
- account: entity.account
1210
- });
1211
- entity.integrationKey = integrationKey;
1212
- } else {
1213
- entity.integrationKey = backstageIntegration.integration_key;
1214
- }
1215
- }
1216
- const entityMappingId = await store.insertEntityMapping(entity);
1217
- if (entity.entityRef !== "") {
1218
- await catalogApi?.refreshEntity(entity.entityRef);
1219
- }
1220
- if (oldMapping && oldMapping.entityRef !== "") {
1221
- await catalogApi?.refreshEntity(oldMapping.entityRef);
1222
- }
1223
- response.json({
1224
- id: entityMappingId,
1225
- entityRef: entity.entityRef,
1226
- integrationKey: entity.integrationKey,
1227
- serviceId: entity.serviceId,
1228
- status: entity.status,
1229
- account: entity.account
1230
- });
1231
- } catch (error) {
1232
- if (error instanceof backstagePluginCommon.HttpError) {
1233
- logger.error(`Error occurred while processing request: ${error.message}`);
1234
- response.status(error.status).json({
1235
- errors: [
1236
- `${error.message}`
1237
- ]
1238
- });
1239
- }
1240
- }
1241
- });
1242
- router.get("/mapping/entity", async (_, response) => {
1243
- try {
1244
- const entityMappings = await store.getAllEntityMappings();
1245
- const componentEntities = await catalogApi.getEntities({
1246
- filter: {
1247
- kind: "Component"
1248
- }
1249
- });
1250
- const componentEntitiesDict = await createComponentEntitiesReferenceDict(componentEntities);
1251
- const pagerDutyServices = await getAllServices();
1252
- const result = await buildEntityMappingsResponse(entityMappings, componentEntitiesDict, componentEntities, pagerDutyServices);
1253
- response.json(result);
1254
- } catch (error) {
1255
- if (error instanceof backstagePluginCommon.HttpError) {
1256
- response.status(error.status).json({
1257
- errors: [
1258
- `${error.message}`
1259
- ]
1260
- });
1261
- }
1262
- }
1263
- });
1264
- router.get("/mapping/entity/:type/:namespace/:name", async (request, response) => {
1265
- try {
1266
- const entityType = request.params.type || "";
1267
- const entityNamespace = request.params.namespace || "";
1268
- const entityName = request.params.name || "";
1269
- if (entityType === "" || entityNamespace === "" || entityName === "") {
1270
- response.status(400).json("Required params not specified.");
1271
- return;
1272
- }
1273
- const entityRef = `${entityType}:${entityNamespace}/${entityName}`.toLowerCase();
1274
- const entityMapping = await store.findEntityMappingByEntityRef(entityRef);
1275
- if (!entityMapping) {
1276
- response.status(404).json(`Mapping for entityRef ${entityRef} not found.`);
1277
- return;
1278
- }
1279
- response.json({
1280
- mapping: entityMapping
1281
- });
1282
- } catch (error) {
1283
- if (error instanceof backstagePluginCommon.HttpError) {
1284
- response.status(error.status).json({
1285
- errors: [
1286
- `${error.message}`
1287
- ]
1288
- });
1289
- }
1290
- }
1291
- });
1292
- router.get("/mapping/entity/service/:serviceId", async (request, response) => {
1293
- try {
1294
- const serviceId = request.params.serviceId ?? "";
1295
- if (serviceId === "") {
1296
- response.status(400).json("Required params not specified.");
1297
- return;
1298
- }
1299
- const entityMapping = await store.findEntityMappingByServiceId(serviceId);
1300
- if (!entityMapping) {
1301
- response.status(404).json(`Mapping for serviceId ${serviceId} not found.`);
1302
- return;
1303
- }
1304
- response.json({
1305
- mapping: entityMapping
1306
- });
1307
- } catch (error) {
1308
- if (error instanceof backstagePluginCommon.HttpError) {
1309
- response.status(error.status).json({
1310
- errors: [
1311
- `${error.message}`
1312
- ]
1313
- });
1314
- }
1315
- }
1316
- });
1317
- router.get("/escalation_policies", async (_, response) => {
1318
- try {
1319
- let escalationPolicyList = await getAllEscalationPolicies();
1320
- escalationPolicyList = escalationPolicyList.sort((a, b) => {
1321
- if (a.account === b.account) {
1322
- return a.name.localeCompare(b.name);
1323
- }
1324
- return a.account.localeCompare(b.account);
1325
- });
1326
- const escalationPolicyDropDownOptions = escalationPolicyList.map((policy) => {
1327
- let policyLabel = policy.name;
1328
- if (policy.account && policy.account !== "default") {
1329
- policyLabel = `(${policy.account}) ${policy.name}`;
1330
- }
1331
- return {
1332
- label: policyLabel,
1333
- value: policy.id
1334
- };
1335
- });
1336
- response.json(escalationPolicyDropDownOptions);
1337
- } catch (error) {
1338
- if (error instanceof backstagePluginCommon.HttpError) {
1339
- response.status(error.status).json({
1340
- errors: [
1341
- `${error.message}`
1342
- ]
1343
- });
1344
- }
1345
- }
1346
- });
1347
- router.get("/oncall-users", async (request, response) => {
1348
- try {
1349
- const escalationPolicyId = request.query.escalation_policy_ids || "";
1350
- const account = request.query.account || "";
1351
- if (escalationPolicyId === "") {
1352
- response.status(400).json("Bad Request: 'escalation_policy_ids[]' is required");
1353
- return;
1354
- }
1355
- const oncallUsers = await getOncallUsers(escalationPolicyId, account);
1356
- const onCallUsersResponse = {
1357
- users: oncallUsers
1358
- };
1359
- response.json(onCallUsersResponse);
1360
- } catch (error) {
1361
- if (error instanceof backstagePluginCommon.HttpError) {
1362
- response.status(error.status).json({
1363
- errors: [
1364
- `${error.message}`
1365
- ]
1366
- });
1367
- }
1368
- }
1369
- });
1370
- router.get("/services/:serviceId", async (request, response) => {
1371
- try {
1372
- const serviceId = request.params.serviceId || "";
1373
- const account = request.query.account || "";
1374
- if (serviceId === "") {
1375
- response.status(400).json("Bad Request: ':serviceId' must be provided as part of the path or 'integration_key' as a query parameter");
1376
- return;
1377
- }
1378
- const service = await getServiceById(serviceId, account);
1379
- const serviceResponse = {
1380
- service
1381
- };
1382
- response.json(serviceResponse);
1383
- } catch (error) {
1384
- if (error instanceof backstagePluginCommon.HttpError) {
1385
- response.status(error.status).json({
1386
- errors: [
1387
- `${error.message}`
1388
- ]
1389
- });
1390
- }
1391
- }
1392
- });
1393
- router.get("/services", async (request, response) => {
1394
- try {
1395
- const integrationKey = request.query.integration_key || "";
1396
- const account = request.query.account || "";
1397
- if (integrationKey !== "") {
1398
- const service = await getServiceByIntegrationKey(integrationKey, account);
1399
- const serviceResponse = {
1400
- service
1401
- };
1402
- response.json(serviceResponse);
1403
- } else {
1404
- const services = await getAllServices();
1405
- const servicesResponse = {
1406
- services
1407
- };
1408
- response.json(servicesResponse);
1409
- }
1410
- } catch (error) {
1411
- if (error instanceof backstagePluginCommon.HttpError) {
1412
- response.status(error.status).json({
1413
- errors: [
1414
- `${error.message}`
1415
- ]
1416
- });
1417
- }
1418
- }
1419
- });
1420
- router.post("/services/:serviceId/integration/:vendorId", async (request, response) => {
1421
- try {
1422
- const serviceId = request.params.serviceId || "";
1423
- const vendorId = request.params.vendorId || "";
1424
- const account = request.query.account || "";
1425
- if (serviceId === "" || vendorId === "") {
1426
- response.status(400).json("Bad Request: ':serviceId' and ':vendorId' must be provided as part of the path");
1427
- return;
1428
- }
1429
- const integrationKey = await createServiceIntegration({
1430
- serviceId,
1431
- vendorId,
1432
- account
1433
- });
1434
- response.json(integrationKey);
1435
- } catch (error) {
1436
- if (error instanceof backstagePluginCommon.HttpError) {
1437
- logger.error(`Error occurred while processing request: ${error.message}`);
1438
- response.status(error.status).json({
1439
- errors: [
1440
- `${error.message}`
1441
- ]
1442
- });
1443
- }
1444
- }
1445
- });
1446
- router.get("/services/:serviceId/change-events", async (request, response) => {
1447
- try {
1448
- const serviceId = request.params.serviceId || "";
1449
- const account = request.query.account || "";
1450
- const changeEvents = await getChangeEvents(serviceId, account);
1451
- const changeEventsResponse = {
1452
- change_events: changeEvents
1453
- };
1454
- response.json(changeEventsResponse);
1455
- } catch (error) {
1456
- if (error instanceof backstagePluginCommon.HttpError) {
1457
- response.status(error.status).json({
1458
- errors: [
1459
- `${error.message}`
1460
- ]
1461
- });
1462
- }
1463
- }
1464
- });
1465
- router.get("/services/:serviceId/incidents", async (request, response) => {
1466
- try {
1467
- const serviceId = request.params.serviceId || "";
1468
- const account = request.query.account || "";
1469
- const incidents = await getIncidents(serviceId, account);
1470
- const incidentsResponse = {
1471
- incidents
1472
- };
1473
- response.json(incidentsResponse);
1474
- } catch (error) {
1475
- if (error instanceof backstagePluginCommon.HttpError) {
1476
- response.status(error.status).json({
1477
- errors: [
1478
- `${error.message}`
1479
- ]
1480
- });
1481
- }
1482
- }
1483
- });
1484
- router.get("/services/:serviceId/standards", async (request, response) => {
1485
- try {
1486
- const serviceId = request.params.serviceId || "";
1487
- const account = request.query.account || "";
1488
- const serviceStandards = await getServiceStandards(serviceId, account);
1489
- const serviceStandardsResponse = {
1490
- standards: serviceStandards
1491
- };
1492
- response.json(serviceStandardsResponse);
1493
- } catch (error) {
1494
- if (error instanceof backstagePluginCommon.HttpError) {
1495
- response.status(error.status).json({
1496
- errors: [
1497
- `${error.message}`
1498
- ]
1499
- });
1500
- }
1501
- }
1502
- });
1503
- router.get("/services/:serviceId/metrics", async (request, response) => {
1504
- try {
1505
- const serviceId = request.params.serviceId || "";
1506
- const account = request.query.account || "";
1507
- const metrics = await getServiceMetrics(serviceId, account);
1508
- const metricsResponse = {
1509
- metrics
1510
- };
1511
- response.json(metricsResponse);
1512
- } catch (error) {
1513
- if (error instanceof backstagePluginCommon.HttpError) {
1514
- response.status(error.status).json({
1515
- errors: [
1516
- `${error.message}`
1517
- ]
1518
- });
1519
- }
1520
- }
1521
- });
1522
- router.get("/health", async (_, response) => {
1523
- response.status(200).json({ status: "ok" });
1524
- });
1525
- router.use(backendCommon.errorHandler());
1526
- return router;
1527
- }
1528
-
1529
- class PagerDutyBackendDatabase {
1530
- constructor(db) {
1531
- this.db = db;
1532
- }
1533
- static async create(knex, options) {
1534
- if (options?.skipMigrations) {
1535
- const migrationsDir = backendPluginApi.resolvePackagePath("@pagerduty/backstage-plugin-backend", "migrations");
1536
- await knex.migrate.latest({
1537
- directory: migrationsDir
1538
- });
1539
- }
1540
- return new PagerDutyBackendDatabase(knex);
1541
- }
1542
- async insertEntityMapping(entity) {
1543
- const entityMappingId = uuid.v4();
1544
- const [result] = await this.db("pagerduty_entity_mapping").insert({
1545
- id: entityMappingId,
1546
- entityRef: entity.entityRef,
1547
- serviceId: entity.serviceId,
1548
- integrationKey: entity.integrationKey,
1549
- account: entity.account,
1550
- processedDate: /* @__PURE__ */ new Date()
1551
- }).onConflict(["serviceId"]).merge(["entityRef", "integrationKey", "account", "processedDate"]).returning("id");
1552
- return result.id;
1553
- }
1554
- async getAllEntityMappings() {
1555
- const rawEntities = await this.db("pagerduty_entity_mapping");
1556
- if (!rawEntities) {
1557
- return [];
1558
- }
1559
- return rawEntities;
1560
- }
1561
- async findEntityMappingByEntityRef(entityRef) {
1562
- const rawEntity = await this.db("pagerduty_entity_mapping").where("entityRef", entityRef).first();
1563
- return rawEntity;
1564
- }
1565
- async findEntityMappingByServiceId(serviceId) {
1566
- const rawEntity = await this.db("pagerduty_entity_mapping").where("serviceId", serviceId).first();
1567
- return rawEntity;
1568
- }
1569
- async updateSetting(setting) {
1570
- const [result] = await this.db("pagerduty_settings").insert({
1571
- id: setting.id,
1572
- value: setting.value
1573
- }).onConflict(["id"]).merge(["value"]).returning("id");
1574
- return result.id;
1575
- }
1576
- async findSetting(settingId) {
1577
- const rawEntity = await this.db("pagerduty_settings").where("id", settingId).first();
1578
- return rawEntity;
1579
- }
1580
- async getAllSettings() {
1581
- const rawEntities = await this.db("pagerduty_settings");
1582
- if (!rawEntities) {
1583
- return [];
1584
- }
1585
- return rawEntities;
1586
- }
1587
- }
1588
-
1589
- class CatalogFetchApi {
1590
- constructor(logger, auth) {
1591
- this.logger = logger;
1592
- this.auth = auth;
1593
- }
1594
- async fetch(input, init) {
1595
- const request = new Request(input, init);
1596
- const { token } = await this.auth.getPluginRequestToken({
1597
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
1598
- targetPluginId: "catalog"
1599
- });
1600
- request.headers.set("Authorization", `Bearer ${token}`);
1601
- this.logger.debug(`Added token to outgoing request to ${request.url}`);
1602
- return fetch(request);
1603
- }
1604
- }
1605
- const pagerDutyPlugin = backendPluginApi.createBackendPlugin({
1606
- pluginId: "pagerduty",
1607
- register(env) {
1608
- env.registerInit({
1609
- deps: {
1610
- logger: backendPluginApi.coreServices.logger,
1611
- config: backendPluginApi.coreServices.rootConfig,
1612
- httpRouter: backendPluginApi.coreServices.httpRouter,
1613
- database: backendPluginApi.coreServices.database,
1614
- discovery: backendPluginApi.coreServices.discovery,
1615
- auth: backendPluginApi.coreServices.auth
1616
- },
1617
- async init({ config, logger, httpRouter, database, discovery, auth }) {
1618
- const pagerDutyBackendStore = await PagerDutyBackendDatabase.create(
1619
- await database.getClient(),
1620
- { skipMigrations: true }
1621
- );
1622
- httpRouter.use(
1623
- await createRouter({
1624
- config,
1625
- logger,
1626
- store: pagerDutyBackendStore,
1627
- discovery,
1628
- auth,
1629
- catalogApi: new catalogClient.CatalogClient({
1630
- discoveryApi: discovery,
1631
- fetchApi: new CatalogFetchApi(logger, auth)
1632
- })
1633
- })
1634
- );
1635
- httpRouter.addAuthPolicy({
1636
- path: "/",
1637
- allow: "unauthenticated"
1638
- });
1639
- }
1640
- });
1641
- }
1642
- });
1643
-
1644
- exports.buildEntityMappingsResponse = buildEntityMappingsResponse;
1645
- exports.createComponentEntitiesReferenceDict = createComponentEntitiesReferenceDict;
1646
- exports.createRouter = createRouter;
1647
- exports.default = pagerDutyPlugin;
10
+ exports.buildEntityMappingsResponse = router.buildEntityMappingsResponse;
11
+ exports.createComponentEntitiesReferenceDict = router.createComponentEntitiesReferenceDict;
12
+ exports.createRouter = router.createRouter;
13
+ exports.default = plugin.pagerDutyPlugin;
1648
14
  //# sourceMappingURL=index.cjs.js.map