@backstage/plugin-kubernetes-backend 0.18.6 → 0.18.7-next.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/alpha/package.json +1 -1
  3. package/dist/alpha.cjs.js +3 -150
  4. package/dist/alpha.cjs.js.map +1 -1
  5. package/dist/auth/AksStrategy.cjs.js +17 -0
  6. package/dist/auth/AksStrategy.cjs.js.map +1 -0
  7. package/dist/auth/AnonymousStrategy.cjs.js +16 -0
  8. package/dist/auth/AnonymousStrategy.cjs.js.map +1 -0
  9. package/dist/auth/AwsIamStrategy.cjs.js +80 -0
  10. package/dist/auth/AwsIamStrategy.cjs.js.map +1 -0
  11. package/dist/auth/AzureIdentityStrategy.cjs.js +58 -0
  12. package/dist/auth/AzureIdentityStrategy.cjs.js.map +1 -0
  13. package/dist/auth/DispatchStrategy.cjs.js +37 -0
  14. package/dist/auth/DispatchStrategy.cjs.js.map +1 -0
  15. package/dist/auth/GoogleServiceAccountStrategy.cjs.js +45 -0
  16. package/dist/auth/GoogleServiceAccountStrategy.cjs.js.map +1 -0
  17. package/dist/auth/GoogleStrategy.cjs.js +22 -0
  18. package/dist/auth/GoogleStrategy.cjs.js.map +1 -0
  19. package/dist/auth/OidcStrategy.cjs.js +34 -0
  20. package/dist/auth/OidcStrategy.cjs.js.map +1 -0
  21. package/dist/auth/ServiceAccountStrategy.cjs.js +33 -0
  22. package/dist/auth/ServiceAccountStrategy.cjs.js.map +1 -0
  23. package/dist/cluster-locator/CatalogClusterLocator.cjs.js +73 -0
  24. package/dist/cluster-locator/CatalogClusterLocator.cjs.js.map +1 -0
  25. package/dist/cluster-locator/ConfigClusterLocator.cjs.js +100 -0
  26. package/dist/cluster-locator/ConfigClusterLocator.cjs.js.map +1 -0
  27. package/dist/cluster-locator/GkeClusterLocator.cjs.js +126 -0
  28. package/dist/cluster-locator/GkeClusterLocator.cjs.js.map +1 -0
  29. package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js +35 -0
  30. package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js.map +1 -0
  31. package/dist/cluster-locator/index.cjs.js +67 -0
  32. package/dist/cluster-locator/index.cjs.js.map +1 -0
  33. package/dist/index.cjs.js +31 -1904
  34. package/dist/index.cjs.js.map +1 -1
  35. package/dist/package.json.cjs.js +156 -0
  36. package/dist/package.json.cjs.js.map +1 -0
  37. package/dist/plugin.cjs.js +155 -0
  38. package/dist/plugin.cjs.js.map +1 -0
  39. package/dist/routes/resourcesRoutes.cjs.js +65 -0
  40. package/dist/routes/resourcesRoutes.cjs.js.map +1 -0
  41. package/dist/service/KubernetesBuilder.cjs.js +367 -0
  42. package/dist/service/KubernetesBuilder.cjs.js.map +1 -0
  43. package/dist/service/KubernetesFanOutHandler.cjs.js +254 -0
  44. package/dist/service/KubernetesFanOutHandler.cjs.js.map +1 -0
  45. package/dist/service/KubernetesFetcher.cjs.js +231 -0
  46. package/dist/service/KubernetesFetcher.cjs.js.map +1 -0
  47. package/dist/service/KubernetesProxy.cjs.js +195 -0
  48. package/dist/service/KubernetesProxy.cjs.js.map +1 -0
  49. package/dist/service/router.cjs.js +11 -0
  50. package/dist/service/router.cjs.js.map +1 -0
  51. package/dist/service/runPeriodically.cjs.js +29 -0
  52. package/dist/service/runPeriodically.cjs.js.map +1 -0
  53. package/dist/service-locator/CatalogRelationServiceLocator.cjs.js +31 -0
  54. package/dist/service-locator/CatalogRelationServiceLocator.cjs.js.map +1 -0
  55. package/dist/service-locator/MultiTenantServiceLocator.cjs.js +15 -0
  56. package/dist/service-locator/MultiTenantServiceLocator.cjs.js.map +1 -0
  57. package/dist/service-locator/SingleTenantServiceLocator.cjs.js +24 -0
  58. package/dist/service-locator/SingleTenantServiceLocator.cjs.js.map +1 -0
  59. package/package.json +21 -20
package/dist/index.cjs.js CHANGED
@@ -1,1907 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var credentialProviders = require('@aws-sdk/credential-providers');
4
- var signatureV4 = require('@aws-sdk/signature-v4');
5
- var sha256Js = require('@aws-crypto/sha256-js');
6
- var integrationAwsNode = require('@backstage/integration-aws-node');
7
- var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
8
- var identity = require('@azure/identity');
9
- var container = require('@google-cloud/container');
10
- var clientNode = require('@kubernetes/client-node');
11
- var fs = require('fs-extra');
12
- var pluginPermissionNode = require('@backstage/plugin-permission-node');
13
- var express = require('express');
14
- var Router = require('express-promise-router');
15
- var luxon = require('luxon');
16
- var errors = require('@backstage/errors');
17
- var catalogClient = require('@backstage/catalog-client');
18
- var dns = require('node:dns');
19
- var backendCommon = require('@backstage/backend-common');
20
- var catalogModel = require('@backstage/catalog-model');
21
- var lodash = require('lodash');
22
- var fetch = require('node-fetch');
23
- var https = require('https');
24
- var pluginPermissionCommon = require('@backstage/plugin-permission-common');
25
- var httpProxyMiddleware = require('http-proxy-middleware');
26
-
27
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
28
-
29
- function _interopNamespaceCompat(e) {
30
- if (e && typeof e === 'object' && 'default' in e) return e;
31
- var n = Object.create(null);
32
- if (e) {
33
- Object.keys(e).forEach(function (k) {
34
- if (k !== 'default') {
35
- var d = Object.getOwnPropertyDescriptor(e, k);
36
- Object.defineProperty(n, k, d.get ? d : {
37
- enumerable: true,
38
- get: function () { return e[k]; }
39
- });
40
- }
41
- });
42
- }
43
- n.default = e;
44
- return Object.freeze(n);
45
- }
46
-
47
- var container__namespace = /*#__PURE__*/_interopNamespaceCompat(container);
48
- var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
49
- var express__default = /*#__PURE__*/_interopDefaultCompat(express);
50
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
51
- var dns__default = /*#__PURE__*/_interopDefaultCompat(dns);
52
- var lodash__default = /*#__PURE__*/_interopDefaultCompat(lodash);
53
- var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
54
- var https__namespace = /*#__PURE__*/_interopNamespaceCompat(https);
55
-
56
- class AksStrategy {
57
- async getCredential(_, requestAuth) {
58
- const token = requestAuth.aks;
59
- return token ? { type: "bearer token", token } : { type: "anonymous" };
60
- }
61
- validateCluster() {
62
- return [];
63
- }
64
- presentAuthMetadata(_authMetadata) {
65
- return {};
66
- }
67
- }
68
-
69
- class AnonymousStrategy {
70
- async getCredential() {
71
- return { type: "anonymous" };
72
- }
73
- validateCluster() {
74
- return [];
75
- }
76
- presentAuthMetadata(_authMetadata) {
77
- return {};
78
- }
79
- }
80
-
81
- const defaultRegion = "us-east-1";
82
- class AwsIamStrategy {
83
- credsManager;
84
- constructor(opts) {
85
- this.credsManager = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(opts.config);
86
- }
87
- async getCredential(clusterDetails) {
88
- return {
89
- type: "bearer token",
90
- token: await this.getBearerToken(
91
- clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_CLUSTER_ID] ?? clusterDetails.name,
92
- clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE],
93
- clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]
94
- )
95
- };
96
- }
97
- validateCluster() {
98
- return [];
99
- }
100
- async getBearerToken(clusterId, assumeRole, externalId) {
101
- const region = process.env.AWS_REGION ?? defaultRegion;
102
- let credentials = (await this.credsManager.getCredentialProvider()).sdkCredentialProvider;
103
- if (assumeRole) {
104
- credentials = credentialProviders.fromTemporaryCredentials({
105
- masterCredentials: credentials,
106
- clientConfig: {
107
- region
108
- },
109
- params: {
110
- RoleArn: assumeRole,
111
- ExternalId: externalId
112
- }
113
- });
114
- }
115
- const signer = new signatureV4.SignatureV4({
116
- credentials,
117
- region,
118
- service: "sts",
119
- sha256: sha256Js.Sha256
120
- });
121
- const request = await signer.presign(
122
- {
123
- headers: {
124
- host: `sts.${region}.amazonaws.com`,
125
- "x-k8s-aws-id": clusterId
126
- },
127
- hostname: `sts.${region}.amazonaws.com`,
128
- method: "GET",
129
- path: "/",
130
- protocol: "https:",
131
- query: {
132
- Action: "GetCallerIdentity",
133
- Version: "2011-06-15"
134
- }
135
- },
136
- { expiresIn: 0 }
137
- );
138
- const query = Object.keys(request?.query ?? {}).map(
139
- (q) => `${encodeURIComponent(q)}=${encodeURIComponent(
140
- request.query?.[q]
141
- )}`
142
- ).join("&");
143
- const url = `https://${request.hostname}${request.path}?${query}`;
144
- return `k8s-aws-v1.${Buffer.from(url).toString("base64url")}`;
145
- }
146
- presentAuthMetadata(_authMetadata) {
147
- return {};
148
- }
149
- }
150
-
151
- const aksScope = "6dae42f8-4368-4678-94ff-3960e28e3630/.default";
152
- class AzureIdentityStrategy {
153
- constructor(logger, tokenCredential = new identity.DefaultAzureCredential()) {
154
- this.logger = logger;
155
- this.tokenCredential = tokenCredential;
156
- }
157
- accessToken = { token: "", expiresOnTimestamp: 0 };
158
- newTokenPromise;
159
- async getCredential() {
160
- if (!this.tokenRequiresRefresh()) {
161
- return { type: "bearer token", token: this.accessToken.token };
162
- }
163
- if (!this.newTokenPromise) {
164
- this.newTokenPromise = this.fetchNewToken();
165
- }
166
- return this.newTokenPromise ? { type: "bearer token", token: await this.newTokenPromise } : { type: "anonymous" };
167
- }
168
- validateCluster() {
169
- return [];
170
- }
171
- async fetchNewToken() {
172
- try {
173
- this.logger.info("Fetching new Azure token for AKS");
174
- const newAccessToken = await this.tokenCredential.getToken(aksScope, {
175
- requestOptions: { timeout: 1e4 }
176
- // 10 seconds
177
- });
178
- if (!newAccessToken) {
179
- throw new Error("AccessToken is null");
180
- }
181
- this.accessToken = newAccessToken;
182
- } catch (err) {
183
- this.logger.error("Unable to fetch Azure token", err);
184
- if (this.tokenExpired()) {
185
- throw err;
186
- }
187
- }
188
- this.newTokenPromise = void 0;
189
- return this.accessToken.token;
190
- }
191
- tokenRequiresRefresh() {
192
- const expiresOn = this.accessToken.expiresOnTimestamp - 15 * 60 * 1e3;
193
- return Date.now() >= expiresOn;
194
- }
195
- tokenExpired() {
196
- return Date.now() >= this.accessToken.expiresOnTimestamp;
197
- }
198
- presentAuthMetadata(_authMetadata) {
199
- return {};
200
- }
201
- }
202
-
203
- class GoogleStrategy {
204
- async getCredential(_, requestAuth) {
205
- const token = requestAuth.google;
206
- if (!token) {
207
- throw new Error(
208
- "Google token not found under auth.google in request body"
209
- );
210
- }
211
- return { type: "bearer token", token };
212
- }
213
- validateCluster() {
214
- return [];
215
- }
216
- presentAuthMetadata(_authMetadata) {
217
- return {};
218
- }
219
- }
220
-
221
- class GoogleServiceAccountStrategy {
222
- async getCredential() {
223
- const client = new container__namespace.v1.ClusterManagerClient();
224
- const token = await client.auth.getAccessToken();
225
- if (!token) {
226
- throw new Error(
227
- "Unable to obtain access token for the current Google Application Default Credentials"
228
- );
229
- }
230
- return { type: "bearer token", token };
231
- }
232
- validateCluster() {
233
- return [];
234
- }
235
- presentAuthMetadata(_authMetadata) {
236
- return {};
237
- }
238
- }
239
-
240
- class DispatchStrategy {
241
- strategyMap;
242
- constructor(options) {
243
- this.strategyMap = options.authStrategyMap;
244
- }
245
- getCredential(clusterDetails, auth) {
246
- const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
247
- if (this.strategyMap[authProvider]) {
248
- return this.strategyMap[authProvider].getCredential(clusterDetails, auth);
249
- }
250
- throw new Error(
251
- `authProvider "${authProvider}" has no AuthenticationStrategy associated with it`
252
- );
253
- }
254
- validateCluster(authMetadata) {
255
- const authProvider = authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
256
- const strategy = this.strategyMap[authProvider];
257
- if (!strategy) {
258
- return [
259
- new Error(
260
- `authProvider "${authProvider}" has no config associated with it`
261
- )
262
- ];
263
- }
264
- return strategy.validateCluster(authMetadata);
265
- }
266
- presentAuthMetadata(_authMetadata) {
267
- return {};
268
- }
269
- }
270
-
271
- class ServiceAccountStrategy {
272
- async getCredential(clusterDetails) {
273
- const token = clusterDetails.authMetadata.serviceAccountToken;
274
- if (token) {
275
- return { type: "bearer token", token };
276
- }
277
- const kc = new clientNode.KubeConfig();
278
- kc.loadFromCluster();
279
- const user = kc.getCurrentUser();
280
- return {
281
- type: "bearer token",
282
- token: fs__default.default.readFileSync(user.authProvider.config.tokenFile).toString()
283
- };
284
- }
285
- validateCluster() {
286
- return [];
287
- }
288
- presentAuthMetadata(_authMetadata) {
289
- return {};
290
- }
291
- }
292
-
293
- class OidcStrategy {
294
- async getCredential(clusterDetails, authConfig) {
295
- const oidcTokenProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
296
- if (!oidcTokenProvider || oidcTokenProvider === "") {
297
- throw new Error(
298
- `oidc authProvider requires a configured oidcTokenProvider`
299
- );
300
- }
301
- const token = authConfig.oidc?.[oidcTokenProvider];
302
- if (!token) {
303
- throw new Error(
304
- `Auth token not found under oidc.${oidcTokenProvider} in request body`
305
- );
306
- }
307
- return { type: "bearer token", token };
308
- }
309
- validateCluster(authMetadata) {
310
- const oidcTokenProvider = authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
311
- if (!oidcTokenProvider || oidcTokenProvider === "") {
312
- return [new Error(`Must specify a token provider for 'oidc' strategy`)];
313
- }
314
- return [];
315
- }
316
- presentAuthMetadata(_authMetadata) {
317
- return {};
318
- }
319
- }
320
-
321
- class ConfigClusterLocator {
322
- clusterDetails;
323
- constructor(clusterDetails) {
324
- this.clusterDetails = clusterDetails;
325
- }
326
- static fromConfig(config, authStrategy) {
327
- const clusterNames = /* @__PURE__ */ new Set();
328
- return new ConfigClusterLocator(
329
- config.getConfigArray("clusters").map((c) => {
330
- const authMetadataBlock = c.getOptional("authMetadata");
331
- const name = c.getString("name");
332
- if (clusterNames.has(name)) {
333
- throw new Error(`Duplicate cluster name '${name}'`);
334
- }
335
- clusterNames.add(name);
336
- const authProvider = authMetadataBlock?.[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER] ?? c.getOptionalString("authProvider");
337
- if (!authProvider) {
338
- throw new Error(
339
- `cluster '${name}' has no auth provider configured; this must be specified via the 'authProvider' or 'authMetadata.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`
340
- );
341
- }
342
- const title = c.getOptionalString("title");
343
- const clusterDetails = {
344
- name,
345
- ...title && { title },
346
- url: c.getString("url"),
347
- skipTLSVerify: c.getOptionalBoolean("skipTLSVerify") ?? false,
348
- skipMetricsLookup: c.getOptionalBoolean("skipMetricsLookup") ?? false,
349
- caData: c.getOptionalString("caData"),
350
- caFile: c.getOptionalString("caFile"),
351
- authMetadata: {
352
- [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider,
353
- ...ConfigClusterLocator.parseAuthMetadata(c),
354
- ...authMetadataBlock
355
- }
356
- };
357
- const customResources = c.getOptionalConfigArray("customResources");
358
- if (customResources) {
359
- clusterDetails.customResources = customResources.map((cr) => {
360
- return {
361
- group: cr.getString("group"),
362
- apiVersion: cr.getString("apiVersion"),
363
- plural: cr.getString("plural")
364
- };
365
- });
366
- }
367
- const dashboardUrl = c.getOptionalString("dashboardUrl");
368
- if (dashboardUrl) {
369
- clusterDetails.dashboardUrl = dashboardUrl;
370
- }
371
- const dashboardApp = c.getOptionalString("dashboardApp");
372
- if (dashboardApp) {
373
- clusterDetails.dashboardApp = dashboardApp;
374
- }
375
- if (c.has("dashboardParameters")) {
376
- clusterDetails.dashboardParameters = c.get("dashboardParameters");
377
- }
378
- const validationErrors = authStrategy.validateCluster(
379
- clusterDetails.authMetadata
380
- );
381
- if (validationErrors.length !== 0) {
382
- throw new Error(
383
- `Invalid cluster '${clusterDetails.name}': ${validationErrors.map((e) => e.message).join(", ")}`
384
- );
385
- }
386
- return clusterDetails;
387
- })
388
- );
389
- }
390
- static parseAuthMetadata(clusterConfig) {
391
- const serviceAccountToken = clusterConfig.getOptionalString(
392
- "serviceAccountToken"
393
- );
394
- const assumeRole = clusterConfig.getOptionalString("assumeRole");
395
- const externalId = clusterConfig.getOptionalString("externalId");
396
- const oidcTokenProvider = clusterConfig.getOptionalString("oidcTokenProvider");
397
- return serviceAccountToken || assumeRole || externalId || oidcTokenProvider ? {
398
- ...serviceAccountToken && { serviceAccountToken },
399
- ...assumeRole && {
400
- [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: assumeRole
401
- },
402
- ...externalId && {
403
- [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: externalId
404
- },
405
- ...oidcTokenProvider && {
406
- [pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: oidcTokenProvider
407
- }
408
- } : void 0;
409
- }
410
- async getClusters() {
411
- return this.clusterDetails;
412
- }
413
- }
414
-
415
- function runPeriodically(fn, delayMs) {
416
- let cancel;
417
- let cancelled = false;
418
- const cancellationPromise = new Promise((resolve) => {
419
- cancel = () => {
420
- resolve();
421
- cancelled = true;
422
- };
423
- });
424
- const startRefresh = async () => {
425
- while (!cancelled) {
426
- try {
427
- await fn();
428
- } catch {
429
- }
430
- await Promise.race([
431
- new Promise((resolve) => setTimeout(resolve, delayMs)),
432
- cancellationPromise
433
- ]);
434
- }
435
- };
436
- startRefresh();
437
- return cancel;
438
- }
439
-
440
- var name = "@backstage/plugin-kubernetes-backend";
441
- var version = "0.18.6";
442
- var description = "A Backstage backend plugin that integrates towards Kubernetes";
443
- var backstage = {
444
- role: "backend-plugin",
445
- pluginId: "kubernetes",
446
- pluginPackages: [
447
- "@backstage/plugin-kubernetes",
448
- "@backstage/plugin-kubernetes-backend",
449
- "@backstage/plugin-kubernetes-common",
450
- "@backstage/plugin-kubernetes-node",
451
- "@backstage/plugin-kubernetes-react"
452
- ]
453
- };
454
- var publishConfig = {
455
- access: "public"
456
- };
457
- var keywords = [
458
- "backstage",
459
- "kubernetes"
460
- ];
461
- var homepage = "https://backstage.io";
462
- var repository = {
463
- type: "git",
464
- url: "https://github.com/backstage/backstage",
465
- directory: "plugins/kubernetes-backend"
466
- };
467
- var license = "Apache-2.0";
468
- var exports$1 = {
469
- ".": "./src/index.ts",
470
- "./alpha": "./src/alpha.ts",
471
- "./package.json": "./package.json"
472
- };
473
- var main = "src/index.ts";
474
- var types = "src/index.ts";
475
- var typesVersions = {
476
- "*": {
477
- alpha: [
478
- "src/alpha.ts"
479
- ],
480
- "package.json": [
481
- "package.json"
482
- ]
483
- }
484
- };
485
- var files = [
486
- "dist",
487
- "config.d.ts"
488
- ];
489
- var scripts = {
490
- build: "backstage-cli package build",
491
- clean: "backstage-cli package clean",
492
- lint: "backstage-cli package lint",
493
- prepack: "backstage-cli package prepack",
494
- postpack: "backstage-cli package postpack",
495
- start: "backstage-cli package start",
496
- test: "backstage-cli package test"
497
- };
498
- var dependencies = {
499
- "@aws-crypto/sha256-js": "^5.0.0",
500
- "@aws-sdk/credential-providers": "^3.350.0",
501
- "@aws-sdk/signature-v4": "^3.347.0",
502
- "@azure/identity": "^4.0.0",
503
- "@backstage/backend-common": "workspace:^",
504
- "@backstage/backend-plugin-api": "workspace:^",
505
- "@backstage/catalog-client": "workspace:^",
506
- "@backstage/catalog-model": "workspace:^",
507
- "@backstage/config": "workspace:^",
508
- "@backstage/errors": "workspace:^",
509
- "@backstage/integration-aws-node": "workspace:^",
510
- "@backstage/plugin-auth-node": "workspace:^",
511
- "@backstage/plugin-catalog-node": "workspace:^",
512
- "@backstage/plugin-kubernetes-common": "workspace:^",
513
- "@backstage/plugin-kubernetes-node": "workspace:^",
514
- "@backstage/plugin-permission-common": "workspace:^",
515
- "@backstage/plugin-permission-node": "workspace:^",
516
- "@backstage/types": "workspace:^",
517
- "@google-cloud/container": "^5.0.0",
518
- "@jest-mock/express": "^2.0.1",
519
- "@kubernetes/client-node": "0.20.0",
520
- "@types/express": "^4.17.6",
521
- "@types/http-proxy-middleware": "^1.0.0",
522
- "@types/luxon": "^3.0.0",
523
- compression: "^1.7.4",
524
- cors: "^2.8.5",
525
- express: "^4.17.1",
526
- "express-promise-router": "^4.1.0",
527
- "fs-extra": "^11.2.0",
528
- helmet: "^6.0.0",
529
- "http-proxy-middleware": "^2.0.6",
530
- lodash: "^4.17.21",
531
- luxon: "^3.0.0",
532
- morgan: "^1.10.0",
533
- "node-fetch": "^2.7.0",
534
- "stream-buffers": "^3.0.2",
535
- winston: "^3.2.1",
536
- yn: "^4.0.0"
537
- };
538
- var devDependencies = {
539
- "@backstage/backend-app-api": "workspace:^",
540
- "@backstage/backend-defaults": "workspace:^",
541
- "@backstage/backend-test-utils": "workspace:^",
542
- "@backstage/cli": "workspace:^",
543
- "@backstage/plugin-permission-backend": "workspace:^",
544
- "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
545
- "@types/aws4": "^1.5.1",
546
- msw: "^1.0.0",
547
- supertest: "^7.0.0",
548
- ws: "^8.18.0"
549
- };
550
- var configSchema = "config.d.ts";
551
- var packageinfo = {
552
- name: name,
553
- version: version,
554
- description: description,
555
- backstage: backstage,
556
- publishConfig: publishConfig,
557
- keywords: keywords,
558
- homepage: homepage,
559
- repository: repository,
560
- license: license,
561
- exports: exports$1,
562
- main: main,
563
- types: types,
564
- typesVersions: typesVersions,
565
- files: files,
566
- scripts: scripts,
567
- dependencies: dependencies,
568
- devDependencies: devDependencies,
569
- configSchema: configSchema
570
- };
571
-
572
- class GkeClusterLocator {
573
- constructor(options, client, clusterDetails = void 0, hasClusterDetails = false) {
574
- this.options = options;
575
- this.client = client;
576
- this.clusterDetails = clusterDetails;
577
- this.hasClusterDetails = hasClusterDetails;
578
- }
579
- static fromConfigWithClient(config, client, refreshInterval) {
580
- const matchingResourceLabels = config.getOptionalConfigArray("matchingResourceLabels")?.map((mrl) => {
581
- return { key: mrl.getString("key"), value: mrl.getString("value") };
582
- }) ?? [];
583
- const storeAuthProviderString = config.getOptionalString("authProvider") === "googleServiceAccount" ? "googleServiceAccount" : "google";
584
- const options = {
585
- projectId: config.getString("projectId"),
586
- authProvider: storeAuthProviderString,
587
- region: config.getOptionalString("region") ?? "-",
588
- skipTLSVerify: config.getOptionalBoolean("skipTLSVerify") ?? false,
589
- skipMetricsLookup: config.getOptionalBoolean("skipMetricsLookup") ?? false,
590
- exposeDashboard: config.getOptionalBoolean("exposeDashboard") ?? false,
591
- matchingResourceLabels
592
- };
593
- const gkeClusterLocator = new GkeClusterLocator(options, client);
594
- if (refreshInterval) {
595
- runPeriodically(
596
- () => gkeClusterLocator.refreshClusters(),
597
- refreshInterval.toMillis()
598
- );
599
- }
600
- return gkeClusterLocator;
601
- }
602
- // Added an `x-goog-api-client` header to API requests made by the GKE cluster locator to clearly identify API requests from this plugin.
603
- static fromConfig(config, refreshInterval = void 0) {
604
- return GkeClusterLocator.fromConfigWithClient(
605
- config,
606
- new container__namespace.v1.ClusterManagerClient({
607
- libName: `backstage/kubernetes-backend.GkeClusterLocator`,
608
- libVersion: packageinfo.version
609
- }),
610
- refreshInterval
611
- );
612
- }
613
- async getClusters() {
614
- if (!this.hasClusterDetails) {
615
- await this.refreshClusters();
616
- }
617
- return this.clusterDetails ?? [];
618
- }
619
- // TODO pass caData into the object
620
- async refreshClusters() {
621
- const {
622
- projectId,
623
- region,
624
- authProvider,
625
- skipTLSVerify,
626
- skipMetricsLookup,
627
- exposeDashboard,
628
- matchingResourceLabels
629
- } = this.options;
630
- const request = {
631
- parent: `projects/${projectId}/locations/${region}`
632
- };
633
- try {
634
- const [response] = await this.client.listClusters(request);
635
- this.clusterDetails = (response.clusters ?? []).filter((r) => {
636
- return matchingResourceLabels?.every((mrl) => {
637
- if (!r.resourceLabels) {
638
- return false;
639
- }
640
- return r.resourceLabels[mrl.key] === mrl.value;
641
- });
642
- }).map((r) => ({
643
- // TODO filter out clusters which don't have name or endpoint
644
- name: r.name ?? "unknown",
645
- url: `https://${r.endpoint ?? ""}`,
646
- authMetadata: { [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider },
647
- skipTLSVerify,
648
- skipMetricsLookup,
649
- ...exposeDashboard ? {
650
- dashboardApp: "gke",
651
- dashboardParameters: {
652
- projectId,
653
- region,
654
- clusterName: r.name
655
- }
656
- } : {}
657
- }));
658
- this.hasClusterDetails = true;
659
- } catch (e) {
660
- throw new errors.ForwardedError(
661
- `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
662
- e
663
- );
664
- }
665
- }
666
- }
667
-
668
- function isObject(obj) {
669
- return typeof obj === "object" && obj !== null && !Array.isArray(obj);
670
- }
671
- class CatalogClusterLocator {
672
- catalogClient;
673
- auth;
674
- constructor(catalogClient, auth) {
675
- this.catalogClient = catalogClient;
676
- this.auth = auth;
677
- }
678
- static fromConfig(catalogApi, auth) {
679
- return new CatalogClusterLocator(catalogApi, auth);
680
- }
681
- async getClusters(options) {
682
- const apiServerKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER}`;
683
- const apiServerCaKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA}`;
684
- const authProviderKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;
685
- const filter = {
686
- kind: "Resource",
687
- "spec.type": "kubernetes-cluster",
688
- [apiServerKey]: catalogClient.CATALOG_FILTER_EXISTS,
689
- [apiServerCaKey]: catalogClient.CATALOG_FILTER_EXISTS,
690
- [authProviderKey]: catalogClient.CATALOG_FILTER_EXISTS
691
- };
692
- const clusters = await this.catalogClient.getEntities(
693
- {
694
- filter: [filter]
695
- },
696
- options?.credentials ? {
697
- token: (await this.auth.getPluginRequestToken({
698
- onBehalfOf: options.credentials,
699
- targetPluginId: "catalog"
700
- })).token
701
- } : void 0
702
- );
703
- return clusters.items.map((entity) => {
704
- const annotations = entity.metadata.annotations;
705
- const clusterDetails = {
706
- name: entity.metadata.name,
707
- title: entity.metadata.title,
708
- url: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER],
709
- authMetadata: annotations,
710
- caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
711
- skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
712
- skipTLSVerify: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true",
713
- dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
714
- dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
715
- dashboardParameters: this.getDashboardParameters(annotations)
716
- };
717
- return clusterDetails;
718
- });
719
- }
720
- getDashboardParameters(annotations) {
721
- const dashboardParamsString = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];
722
- if (dashboardParamsString) {
723
- try {
724
- const dashboardParams = JSON.parse(dashboardParamsString);
725
- return isObject(dashboardParams) ? dashboardParams : void 0;
726
- } catch {
727
- return void 0;
728
- }
729
- }
730
- return void 0;
731
- }
732
- }
733
-
734
- class LocalKubectlProxyClusterLocator {
735
- clusterDetails;
736
- // verbatim: when false, IPv4 addresses are placed before IPv6 addresses, ignoring the order from the DNS resolver
737
- // By default kubectl proxy listens on 127.0.0.1 instead of [::1]
738
- lookupPromise = dns__default.default.promises.lookup("localhost", { verbatim: false });
739
- constructor() {
740
- this.clusterDetails = [
741
- {
742
- name: "local",
743
- url: "http://localhost:8001",
744
- authMetadata: {
745
- [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: "localKubectlProxy"
746
- },
747
- skipMetricsLookup: true
748
- }
749
- ];
750
- }
751
- async getClusters() {
752
- const lookupResolution = await this.lookupPromise;
753
- this.clusterDetails[0].url = `http://${lookupResolution.address}:8001`;
754
- return this.clusterDetails;
755
- }
756
- }
757
-
758
- class CombinedClustersSupplier {
759
- constructor(clusterSuppliers, logger) {
760
- this.clusterSuppliers = clusterSuppliers;
761
- this.logger = logger;
762
- }
763
- async getClusters(options) {
764
- const clusters = await Promise.all(
765
- this.clusterSuppliers.map((supplier) => supplier.getClusters(options))
766
- ).then((res) => {
767
- return res.flat();
768
- }).catch((e) => {
769
- throw e;
770
- });
771
- return this.warnDuplicates(clusters);
772
- }
773
- warnDuplicates(clusters) {
774
- const clusterNames = /* @__PURE__ */ new Set();
775
- const duplicatedNames = /* @__PURE__ */ new Set();
776
- for (const clusterName of clusters.map((c) => c.name)) {
777
- if (clusterNames.has(clusterName)) {
778
- duplicatedNames.add(clusterName);
779
- } else {
780
- clusterNames.add(clusterName);
781
- }
782
- }
783
- for (const clusterName of duplicatedNames) {
784
- this.logger.warn(`Duplicate cluster name '${clusterName}'`);
785
- }
786
- return clusters;
787
- }
788
- }
789
- const getCombinedClusterSupplier = (rootConfig, catalogClient, authStrategy, logger, refreshInterval = void 0, auth) => {
790
- const clusterSuppliers = rootConfig.getConfigArray("kubernetes.clusterLocatorMethods").map((clusterLocatorMethod) => {
791
- const type = clusterLocatorMethod.getString("type");
792
- switch (type) {
793
- case "catalog":
794
- return CatalogClusterLocator.fromConfig(catalogClient, auth);
795
- case "localKubectlProxy":
796
- return new LocalKubectlProxyClusterLocator();
797
- case "config":
798
- return ConfigClusterLocator.fromConfig(
799
- clusterLocatorMethod,
800
- authStrategy
801
- );
802
- case "gke":
803
- return GkeClusterLocator.fromConfig(
804
- clusterLocatorMethod,
805
- refreshInterval
806
- );
807
- default:
808
- throw new Error(
809
- `Unsupported kubernetes.clusterLocatorMethods: "${type}"`
810
- );
811
- }
812
- });
813
- return new CombinedClustersSupplier(clusterSuppliers, logger);
814
- };
815
-
816
- const addResourceRoutesToRouter = (router, catalogApi, objectsProvider, auth, httpAuth) => {
817
- const getEntityByReq = async (req) => {
818
- const rawEntityRef = req.body.entityRef;
819
- if (rawEntityRef && typeof rawEntityRef !== "string") {
820
- throw new errors.InputError(`entity query must be a string`);
821
- } else if (!rawEntityRef) {
822
- throw new errors.InputError("entity is a required field");
823
- }
824
- let entityRef = void 0;
825
- try {
826
- entityRef = catalogModel.parseEntityRef(rawEntityRef);
827
- } catch (error) {
828
- throw new errors.InputError(`Invalid entity ref, ${error}`);
829
- }
830
- const { token } = await auth.getPluginRequestToken({
831
- onBehalfOf: await httpAuth.credentials(req),
832
- targetPluginId: "catalog"
833
- });
834
- const entity = await catalogApi.getEntityByRef(entityRef, { token });
835
- if (!entity) {
836
- throw new errors.InputError(
837
- `Entity ref missing, ${catalogModel.stringifyEntityRef(entityRef)}`
838
- );
839
- }
840
- return entity;
841
- };
842
- router.post("/resources/workloads/query", async (req, res) => {
843
- const entity = await getEntityByReq(req);
844
- const response = await objectsProvider.getKubernetesObjectsByEntity(
845
- {
846
- entity,
847
- auth: req.body.auth
848
- },
849
- { credentials: await httpAuth.credentials(req) }
850
- );
851
- res.json(response);
852
- });
853
- router.post("/resources/custom/query", async (req, res) => {
854
- const entity = await getEntityByReq(req);
855
- if (!req.body.customResources) {
856
- throw new errors.InputError("customResources is a required field");
857
- } else if (!Array.isArray(req.body.customResources)) {
858
- throw new errors.InputError("customResources must be an array");
859
- } else if (req.body.customResources.length === 0) {
860
- throw new errors.InputError("at least 1 customResource is required");
861
- }
862
- const response = await objectsProvider.getCustomResourcesByEntity(
863
- {
864
- entity,
865
- customResources: req.body.customResources,
866
- auth: req.body.auth
867
- },
868
- { credentials: await httpAuth.credentials(req) }
869
- );
870
- res.json(response);
871
- });
872
- };
873
-
874
- class CatalogRelationServiceLocator {
875
- clusterSupplier;
876
- constructor(clusterSupplier) {
877
- this.clusterSupplier = clusterSupplier;
878
- }
879
- // As this implementation always returns all clusters serviceId is ignored here
880
- getClustersByEntity(entity, requestContext) {
881
- if (entity.relations && entity.relations.some(
882
- (r) => r.type === "dependsOn" && r.targetRef.includes("resource:")
883
- )) {
884
- return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => {
885
- return {
886
- clusters: clusters.filter(
887
- (c) => this.doesEntityDependOnCluster(entity, c)
888
- )
889
- };
890
- });
891
- }
892
- return Promise.resolve({ clusters: [] });
893
- }
894
- doesEntityDependOnCluster(entity, cluster) {
895
- return entity.relations.some(
896
- (rel) => rel.type === "dependsOn" && rel.targetRef === `resource:${entity.metadata.namespace ?? "default"}/${cluster.name}`
897
- );
898
- }
899
- }
900
-
901
- class MultiTenantServiceLocator {
902
- clusterSupplier;
903
- constructor(clusterSupplier) {
904
- this.clusterSupplier = clusterSupplier;
905
- }
906
- // As this implementation always returns all clusters serviceId is ignored here
907
- getClustersByEntity(_entity, requestContext) {
908
- return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => ({ clusters }));
909
- }
910
- }
911
-
912
- class SingleTenantServiceLocator {
913
- clusterSupplier;
914
- constructor(clusterSupplier) {
915
- this.clusterSupplier = clusterSupplier;
916
- }
917
- // As this implementation always returns all clusters serviceId is ignored here
918
- getClustersByEntity(_entity, requestContext) {
919
- return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => {
920
- if (_entity.metadata?.annotations?.["backstage.io/kubernetes-cluster"]) {
921
- return {
922
- clusters: clusters.filter(
923
- (c) => c.name === _entity.metadata?.annotations?.["backstage.io/kubernetes-cluster"]
924
- )
925
- };
926
- }
927
- return { clusters };
928
- });
929
- }
930
- }
931
-
932
- const DEFAULT_OBJECTS = [
933
- {
934
- group: "",
935
- apiVersion: "v1",
936
- plural: "pods",
937
- objectType: "pods"
938
- },
939
- {
940
- group: "",
941
- apiVersion: "v1",
942
- plural: "services",
943
- objectType: "services"
944
- },
945
- {
946
- group: "",
947
- apiVersion: "v1",
948
- plural: "configmaps",
949
- objectType: "configmaps"
950
- },
951
- {
952
- group: "",
953
- apiVersion: "v1",
954
- plural: "limitranges",
955
- objectType: "limitranges"
956
- },
957
- {
958
- group: "",
959
- apiVersion: "v1",
960
- plural: "resourcequotas",
961
- objectType: "resourcequotas"
962
- },
963
- {
964
- group: "apps",
965
- apiVersion: "v1",
966
- plural: "deployments",
967
- objectType: "deployments"
968
- },
969
- {
970
- group: "apps",
971
- apiVersion: "v1",
972
- plural: "replicasets",
973
- objectType: "replicasets"
974
- },
975
- {
976
- group: "autoscaling",
977
- apiVersion: "v2",
978
- plural: "horizontalpodautoscalers",
979
- objectType: "horizontalpodautoscalers"
980
- },
981
- {
982
- group: "batch",
983
- apiVersion: "v1",
984
- plural: "jobs",
985
- objectType: "jobs"
986
- },
987
- {
988
- group: "batch",
989
- apiVersion: "v1",
990
- plural: "cronjobs",
991
- objectType: "cronjobs"
992
- },
993
- {
994
- group: "networking.k8s.io",
995
- apiVersion: "v1",
996
- plural: "ingresses",
997
- objectType: "ingresses"
998
- },
999
- {
1000
- group: "apps",
1001
- apiVersion: "v1",
1002
- plural: "statefulsets",
1003
- objectType: "statefulsets"
1004
- },
1005
- {
1006
- group: "apps",
1007
- apiVersion: "v1",
1008
- plural: "daemonsets",
1009
- objectType: "daemonsets"
1010
- }
1011
- ];
1012
- const isPodFetchResponse = (fr) => fr.type === "pods";
1013
- const isString = (str) => str !== void 0;
1014
- const numberOrBigIntToNumberOrString = (value) => {
1015
- return typeof value === "bigint" ? value.toString() : value;
1016
- };
1017
- const toClientSafeResource = (current) => {
1018
- return {
1019
- currentUsage: numberOrBigIntToNumberOrString(current.CurrentUsage),
1020
- requestTotal: numberOrBigIntToNumberOrString(current.RequestTotal),
1021
- limitTotal: numberOrBigIntToNumberOrString(current.LimitTotal)
1022
- };
1023
- };
1024
- const toClientSafeContainer = (container) => {
1025
- return {
1026
- container: container.Container,
1027
- cpuUsage: toClientSafeResource(container.CPUUsage),
1028
- memoryUsage: toClientSafeResource(container.MemoryUsage)
1029
- };
1030
- };
1031
- const toClientSafePodMetrics = (podMetrics) => {
1032
- return podMetrics.map((r) => r.resources).flat().map((pd) => {
1033
- return {
1034
- pod: pd.Pod,
1035
- memory: toClientSafeResource(pd.Memory),
1036
- cpu: toClientSafeResource(pd.CPU),
1037
- containers: pd.Containers.map(toClientSafeContainer)
1038
- };
1039
- });
1040
- };
1041
- class KubernetesFanOutHandler {
1042
- logger;
1043
- fetcher;
1044
- serviceLocator;
1045
- customResources;
1046
- objectTypesToFetch;
1047
- authStrategy;
1048
- constructor({
1049
- logger,
1050
- fetcher,
1051
- serviceLocator,
1052
- customResources,
1053
- objectTypesToFetch = DEFAULT_OBJECTS,
1054
- authStrategy
1055
- }) {
1056
- this.logger = logger;
1057
- this.fetcher = fetcher;
1058
- this.serviceLocator = serviceLocator;
1059
- this.customResources = customResources;
1060
- this.objectTypesToFetch = new Set(objectTypesToFetch);
1061
- this.authStrategy = authStrategy;
1062
- }
1063
- async getCustomResourcesByEntity({ entity, auth, customResources }, options) {
1064
- return this.fanOutRequests(
1065
- entity,
1066
- auth,
1067
- { credentials: options.credentials },
1068
- /* @__PURE__ */ new Set(),
1069
- customResources
1070
- );
1071
- }
1072
- async getKubernetesObjectsByEntity({ entity, auth }, options) {
1073
- return this.fanOutRequests(
1074
- entity,
1075
- auth,
1076
- {
1077
- credentials: options.credentials
1078
- },
1079
- this.objectTypesToFetch
1080
- );
1081
- }
1082
- async fanOutRequests(entity, auth, options, objectTypesToFetch, customResources) {
1083
- const entityName = entity.metadata?.annotations?.["backstage.io/kubernetes-id"] || entity.metadata?.name;
1084
- const { clusters } = await this.serviceLocator.getClustersByEntity(entity, {
1085
- objectTypesToFetch,
1086
- customResources: customResources ?? [],
1087
- credentials: options.credentials
1088
- });
1089
- this.logger.info(
1090
- `entity.metadata.name=${entityName} clusterDetails=[${clusters.map((c) => c.name).join(", ")}]`
1091
- );
1092
- const labelSelector = entity.metadata?.annotations?.["backstage.io/kubernetes-label-selector"] || `backstage.io/kubernetes-id=${entityName}`;
1093
- const namespace = entity.metadata?.annotations?.["backstage.io/kubernetes-namespace"];
1094
- return Promise.all(
1095
- clusters.map(async (clusterDetails) => {
1096
- const credential = await this.authStrategy.getCredential(
1097
- clusterDetails,
1098
- auth
1099
- );
1100
- return this.fetcher.fetchObjectsForService({
1101
- serviceId: entityName,
1102
- clusterDetails,
1103
- credential,
1104
- objectTypesToFetch,
1105
- labelSelector,
1106
- customResources: (customResources || clusterDetails.customResources || this.customResources).map((c) => ({
1107
- ...c,
1108
- objectType: "customresources"
1109
- })),
1110
- namespace
1111
- }).then(
1112
- (result) => this.getMetricsForPods(
1113
- clusterDetails,
1114
- credential,
1115
- labelSelector,
1116
- result
1117
- )
1118
- ).catch(
1119
- (e) => e.name === "FetchError" ? Promise.resolve([
1120
- {
1121
- errors: [
1122
- { errorType: "FETCH_ERROR", message: e.message }
1123
- ],
1124
- responses: []
1125
- },
1126
- []
1127
- ]) : Promise.reject(e)
1128
- ).then((r) => this.toClusterObjects(clusterDetails, r));
1129
- })
1130
- ).then(this.toObjectsByEntityResponse);
1131
- }
1132
- toObjectsByEntityResponse(clusterObjects) {
1133
- return {
1134
- items: clusterObjects.filter(
1135
- (item) => item.errors !== void 0 && item.errors.length >= 1 || item.resources !== void 0 && item.resources.length >= 1 && item.resources.some((fr) => fr.resources?.length >= 1)
1136
- )
1137
- };
1138
- }
1139
- toClusterObjects(clusterDetails, [result, metrics]) {
1140
- const objects = {
1141
- cluster: {
1142
- name: clusterDetails.name,
1143
- ...clusterDetails.title && { title: clusterDetails.title }
1144
- },
1145
- podMetrics: toClientSafePodMetrics(metrics),
1146
- resources: result.responses,
1147
- errors: result.errors
1148
- };
1149
- if (clusterDetails.dashboardUrl) {
1150
- objects.cluster.dashboardUrl = clusterDetails.dashboardUrl;
1151
- }
1152
- if (clusterDetails.dashboardApp) {
1153
- objects.cluster.dashboardApp = clusterDetails.dashboardApp;
1154
- }
1155
- if (clusterDetails.dashboardParameters) {
1156
- objects.cluster.dashboardParameters = clusterDetails.dashboardParameters;
1157
- }
1158
- return objects;
1159
- }
1160
- async getMetricsForPods(clusterDetails, credential, labelSelector, result) {
1161
- if (clusterDetails.skipMetricsLookup) {
1162
- return [result, []];
1163
- }
1164
- const namespaces = new Set(
1165
- result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => p.metadata?.namespace).filter(isString)
1166
- );
1167
- if (namespaces.size === 0) {
1168
- return [result, []];
1169
- }
1170
- const podMetrics = await this.fetcher.fetchPodMetricsByNamespaces(
1171
- clusterDetails,
1172
- credential,
1173
- namespaces,
1174
- labelSelector
1175
- );
1176
- result.errors.push(...podMetrics.errors);
1177
- return [result, podMetrics.responses];
1178
- }
1179
- }
1180
-
1181
- const isError = (fr) => fr.hasOwnProperty("errorType");
1182
- function fetchResultsToResponseWrapper(results) {
1183
- const groupBy = lodash__default.default.groupBy(results, (value) => {
1184
- return isError(value) ? "errors" : "responses";
1185
- });
1186
- return {
1187
- errors: groupBy.errors ?? [],
1188
- responses: groupBy.responses ?? []
1189
- };
1190
- }
1191
- const statusCodeToErrorType = (statusCode) => {
1192
- switch (statusCode) {
1193
- case 400:
1194
- return "BAD_REQUEST";
1195
- case 401:
1196
- return "UNAUTHORIZED_ERROR";
1197
- case 404:
1198
- return "NOT_FOUND";
1199
- case 500:
1200
- return "SYSTEM_ERROR";
1201
- default:
1202
- return "UNKNOWN_ERROR";
1203
- }
1204
- };
1205
- class KubernetesClientBasedFetcher {
1206
- logger;
1207
- constructor({ logger }) {
1208
- this.logger = logger;
1209
- }
1210
- fetchObjectsForService(params) {
1211
- const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map(
1212
- ({ objectType, group, apiVersion, plural }) => this.fetchResource(
1213
- params.clusterDetails,
1214
- params.credential,
1215
- group,
1216
- apiVersion,
1217
- plural,
1218
- params.namespace,
1219
- params.labelSelector
1220
- ).then(
1221
- (r) => r.ok ? r.json().then(
1222
- ({ kind, items }) => ({
1223
- type: objectType,
1224
- resources: objectType === "customresources" ? items.map((item) => ({
1225
- ...item,
1226
- kind: kind.replace(/(List)$/, "")
1227
- })) : items
1228
- })
1229
- ) : this.handleUnsuccessfulResponse(params.clusterDetails.name, r)
1230
- )
1231
- );
1232
- return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
1233
- }
1234
- fetchPodMetricsByNamespaces(clusterDetails, credential, namespaces, labelSelector) {
1235
- const fetchResults = Array.from(namespaces).map(async (ns) => {
1236
- const [podMetrics, podList] = await Promise.all([
1237
- this.fetchResource(
1238
- clusterDetails,
1239
- credential,
1240
- "metrics.k8s.io",
1241
- "v1beta1",
1242
- "pods",
1243
- ns,
1244
- labelSelector
1245
- ),
1246
- this.fetchResource(
1247
- clusterDetails,
1248
- credential,
1249
- "",
1250
- "v1",
1251
- "pods",
1252
- ns,
1253
- labelSelector
1254
- )
1255
- ]);
1256
- if (podMetrics.ok && podList.ok) {
1257
- return clientNode.topPods(
1258
- {
1259
- listPodForAllNamespaces: () => podList.json().then((b) => ({ body: b }))
1260
- },
1261
- {
1262
- getPodMetrics: () => podMetrics.json()
1263
- }
1264
- ).then(
1265
- (resources) => ({
1266
- type: "podstatus",
1267
- resources
1268
- })
1269
- );
1270
- } else if (podMetrics.ok) {
1271
- return this.handleUnsuccessfulResponse(clusterDetails.name, podList);
1272
- }
1273
- return this.handleUnsuccessfulResponse(clusterDetails.name, podMetrics);
1274
- });
1275
- return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
1276
- }
1277
- async handleUnsuccessfulResponse(clusterName, res) {
1278
- const resourcePath = new URL(res.url).pathname;
1279
- this.logger.warn(
1280
- `Received ${res.status} status when fetching "${resourcePath}" from cluster "${clusterName}"; body=[${await res.text()}]`
1281
- );
1282
- return {
1283
- errorType: statusCodeToErrorType(res.status),
1284
- statusCode: res.status,
1285
- resourcePath
1286
- };
1287
- }
1288
- fetchResource(clusterDetails, credential, group, apiVersion, plural, namespace, labelSelector) {
1289
- const encode = (s) => encodeURIComponent(s);
1290
- let resourcePath = group ? `/apis/${encode(group)}/${encode(apiVersion)}` : `/api/${encode(apiVersion)}`;
1291
- if (namespace) {
1292
- resourcePath += `/namespaces/${encode(namespace)}`;
1293
- }
1294
- resourcePath += `/${encode(plural)}`;
1295
- let url;
1296
- let requestInit;
1297
- const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
1298
- if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
1299
- [url, requestInit] = this.fetchArgsInCluster(credential);
1300
- } else if (!this.isCredentialMissing(authProvider, credential)) {
1301
- [url, requestInit] = this.fetchArgs(clusterDetails, credential);
1302
- } else {
1303
- return Promise.reject(
1304
- new Error(
1305
- `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`
1306
- )
1307
- );
1308
- }
1309
- if (url.pathname === "/") {
1310
- url.pathname = resourcePath;
1311
- } else {
1312
- url.pathname += resourcePath;
1313
- }
1314
- if (labelSelector) {
1315
- url.search = `labelSelector=${encode(labelSelector)}`;
1316
- }
1317
- return fetch__default.default(url, requestInit);
1318
- }
1319
- isServiceAccountAuthentication(authProvider, clusterDetails) {
1320
- return authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default.default.pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH);
1321
- }
1322
- isCredentialMissing(authProvider, credential) {
1323
- return authProvider !== "localKubectlProxy" && credential.type === "anonymous";
1324
- }
1325
- fetchArgs(clusterDetails, credential) {
1326
- const requestInit = {
1327
- method: "GET",
1328
- headers: {
1329
- Accept: "application/json",
1330
- "Content-Type": "application/json",
1331
- ...credential.type === "bearer token" && {
1332
- Authorization: `Bearer ${credential.token}`
1333
- }
1334
- }
1335
- };
1336
- const url = new URL(clusterDetails.url);
1337
- if (url.protocol === "https:") {
1338
- requestInit.agent = new https__namespace.Agent({
1339
- ca: clientNode.bufferFromFileOrString(
1340
- clusterDetails.caFile,
1341
- clusterDetails.caData
1342
- ) ?? void 0,
1343
- rejectUnauthorized: !clusterDetails.skipTLSVerify,
1344
- ...credential.type === "x509 client certificate" && {
1345
- cert: credential.cert,
1346
- key: credential.key
1347
- }
1348
- });
1349
- }
1350
- return [url, requestInit];
1351
- }
1352
- fetchArgsInCluster(credential) {
1353
- const requestInit = {
1354
- method: "GET",
1355
- headers: {
1356
- Accept: "application/json",
1357
- "Content-Type": "application/json",
1358
- ...credential.type === "bearer token" && {
1359
- Authorization: `Bearer ${credential.token}`
1360
- }
1361
- }
1362
- };
1363
- const kc = new clientNode.KubeConfig();
1364
- kc.loadFromCluster();
1365
- const cluster = kc.getCurrentCluster();
1366
- const url = new URL(cluster.server);
1367
- if (url.protocol === "https:") {
1368
- requestInit.agent = new https__namespace.Agent({
1369
- ca: fs__default.default.readFileSync(cluster.caFile)
1370
- });
1371
- }
1372
- return [url, requestInit];
1373
- }
1374
- }
1375
-
1376
- const HEADER_KUBERNETES_CLUSTER = "Backstage-Kubernetes-Cluster";
1377
- const HEADER_KUBERNETES_AUTH = "Backstage-Kubernetes-Authorization";
1378
- class KubernetesProxy {
1379
- middlewareForClusterName = /* @__PURE__ */ new Map();
1380
- logger;
1381
- clusterSupplier;
1382
- authStrategy;
1383
- httpAuth;
1384
- constructor(options) {
1385
- this.logger = options.logger;
1386
- this.clusterSupplier = options.clusterSupplier;
1387
- this.authStrategy = options.authStrategy;
1388
- const legacy = backendCommon.createLegacyAuthAdapters({
1389
- discovery: options.discovery,
1390
- httpAuth: options.httpAuth
1391
- });
1392
- this.httpAuth = legacy.httpAuth;
1393
- }
1394
- createRequestHandler(options) {
1395
- const { permissionApi } = options;
1396
- return async (req, res, next) => {
1397
- const authorizeResponse = await permissionApi.authorize(
1398
- [{ permission: pluginKubernetesCommon.kubernetesProxyPermission }],
1399
- {
1400
- credentials: await this.httpAuth.credentials(req)
1401
- }
1402
- );
1403
- const auth = authorizeResponse[0];
1404
- if (auth.result === pluginPermissionCommon.AuthorizeResult.DENY) {
1405
- res.status(403).json({ error: new errors.NotAllowedError("Unauthorized") });
1406
- return;
1407
- }
1408
- const middleware = await this.getMiddleware(req);
1409
- if (req.header("connection")?.toLowerCase() === "upgrade" && req.header("upgrade")?.toLowerCase() === "websocket") {
1410
- middleware.upgrade(req, req.socket, void 0);
1411
- } else {
1412
- middleware(req, res, next);
1413
- }
1414
- };
1415
- }
1416
- // We create one middleware per remote cluster and hold on to them, because
1417
- // the secure property isn't possible to decide on a per-request basis with a
1418
- // single middleware instance - and we don't expect it to change over time.
1419
- async getMiddleware(originalReq) {
1420
- const originalCluster = await this.getClusterForRequest(originalReq);
1421
- let middleware = this.middlewareForClusterName.get(originalCluster.name);
1422
- if (!middleware) {
1423
- const logger = this.logger.child({ cluster: originalCluster.name });
1424
- middleware = httpProxyMiddleware.createProxyMiddleware({
1425
- // TODO: Add 'log' to LoggerService
1426
- logProvider: () => backendCommon.loggerToWinstonLogger(logger),
1427
- ws: true,
1428
- secure: !originalCluster.skipTLSVerify,
1429
- changeOrigin: true,
1430
- pathRewrite: async (path, req) => {
1431
- const cluster = await this.getClusterForRequest(req);
1432
- const url = new URL(cluster.url);
1433
- return path.replace(
1434
- new RegExp(`^${originalReq.baseUrl}`),
1435
- url.pathname || ""
1436
- );
1437
- },
1438
- router: async (req) => {
1439
- const cluster = await this.getClusterForRequest(req);
1440
- const url = new URL(cluster.url);
1441
- const target = {
1442
- protocol: url.protocol,
1443
- host: url.hostname,
1444
- port: url.port,
1445
- ca: clientNode.bufferFromFileOrString(
1446
- cluster.caFile,
1447
- cluster.caData
1448
- )?.toString()
1449
- };
1450
- const authHeader = req.headers[HEADER_KUBERNETES_AUTH.toLocaleLowerCase("en-US")];
1451
- if (typeof authHeader === "string") {
1452
- req.headers.authorization = authHeader;
1453
- } else {
1454
- const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
1455
- req.headers
1456
- );
1457
- const credential = await this.getClusterForRequest(req).then((cd) => {
1458
- return this.authStrategy.getCredential(cd, authObj);
1459
- });
1460
- if (credential.type === "bearer token") {
1461
- req.headers.authorization = `Bearer ${credential.token}`;
1462
- } else if (credential.type === "x509 client certificate") {
1463
- target.key = credential.key;
1464
- target.cert = credential.cert;
1465
- }
1466
- }
1467
- return target;
1468
- },
1469
- onError: (error, req, res) => {
1470
- const wrappedError = new errors.ForwardedError(
1471
- `Cluster '${originalCluster.name}' request error`,
1472
- error
1473
- );
1474
- logger.error("Kubernetes proxy error", wrappedError);
1475
- const body = {
1476
- error: errors.serializeError(wrappedError, {
1477
- includeStack: process.env.NODE_ENV === "development"
1478
- }),
1479
- request: { method: req.method, url: req.originalUrl },
1480
- response: { statusCode: 500 }
1481
- };
1482
- res.status(500).json(body);
1483
- }
1484
- });
1485
- this.middlewareForClusterName.set(originalCluster.name, middleware);
1486
- }
1487
- return middleware;
1488
- }
1489
- async getClusterForRequest(req) {
1490
- const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];
1491
- const clusters = await this.clusterSupplier.getClusters({
1492
- credentials: await this.httpAuth.credentials(req)
1493
- });
1494
- if (!clusters || clusters.length <= 0) {
1495
- throw new errors.NotFoundError(`No Clusters configured`);
1496
- }
1497
- const hasClusterNameHeader = typeof clusterName === "string" && clusterName.length > 0;
1498
- let cluster;
1499
- if (hasClusterNameHeader) {
1500
- cluster = clusters.find((c) => c.name === clusterName);
1501
- } else if (clusters.length === 1) {
1502
- cluster = clusters.at(0);
1503
- }
1504
- if (!cluster) {
1505
- throw new errors.NotFoundError(`Cluster '${clusterName}' not found`);
1506
- }
1507
- const authProvider = cluster.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
1508
- if (authProvider === "serviceAccount" && fs__default.default.pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH) && !cluster.authMetadata.serviceAccountToken) {
1509
- const kc = new clientNode.KubeConfig();
1510
- kc.loadFromCluster();
1511
- const clusterFromKubeConfig = kc.getCurrentCluster();
1512
- const url = new URL(clusterFromKubeConfig.server);
1513
- cluster.url = clusterFromKubeConfig.server;
1514
- if (url.protocol === "https:") {
1515
- cluster.caFile = clusterFromKubeConfig.caFile;
1516
- }
1517
- }
1518
- return cluster;
1519
- }
1520
- static authHeadersToKubernetesRequestAuth(originalHeaders) {
1521
- return Object.keys(originalHeaders).filter((header) => header.startsWith("backstage-kubernetes-authorization")).map(
1522
- (header) => KubernetesProxy.headerToDictionary(header, originalHeaders)
1523
- ).filter((headerAsDic) => Object.keys(headerAsDic).length !== 0).reduce(KubernetesProxy.combineHeaders, {});
1524
- }
1525
- static headerToDictionary(header, originalHeaders) {
1526
- const obj = {};
1527
- const headerSplitted = header.split("-");
1528
- if (headerSplitted.length >= 4) {
1529
- const framework = headerSplitted[3].toLowerCase();
1530
- if (headerSplitted.length >= 5) {
1531
- const provider = headerSplitted.slice(4).join("-").toLowerCase();
1532
- obj[framework] = { [provider]: originalHeaders[header] };
1533
- } else {
1534
- obj[framework] = originalHeaders[header];
1535
- }
1536
- }
1537
- return obj;
1538
- }
1539
- static combineHeaders(authObj, header) {
1540
- const framework = Object.keys(header)[0];
1541
- if (authObj[framework]) {
1542
- authObj[framework] = {
1543
- ...authObj[framework],
1544
- ...header[framework]
1545
- };
1546
- } else {
1547
- authObj[framework] = header[framework];
1548
- }
1549
- return authObj;
1550
- }
1551
- }
1552
-
1553
- class KubernetesBuilder {
1554
- constructor(env) {
1555
- this.env = env;
1556
- }
1557
- clusterSupplier;
1558
- defaultClusterRefreshInterval = luxon.Duration.fromObject({
1559
- minutes: 60
1560
- });
1561
- objectsProvider;
1562
- fetcher;
1563
- serviceLocator;
1564
- proxy;
1565
- authStrategyMap;
1566
- static createBuilder(env) {
1567
- return new KubernetesBuilder(env);
1568
- }
1569
- async build() {
1570
- const logger = this.env.logger;
1571
- const config = this.env.config;
1572
- const permissions = this.env.permissions;
1573
- logger.info("Initializing Kubernetes backend");
1574
- if (!config.has("kubernetes")) {
1575
- if (process.env.NODE_ENV !== "development") {
1576
- throw new Error("Kubernetes configuration is missing");
1577
- }
1578
- logger.warn(
1579
- "Failed to initialize kubernetes backend: kubernetes config is missing"
1580
- );
1581
- return {
1582
- router: Router__default.default()
1583
- };
1584
- }
1585
- const { auth, httpAuth } = backendCommon.createLegacyAuthAdapters({
1586
- auth: this.env.auth,
1587
- httpAuth: this.env.httpAuth,
1588
- discovery: this.env.discovery
1589
- });
1590
- const customResources = this.buildCustomResources();
1591
- const fetcher = this.getFetcher();
1592
- const clusterSupplier = this.getClusterSupplier();
1593
- const authStrategyMap = this.getAuthStrategyMap();
1594
- const proxy = this.getProxy(
1595
- logger,
1596
- clusterSupplier,
1597
- this.env.discovery,
1598
- httpAuth
1599
- );
1600
- const serviceLocator = this.getServiceLocator();
1601
- const objectsProvider = this.getObjectsProvider({
1602
- logger,
1603
- fetcher,
1604
- config,
1605
- serviceLocator,
1606
- customResources,
1607
- objectTypesToFetch: this.getObjectTypesToFetch()
1608
- });
1609
- const router = this.buildRouter(
1610
- objectsProvider,
1611
- clusterSupplier,
1612
- this.env.catalogApi,
1613
- proxy,
1614
- permissions,
1615
- auth,
1616
- httpAuth
1617
- );
1618
- return {
1619
- clusterSupplier,
1620
- customResources,
1621
- fetcher,
1622
- proxy,
1623
- objectsProvider,
1624
- router,
1625
- serviceLocator,
1626
- authStrategyMap
1627
- };
1628
- }
1629
- setClusterSupplier(clusterSupplier) {
1630
- this.clusterSupplier = clusterSupplier;
1631
- return this;
1632
- }
1633
- setDefaultClusterRefreshInterval(refreshInterval) {
1634
- this.defaultClusterRefreshInterval = refreshInterval;
1635
- return this;
1636
- }
1637
- setObjectsProvider(objectsProvider) {
1638
- this.objectsProvider = objectsProvider;
1639
- return this;
1640
- }
1641
- setFetcher(fetcher) {
1642
- this.fetcher = fetcher;
1643
- return this;
1644
- }
1645
- setServiceLocator(serviceLocator) {
1646
- this.serviceLocator = serviceLocator;
1647
- return this;
1648
- }
1649
- setProxy(proxy) {
1650
- this.proxy = proxy;
1651
- return this;
1652
- }
1653
- setAuthStrategyMap(authStrategyMap) {
1654
- this.authStrategyMap = authStrategyMap;
1655
- }
1656
- addAuthStrategy(key, strategy) {
1657
- if (key.includes("-")) {
1658
- throw new Error("Strategy name can not include dashes");
1659
- }
1660
- this.getAuthStrategyMap()[key] = strategy;
1661
- return this;
1662
- }
1663
- buildCustomResources() {
1664
- const customResources = (this.env.config.getOptionalConfigArray("kubernetes.customResources") ?? []).map(
1665
- (c) => ({
1666
- group: c.getString("group"),
1667
- apiVersion: c.getString("apiVersion"),
1668
- plural: c.getString("plural"),
1669
- objectType: "customresources"
1670
- })
1671
- );
1672
- this.env.logger.info(
1673
- `action=LoadingCustomResources numOfCustomResources=${customResources.length}`
1674
- );
1675
- return customResources;
1676
- }
1677
- buildClusterSupplier(refreshInterval) {
1678
- const config = this.env.config;
1679
- const { auth } = backendCommon.createLegacyAuthAdapters(this.env);
1680
- this.clusterSupplier = getCombinedClusterSupplier(
1681
- config,
1682
- this.env.catalogApi,
1683
- new DispatchStrategy({ authStrategyMap: this.getAuthStrategyMap() }),
1684
- this.env.logger,
1685
- refreshInterval,
1686
- auth
1687
- );
1688
- return this.clusterSupplier;
1689
- }
1690
- buildObjectsProvider(options) {
1691
- const authStrategyMap = this.getAuthStrategyMap();
1692
- this.objectsProvider = new KubernetesFanOutHandler({
1693
- ...options,
1694
- authStrategy: new DispatchStrategy({
1695
- authStrategyMap
1696
- })
1697
- });
1698
- return this.objectsProvider;
1699
- }
1700
- buildFetcher() {
1701
- this.fetcher = new KubernetesClientBasedFetcher({
1702
- logger: this.env.logger
1703
- });
1704
- return this.fetcher;
1705
- }
1706
- buildServiceLocator(method, clusterSupplier) {
1707
- switch (method) {
1708
- case "multiTenant":
1709
- this.serviceLocator = this.buildMultiTenantServiceLocator(clusterSupplier);
1710
- break;
1711
- case "singleTenant":
1712
- this.serviceLocator = this.buildSingleTenantServiceLocator(clusterSupplier);
1713
- break;
1714
- case "catalogRelation":
1715
- this.serviceLocator = this.buildCatalogRelationServiceLocator(clusterSupplier);
1716
- break;
1717
- case "http":
1718
- this.serviceLocator = this.buildHttpServiceLocator(clusterSupplier);
1719
- break;
1720
- default:
1721
- throw new Error(
1722
- `Unsupported kubernetes.serviceLocatorMethod "${method}"`
1723
- );
1724
- }
1725
- return this.serviceLocator;
1726
- }
1727
- buildMultiTenantServiceLocator(clusterSupplier) {
1728
- return new MultiTenantServiceLocator(clusterSupplier);
1729
- }
1730
- buildSingleTenantServiceLocator(clusterSupplier) {
1731
- return new SingleTenantServiceLocator(clusterSupplier);
1732
- }
1733
- buildCatalogRelationServiceLocator(clusterSupplier) {
1734
- return new CatalogRelationServiceLocator(clusterSupplier);
1735
- }
1736
- buildHttpServiceLocator(_clusterSupplier) {
1737
- throw new Error("not implemented");
1738
- }
1739
- buildProxy(logger, clusterSupplier, discovery, httpAuth) {
1740
- const authStrategyMap = this.getAuthStrategyMap();
1741
- const authStrategy = new DispatchStrategy({
1742
- authStrategyMap
1743
- });
1744
- this.proxy = new KubernetesProxy({
1745
- logger,
1746
- clusterSupplier,
1747
- authStrategy,
1748
- discovery,
1749
- httpAuth
1750
- });
1751
- return this.proxy;
1752
- }
1753
- buildRouter(objectsProvider, clusterSupplier, catalogApi, proxy, permissionApi, authService, httpAuth) {
1754
- const logger = this.env.logger;
1755
- const router = Router__default.default();
1756
- router.use("/proxy", proxy.createRequestHandler({ permissionApi }));
1757
- router.use(express__default.default.json());
1758
- router.use(
1759
- pluginPermissionNode.createPermissionIntegrationRouter({
1760
- permissions: pluginKubernetesCommon.kubernetesPermissions
1761
- })
1762
- );
1763
- router.post("/services/:serviceId", async (req, res) => {
1764
- const serviceId = req.params.serviceId;
1765
- const requestBody = req.body;
1766
- try {
1767
- const response = await objectsProvider.getKubernetesObjectsByEntity(
1768
- {
1769
- entity: requestBody.entity,
1770
- auth: requestBody.auth || {}
1771
- },
1772
- { credentials: await httpAuth.credentials(req) }
1773
- );
1774
- res.json(response);
1775
- } catch (e) {
1776
- logger.error(
1777
- `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`
1778
- );
1779
- res.status(500).json({ error: e.message });
1780
- }
1781
- });
1782
- router.get("/clusters", async (req, res) => {
1783
- const credentials = await httpAuth.credentials(req);
1784
- const clusterDetails = await this.fetchClusterDetails(clusterSupplier, {
1785
- credentials
1786
- });
1787
- res.json({
1788
- items: clusterDetails.map((cd) => {
1789
- const oidcTokenProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
1790
- const authProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
1791
- const strategy = this.getAuthStrategyMap()[authProvider];
1792
- let auth = {};
1793
- if (strategy) {
1794
- auth = strategy.presentAuthMetadata(cd.authMetadata);
1795
- }
1796
- return {
1797
- name: cd.name,
1798
- title: cd.title,
1799
- dashboardUrl: cd.dashboardUrl,
1800
- authProvider,
1801
- ...oidcTokenProvider && { oidcTokenProvider },
1802
- ...auth && Object.keys(auth).length !== 0 && { auth }
1803
- };
1804
- })
1805
- });
1806
- });
1807
- addResourceRoutesToRouter(
1808
- router,
1809
- catalogApi,
1810
- objectsProvider,
1811
- authService,
1812
- httpAuth
1813
- );
1814
- return router;
1815
- }
1816
- buildAuthStrategyMap() {
1817
- this.authStrategyMap = {
1818
- aks: new AksStrategy(),
1819
- aws: new AwsIamStrategy({ config: this.env.config }),
1820
- azure: new AzureIdentityStrategy(this.env.logger),
1821
- google: new GoogleStrategy(),
1822
- googleServiceAccount: new GoogleServiceAccountStrategy(),
1823
- localKubectlProxy: new AnonymousStrategy(),
1824
- oidc: new OidcStrategy(),
1825
- serviceAccount: new ServiceAccountStrategy()
1826
- };
1827
- return this.authStrategyMap;
1828
- }
1829
- async fetchClusterDetails(clusterSupplier, options) {
1830
- const clusterDetails = await clusterSupplier.getClusters(options);
1831
- this.env.logger.info(
1832
- `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`
1833
- );
1834
- return clusterDetails;
1835
- }
1836
- getServiceLocatorMethod() {
1837
- return this.env.config.getString(
1838
- "kubernetes.serviceLocatorMethod.type"
1839
- );
1840
- }
1841
- getFetcher() {
1842
- return this.fetcher ?? this.buildFetcher();
1843
- }
1844
- getClusterSupplier() {
1845
- return this.clusterSupplier ?? this.buildClusterSupplier(this.defaultClusterRefreshInterval);
1846
- }
1847
- getServiceLocator() {
1848
- return this.serviceLocator ?? this.buildServiceLocator(
1849
- this.getServiceLocatorMethod(),
1850
- this.getClusterSupplier()
1851
- );
1852
- }
1853
- getObjectsProvider(options) {
1854
- return this.objectsProvider ?? this.buildObjectsProvider(options);
1855
- }
1856
- getObjectTypesToFetch() {
1857
- const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(
1858
- "kubernetes.objectTypes"
1859
- );
1860
- const apiVersionOverrides = this.env.config.getOptionalConfig(
1861
- "kubernetes.apiVersionOverrides"
1862
- );
1863
- let objectTypesToFetch;
1864
- if (objectTypesToFetchStrings) {
1865
- objectTypesToFetch = DEFAULT_OBJECTS.filter(
1866
- (obj) => objectTypesToFetchStrings.includes(obj.objectType)
1867
- );
1868
- }
1869
- if (apiVersionOverrides) {
1870
- objectTypesToFetch = objectTypesToFetch ?? DEFAULT_OBJECTS;
1871
- for (const obj of objectTypesToFetch) {
1872
- if (apiVersionOverrides.has(obj.objectType)) {
1873
- obj.apiVersion = apiVersionOverrides.getString(obj.objectType);
1874
- }
1875
- }
1876
- }
1877
- return objectTypesToFetch;
1878
- }
1879
- getProxy(logger, clusterSupplier, discovery, httpAuth) {
1880
- return this.proxy ?? this.buildProxy(logger, clusterSupplier, discovery, httpAuth);
1881
- }
1882
- getAuthStrategyMap() {
1883
- return this.authStrategyMap ?? this.buildAuthStrategyMap();
1884
- }
1885
- }
1886
-
1887
- async function createRouter(options) {
1888
- const { router } = await KubernetesBuilder.createBuilder(options).setClusterSupplier(options.clusterSupplier).build();
1889
- return router;
1890
- }
1891
-
1892
- exports.AksStrategy = AksStrategy;
1893
- exports.AnonymousStrategy = AnonymousStrategy;
1894
- exports.AwsIamStrategy = AwsIamStrategy;
1895
- exports.AzureIdentityStrategy = AzureIdentityStrategy;
1896
- exports.DEFAULT_OBJECTS = DEFAULT_OBJECTS;
1897
- exports.DispatchStrategy = DispatchStrategy;
1898
- exports.GoogleServiceAccountStrategy = GoogleServiceAccountStrategy;
1899
- exports.GoogleStrategy = GoogleStrategy;
1900
- exports.HEADER_KUBERNETES_AUTH = HEADER_KUBERNETES_AUTH;
1901
- exports.HEADER_KUBERNETES_CLUSTER = HEADER_KUBERNETES_CLUSTER;
1902
- exports.KubernetesBuilder = KubernetesBuilder;
1903
- exports.KubernetesProxy = KubernetesProxy;
1904
- exports.OidcStrategy = OidcStrategy;
1905
- exports.ServiceAccountStrategy = ServiceAccountStrategy;
1906
- exports.createRouter = createRouter;
3
+ var AksStrategy = require('./auth/AksStrategy.cjs.js');
4
+ var AnonymousStrategy = require('./auth/AnonymousStrategy.cjs.js');
5
+ var AwsIamStrategy = require('./auth/AwsIamStrategy.cjs.js');
6
+ var AzureIdentityStrategy = require('./auth/AzureIdentityStrategy.cjs.js');
7
+ var GoogleStrategy = require('./auth/GoogleStrategy.cjs.js');
8
+ var GoogleServiceAccountStrategy = require('./auth/GoogleServiceAccountStrategy.cjs.js');
9
+ var DispatchStrategy = require('./auth/DispatchStrategy.cjs.js');
10
+ var ServiceAccountStrategy = require('./auth/ServiceAccountStrategy.cjs.js');
11
+ var OidcStrategy = require('./auth/OidcStrategy.cjs.js');
12
+ var KubernetesBuilder = require('./service/KubernetesBuilder.cjs.js');
13
+ var KubernetesFanOutHandler = require('./service/KubernetesFanOutHandler.cjs.js');
14
+ var KubernetesProxy = require('./service/KubernetesProxy.cjs.js');
15
+ var router = require('./service/router.cjs.js');
16
+
17
+
18
+
19
+ exports.AksStrategy = AksStrategy.AksStrategy;
20
+ exports.AnonymousStrategy = AnonymousStrategy.AnonymousStrategy;
21
+ exports.AwsIamStrategy = AwsIamStrategy.AwsIamStrategy;
22
+ exports.AzureIdentityStrategy = AzureIdentityStrategy.AzureIdentityStrategy;
23
+ exports.GoogleStrategy = GoogleStrategy.GoogleStrategy;
24
+ exports.GoogleServiceAccountStrategy = GoogleServiceAccountStrategy.GoogleServiceAccountStrategy;
25
+ exports.DispatchStrategy = DispatchStrategy.DispatchStrategy;
26
+ exports.ServiceAccountStrategy = ServiceAccountStrategy.ServiceAccountStrategy;
27
+ exports.OidcStrategy = OidcStrategy.OidcStrategy;
28
+ exports.KubernetesBuilder = KubernetesBuilder.KubernetesBuilder;
29
+ exports.DEFAULT_OBJECTS = KubernetesFanOutHandler.DEFAULT_OBJECTS;
30
+ exports.HEADER_KUBERNETES_AUTH = KubernetesProxy.HEADER_KUBERNETES_AUTH;
31
+ exports.HEADER_KUBERNETES_CLUSTER = KubernetesProxy.HEADER_KUBERNETES_CLUSTER;
32
+ exports.KubernetesProxy = KubernetesProxy.KubernetesProxy;
33
+ exports.createRouter = router.createRouter;
1907
34
  //# sourceMappingURL=index.cjs.js.map