@backstage/plugin-kubernetes-backend 0.3.19 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @backstage/plugin-kubernetes-backend
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 7ac0bd2c66: implement dashboard link formatter for GKE
8
+ - Updated dependencies
9
+ - @backstage/backend-common@0.10.2
10
+ - @backstage/plugin-kubernetes-common@0.2.1
11
+
12
+ ## 0.4.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies
17
+ - @backstage/backend-common@0.10.0
18
+
19
+ ## 0.4.0
20
+
21
+ ### Minor Changes
22
+
23
+ - c010632f88: Add pod metrics lookup and display in pod table.
24
+
25
+ ## Backwards incompatible changes
26
+
27
+ If your Kubernetes distribution does not have the [metrics server](https://github.com/kubernetes-sigs/metrics-server) installed,
28
+ you will need to set the `skipMetricsLookup` config flag to `false`.
29
+
30
+ See the [configuration docs](https://backstage.io/docs/features/kubernetes/configuration) for more details.
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies
35
+ - @backstage/plugin-kubernetes-common@0.2.0
36
+ - @backstage/backend-common@0.9.13
37
+
38
+ ## 0.3.20
39
+
40
+ ### Patch Changes
41
+
42
+ - 65ddccb5e8: Added apiVersionOverrides config to allow for specifying api versions to use for kubernetes objects
43
+ - f6087fc8f8: Query CronJobs from Kubernetes with apiGroup BatchV1beta1
44
+ - Updated dependencies
45
+ - @backstage/backend-common@0.9.12
46
+ - @backstage/plugin-kubernetes-common@0.1.7
47
+
3
48
  ## 0.3.19
4
49
 
5
50
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -22,14 +22,12 @@ function _interopNamespace(e) {
22
22
  var d = Object.getOwnPropertyDescriptor(e, k);
23
23
  Object.defineProperty(n, k, d.get ? d : {
24
24
  enumerable: true,
25
- get: function () {
26
- return e[k];
27
- }
25
+ get: function () { return e[k]; }
28
26
  });
29
27
  }
30
28
  });
31
29
  }
32
- n['default'] = e;
30
+ n["default"] = e;
33
31
  return Object.freeze(n);
34
32
  }
35
33
 
@@ -45,13 +43,14 @@ class ConfigClusterLocator {
45
43
  }
46
44
  static fromConfig(config) {
47
45
  return new ConfigClusterLocator(config.getConfigArray("clusters").map((c) => {
48
- var _a;
46
+ var _a, _b;
49
47
  const authProvider = c.getString("authProvider");
50
48
  const clusterDetails = {
51
49
  name: c.getString("name"),
52
50
  url: c.getString("url"),
53
51
  serviceAccountToken: c.getOptionalString("serviceAccountToken"),
54
52
  skipTLSVerify: (_a = c.getOptionalBoolean("skipTLSVerify")) != null ? _a : false,
53
+ skipMetricsLookup: (_b = c.getOptionalBoolean("skipMetricsLookup")) != null ? _b : false,
55
54
  caData: c.getOptionalString("caData"),
56
55
  authProvider
57
56
  };
@@ -63,6 +62,9 @@ class ConfigClusterLocator {
63
62
  if (dashboardApp) {
64
63
  clusterDetails.dashboardApp = dashboardApp;
65
64
  }
65
+ if (c.has("dashboardParameters")) {
66
+ clusterDetails.dashboardParameters = c.get("dashboardParameters");
67
+ }
66
68
  switch (authProvider) {
67
69
  case "google": {
68
70
  return clusterDetails;
@@ -70,7 +72,7 @@ class ConfigClusterLocator {
70
72
  case "aws": {
71
73
  const assumeRole = c.getOptionalString("assumeRole");
72
74
  const externalId = c.getOptionalString("externalId");
73
- return {assumeRole, externalId, ...clusterDetails};
75
+ return { assumeRole, externalId, ...clusterDetails };
74
76
  }
75
77
  case "serviceAccount": {
76
78
  return clusterDetails;
@@ -92,11 +94,13 @@ class GkeClusterLocator {
92
94
  this.client = client;
93
95
  }
94
96
  static fromConfigWithClient(config, client) {
95
- var _a, _b;
97
+ var _a, _b, _c, _d;
96
98
  const options = {
97
99
  projectId: config.getString("projectId"),
98
100
  region: (_a = config.getOptionalString("region")) != null ? _a : "-",
99
- skipTLSVerify: (_b = config.getOptionalBoolean("skipTLSVerify")) != null ? _b : false
101
+ skipTLSVerify: (_b = config.getOptionalBoolean("skipTLSVerify")) != null ? _b : false,
102
+ skipMetricsLookup: (_c = config.getOptionalBoolean("skipMetricsLookup")) != null ? _c : false,
103
+ exposeDashboard: (_d = config.getOptionalBoolean("exposeDashboard")) != null ? _d : false
100
104
  };
101
105
  return new GkeClusterLocator(options, client);
102
106
  }
@@ -105,7 +109,13 @@ class GkeClusterLocator {
105
109
  }
106
110
  async getClusters() {
107
111
  var _a;
108
- const {projectId, region, skipTLSVerify} = this.options;
112
+ const {
113
+ projectId,
114
+ region,
115
+ skipTLSVerify,
116
+ skipMetricsLookup,
117
+ exposeDashboard
118
+ } = this.options;
109
119
  const request = {
110
120
  parent: `projects/${projectId}/locations/${region}`
111
121
  };
@@ -117,7 +127,16 @@ class GkeClusterLocator {
117
127
  name: (_a2 = r.name) != null ? _a2 : "unknown",
118
128
  url: `https://${(_b = r.endpoint) != null ? _b : ""}`,
119
129
  authProvider: "google",
120
- skipTLSVerify
130
+ skipTLSVerify,
131
+ skipMetricsLookup,
132
+ ...exposeDashboard ? {
133
+ dashboardApp: "gke",
134
+ dashboardParameters: {
135
+ projectId,
136
+ region,
137
+ clusterName: r.name
138
+ }
139
+ } : {}
121
140
  };
122
141
  });
123
142
  } catch (e) {
@@ -183,21 +202,9 @@ class KubernetesClientProvider {
183
202
  const kc = this.getKubeConfig(clusterDetails);
184
203
  return kc.makeApiClient(clientNode.CoreV1Api);
185
204
  }
186
- getAppsClientByClusterDetails(clusterDetails) {
187
- const kc = this.getKubeConfig(clusterDetails);
188
- return kc.makeApiClient(clientNode.AppsV1Api);
189
- }
190
- getAutoscalingClientByClusterDetails(clusterDetails) {
191
- const kc = this.getKubeConfig(clusterDetails);
192
- return kc.makeApiClient(clientNode.AutoscalingV1Api);
193
- }
194
- getBatchClientByClusterDetails(clusterDetails) {
205
+ getMetricsClient(clusterDetails) {
195
206
  const kc = this.getKubeConfig(clusterDetails);
196
- return kc.makeApiClient(clientNode.BatchV1Api);
197
- }
198
- getNetworkingBeta1Client(clusterDetails) {
199
- const kc = this.getKubeConfig(clusterDetails);
200
- return kc.makeApiClient(clientNode.NetworkingV1beta1Api);
207
+ return new clientNode.Metrics(kc);
201
208
  }
202
209
  getCustomObjectsClient(clusterDetails) {
203
210
  const kc = this.getKubeConfig(clusterDetails);
@@ -235,11 +242,11 @@ class AwsIamKubernetesAuthTranslator {
235
242
  constructor() {
236
243
  this.awsGetCredentials = async () => {
237
244
  return new Promise((resolve, reject) => {
238
- AWS__default['default'].config.getCredentials((err) => {
245
+ AWS__default["default"].config.getCredentials((err) => {
239
246
  if (err) {
240
247
  return reject(err);
241
248
  }
242
- return resolve(AWS__default['default'].config.credentials);
249
+ return resolve(AWS__default["default"].config.credentials);
243
250
  });
244
251
  });
245
252
  };
@@ -268,7 +275,7 @@ class AwsIamKubernetesAuthTranslator {
268
275
  };
269
276
  if (externalId)
270
277
  params.ExternalId = externalId;
271
- const assumedRole = await new AWS__default['default'].STS().assumeRole(params).promise();
278
+ const assumedRole = await new AWS__default["default"].STS().assumeRole(params).promise();
272
279
  if (!assumedRole.Credentials) {
273
280
  throw new Error(`No credentials returned for role ${assumeRole}`);
274
281
  }
@@ -385,6 +392,35 @@ const DEFAULT_OBJECTS = [
385
392
  objectType: "ingresses"
386
393
  }
387
394
  ];
395
+ const isPodFetchResponse = (fr) => fr.type === "pods";
396
+ const isString = (str) => str !== void 0;
397
+ const numberOrBigIntToNumberOrString = (value) => {
398
+ return typeof value === "bigint" ? value.toString() : value;
399
+ };
400
+ const toClientSafeResource = (current) => {
401
+ return {
402
+ currentUsage: numberOrBigIntToNumberOrString(current.CurrentUsage),
403
+ requestTotal: numberOrBigIntToNumberOrString(current.RequestTotal),
404
+ limitTotal: numberOrBigIntToNumberOrString(current.LimitTotal)
405
+ };
406
+ };
407
+ const toClientSafeContainer = (container) => {
408
+ return {
409
+ container: container.Container,
410
+ cpuUsage: toClientSafeResource(container.CPUUsage),
411
+ memoryUsage: toClientSafeResource(container.MemoryUsage)
412
+ };
413
+ };
414
+ const toClientSafePodMetrics = (podMetrics) => {
415
+ return podMetrics.flat().map((pd) => {
416
+ return {
417
+ pod: pd.Pod,
418
+ memory: toClientSafeResource(pd.Memory),
419
+ cpu: toClientSafeResource(pd.CPU),
420
+ containers: pd.Containers.map(toClientSafeContainer)
421
+ };
422
+ });
423
+ };
388
424
  class KubernetesFanOutHandler {
389
425
  constructor({
390
426
  logger,
@@ -418,10 +454,27 @@ class KubernetesFanOutHandler {
418
454
  labelSelector,
419
455
  customResources: this.customResources
420
456
  }).then((result) => {
457
+ if (clusterDetailsItem.skipMetricsLookup) {
458
+ return Promise.all([
459
+ Promise.resolve(result),
460
+ Promise.resolve([])
461
+ ]);
462
+ }
463
+ const namespaces = new Set(result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => {
464
+ var _a2;
465
+ return (_a2 = p.metadata) == null ? void 0 : _a2.namespace;
466
+ }).filter(isString));
467
+ const podMetrics = Array.from(namespaces).map((ns) => this.fetcher.fetchPodMetricsByNamespace(clusterDetailsItem, ns));
468
+ return Promise.all([
469
+ Promise.resolve(result),
470
+ Promise.all(podMetrics)
471
+ ]);
472
+ }).then(([result, metrics]) => {
421
473
  const objects = {
422
474
  cluster: {
423
475
  name: clusterDetailsItem.name
424
476
  },
477
+ podMetrics: toClientSafePodMetrics(metrics),
425
478
  resources: result.responses,
426
479
  errors: result.errors
427
480
  };
@@ -431,6 +484,9 @@ class KubernetesFanOutHandler {
431
484
  if (clusterDetailsItem.dashboardApp) {
432
485
  objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp;
433
486
  }
487
+ if (clusterDetailsItem.dashboardParameters) {
488
+ objects.cluster.dashboardParameters = clusterDetailsItem.dashboardParameters;
489
+ }
434
490
  return objects;
435
491
  });
436
492
  })).then((r) => ({
@@ -442,7 +498,7 @@ class KubernetesFanOutHandler {
442
498
  const isError = (fr) => fr.hasOwnProperty("errorType");
443
499
  function fetchResultsToResponseWrapper(results) {
444
500
  var _a, _b;
445
- const groupBy = lodash__default['default'].groupBy(results, (value) => {
501
+ const groupBy = lodash__default["default"].groupBy(results, (value) => {
446
502
  return isError(value) ? "errors" : "responses";
447
503
  });
448
504
  return {
@@ -476,9 +532,14 @@ class KubernetesClientBasedFetcher {
476
532
  });
477
533
  return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
478
534
  }
535
+ fetchPodMetricsByNamespace(clusterDetails, namespace) {
536
+ const metricsClient = this.kubernetesClientProvider.getMetricsClient(clusterDetails);
537
+ const coreApi = this.kubernetesClientProvider.getCoreClientByClusterDetails(clusterDetails);
538
+ return clientNode.topPods(coreApi, metricsClient, namespace);
539
+ }
479
540
  captureKubernetesErrorsRethrowOthers(e) {
480
541
  if (e.response && e.response.statusCode) {
481
- this.logger.info(`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname}`);
542
+ this.logger.warn(`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname} body=[${JSON.stringify(e.response.body)}]`);
482
543
  return {
483
544
  errorType: statusCodeToErrorType(e.response.statusCode),
484
545
  statusCode: e.response.statusCode,
@@ -492,7 +553,7 @@ class KubernetesClientBasedFetcher {
492
553
  customObjects.addInterceptor((requestOptions) => {
493
554
  requestOptions.uri = requestOptions.uri.replace("/apis//v1/", "/api/v1/");
494
555
  });
495
- return customObjects.listClusterCustomObject(resource.group, resource.apiVersion, resource.plural, "", "", "", labelSelector).then((r) => {
556
+ return customObjects.listClusterCustomObject(resource.group, resource.apiVersion, resource.plural, "", false, "", "", labelSelector).then((r) => {
496
557
  return {
497
558
  type: objectType,
498
559
  resources: r.body.items
@@ -597,8 +658,8 @@ class KubernetesBuilder {
597
658
  }
598
659
  buildRouter(objectsProvider, clusterDetails) {
599
660
  const logger = this.env.logger;
600
- const router = Router__default['default']();
601
- router.use(express__default['default'].json());
661
+ const router = Router__default["default"]();
662
+ router.use(express__default["default"].json());
602
663
  router.post("/services/:serviceId", async (req, res) => {
603
664
  const serviceId = req.params.serviceId;
604
665
  const requestBody = req.body;
@@ -607,7 +668,7 @@ class KubernetesBuilder {
607
668
  res.json(response);
608
669
  } catch (e) {
609
670
  logger.error(`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`);
610
- res.status(500).json({error: e.message});
671
+ res.status(500).json({ error: e.message });
611
672
  }
612
673
  });
613
674
  router.get("/clusters", async (_, res) => {
@@ -631,16 +692,25 @@ class KubernetesBuilder {
631
692
  }
632
693
  getObjectTypesToFetch() {
633
694
  const objectTypesToFetchStrings = this.env.config.getOptionalStringArray("kubernetes.objectTypes");
695
+ const apiVersionOverrides = this.env.config.getOptionalConfig("kubernetes.apiVersionOverrides");
634
696
  let objectTypesToFetch;
635
697
  if (objectTypesToFetchStrings) {
636
698
  objectTypesToFetch = DEFAULT_OBJECTS.filter((obj) => objectTypesToFetchStrings.includes(obj.objectType));
637
699
  }
700
+ if (apiVersionOverrides) {
701
+ objectTypesToFetch = objectTypesToFetch != null ? objectTypesToFetch : DEFAULT_OBJECTS;
702
+ for (const obj of objectTypesToFetch) {
703
+ if (apiVersionOverrides.has(obj.objectType)) {
704
+ obj.apiVersion = apiVersionOverrides.getString(obj.objectType);
705
+ }
706
+ }
707
+ }
638
708
  return objectTypesToFetch;
639
709
  }
640
710
  }
641
711
 
642
712
  async function createRouter(options) {
643
- const {router} = await KubernetesBuilder.createBuilder(options).setClusterSupplier(options.clusterSupplier).build();
713
+ const { router } = await KubernetesBuilder.createBuilder(options).setClusterSupplier(options.clusterSupplier).build();
644
714
  return router;
645
715
  }
646
716
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/cluster-locator/ConfigClusterLocator.ts","../src/cluster-locator/GkeClusterLocator.ts","../src/cluster-locator/index.ts","../src/service-locator/MultiTenantServiceLocator.ts","../src/service/KubernetesClientProvider.ts","../src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts","../src/service/KubernetesFanOutHandler.ts","../src/service/KubernetesFetcher.ts","../src/service/KubernetesBuilder.ts","../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\n\nexport class ConfigClusterLocator implements KubernetesClustersSupplier {\n private readonly clusterDetails: ClusterDetails[];\n\n constructor(clusterDetails: ClusterDetails[]) {\n this.clusterDetails = clusterDetails;\n }\n\n static fromConfig(config: Config): ConfigClusterLocator {\n // TODO: Add validation that authProvider is required and serviceAccountToken\n // is required if authProvider is serviceAccount\n return new ConfigClusterLocator(\n config.getConfigArray('clusters').map(c => {\n const authProvider = c.getString('authProvider');\n const clusterDetails: ClusterDetails = {\n name: c.getString('name'),\n url: c.getString('url'),\n serviceAccountToken: c.getOptionalString('serviceAccountToken'),\n skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false,\n caData: c.getOptionalString('caData'),\n authProvider: authProvider,\n };\n const dashboardUrl = c.getOptionalString('dashboardUrl');\n if (dashboardUrl) {\n clusterDetails.dashboardUrl = dashboardUrl;\n }\n const dashboardApp = c.getOptionalString('dashboardApp');\n if (dashboardApp) {\n clusterDetails.dashboardApp = dashboardApp;\n }\n\n switch (authProvider) {\n case 'google': {\n return clusterDetails;\n }\n case 'aws': {\n const assumeRole = c.getOptionalString('assumeRole');\n const externalId = c.getOptionalString('externalId');\n\n return { assumeRole, externalId, ...clusterDetails };\n }\n case 'serviceAccount': {\n return clusterDetails;\n }\n default: {\n throw new Error(\n `authProvider \"${authProvider}\" has no config associated with it`,\n );\n }\n }\n }),\n );\n }\n\n async getClusters(): Promise<ClusterDetails[]> {\n return this.clusterDetails;\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport * as container from '@google-cloud/container';\nimport { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types';\n\ntype GkeClusterLocatorOptions = {\n projectId: string;\n region?: string;\n skipTLSVerify?: boolean;\n};\n\nexport class GkeClusterLocator implements KubernetesClustersSupplier {\n constructor(\n private readonly options: GkeClusterLocatorOptions,\n private readonly client: container.v1.ClusterManagerClient,\n ) {}\n\n static fromConfigWithClient(\n config: Config,\n client: container.v1.ClusterManagerClient,\n ): GkeClusterLocator {\n const options = {\n projectId: config.getString('projectId'),\n region: config.getOptionalString('region') ?? '-',\n skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false,\n };\n return new GkeClusterLocator(options, client);\n }\n\n static fromConfig(config: Config): GkeClusterLocator {\n return GkeClusterLocator.fromConfigWithClient(\n config,\n new container.v1.ClusterManagerClient(),\n );\n }\n\n // TODO pass caData into the object\n async getClusters(): Promise<GKEClusterDetails[]> {\n const { projectId, region, skipTLSVerify } = this.options;\n const request = {\n parent: `projects/${projectId}/locations/${region}`,\n };\n\n try {\n const [response] = await this.client.listClusters(request);\n return (response.clusters ?? []).map(r => ({\n // TODO filter out clusters which don't have name or endpoint\n name: r.name ?? 'unknown',\n url: `https://${r.endpoint ?? ''}`,\n authProvider: 'google',\n skipTLSVerify,\n }));\n } catch (e) {\n throw new ForwardedError(\n `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,\n e,\n );\n }\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ClusterDetails } from '../types/types';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\n\nexport const getCombinedClusterDetails = async (\n rootConfig: Config,\n): Promise<ClusterDetails[]> => {\n return Promise.all(\n rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n ).getClusters();\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n ).getClusters();\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n }),\n )\n .then(res => {\n return res.flat();\n })\n .catch(e => {\n throw e;\n });\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ClusterDetails, KubernetesServiceLocator } from '../types/types';\n\n// This locator assumes that every service is located on every cluster\n// Therefore it will always return all clusters provided\nexport class MultiTenantServiceLocator implements KubernetesServiceLocator {\n private readonly clusterDetails: ClusterDetails[];\n\n constructor(clusterDetails: ClusterDetails[]) {\n this.clusterDetails = clusterDetails;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async getClustersByServiceId(_serviceId: string): Promise<ClusterDetails[]> {\n return this.clusterDetails;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppsV1Api,\n BatchV1Api,\n AutoscalingV1Api,\n CoreV1Api,\n KubeConfig,\n NetworkingV1beta1Api,\n CustomObjectsApi,\n} from '@kubernetes/client-node';\nimport { ClusterDetails } from '../types/types';\n\nexport class KubernetesClientProvider {\n // visible for testing\n getKubeConfig(clusterDetails: ClusterDetails) {\n const cluster = {\n name: clusterDetails.name,\n server: clusterDetails.url,\n skipTLSVerify: clusterDetails.skipTLSVerify,\n caData: clusterDetails.caData,\n };\n\n // TODO configure\n const user = {\n name: 'backstage',\n token: clusterDetails.serviceAccountToken,\n };\n\n const context = {\n name: `${clusterDetails.name}`,\n user: user.name,\n cluster: cluster.name,\n };\n\n const kc = new KubeConfig();\n kc.loadFromOptions({\n clusters: [cluster],\n users: [user],\n contexts: [context],\n currentContext: context.name,\n });\n return kc;\n }\n\n getCoreClientByClusterDetails(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(CoreV1Api);\n }\n\n getAppsClientByClusterDetails(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(AppsV1Api);\n }\n\n getAutoscalingClientByClusterDetails(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(AutoscalingV1Api);\n }\n\n getBatchClientByClusterDetails(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(BatchV1Api);\n }\n\n getNetworkingBeta1Client(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(NetworkingV1beta1Api);\n }\n\n getCustomObjectsClient(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(CustomObjectsApi);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { GKEClusterDetails } from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\n\nexport class GoogleKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n async decorateClusterDetailsWithAuth(\n clusterDetails: GKEClusterDetails,\n requestBody: KubernetesRequestBody,\n ): Promise<GKEClusterDetails> {\n const clusterDetailsWithAuthToken: GKEClusterDetails = Object.assign(\n {},\n clusterDetails,\n );\n const authToken: string | undefined = requestBody.auth?.google;\n\n if (authToken) {\n clusterDetailsWithAuthToken.serviceAccountToken = authToken;\n } else {\n throw new Error(\n 'Google token not found under auth.google in request body',\n );\n }\n return clusterDetailsWithAuthToken;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { ServiceAccountClusterDetails } from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\n\nexport class ServiceAccountKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n async decorateClusterDetailsWithAuth(\n clusterDetails: ServiceAccountClusterDetails,\n // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read.\n // @ts-ignore-start\n requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars\n // @ts-ignore-end\n ): Promise<ServiceAccountClusterDetails> {\n return clusterDetails;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport AWS, { Credentials } from 'aws-sdk';\nimport { sign } from 'aws4';\nimport { AWSClusterDetails } from '../types/types';\nimport { KubernetesAuthTranslator } from './types';\n\nconst base64 = (str: string) =>\n Buffer.from(str.toString(), 'binary').toString('base64');\nconst prepend = (prep: string) => (str: string) => prep + str;\nconst replace =\n (search: string | RegExp, substitution: string) => (str: string) =>\n str.replace(search, substitution);\nconst pipe =\n (fns: ReadonlyArray<any>) =>\n (thing: string): string =>\n fns.reduce((val, fn) => fn(val), thing);\nconst removePadding = replace(/=+$/, '');\nconst makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]);\n\ntype SigningCreds = {\n accessKeyId: string | undefined;\n secretAccessKey: string | undefined;\n sessionToken: string | undefined;\n};\n\nexport class AwsIamKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n validCredentials(creds: SigningCreds): boolean {\n return (creds?.accessKeyId &&\n creds?.secretAccessKey &&\n creds?.sessionToken) as unknown as boolean;\n }\n\n awsGetCredentials = async (): Promise<Credentials> => {\n return new Promise((resolve, reject) => {\n AWS.config.getCredentials(err => {\n if (err) {\n return reject(err);\n }\n\n return resolve(AWS.config.credentials as Credentials);\n });\n });\n };\n\n async getCredentials(\n assumeRole?: string,\n externalId?: string,\n ): Promise<SigningCreds> {\n return new Promise<SigningCreds>(async (resolve, reject) => {\n const awsCreds = await this.awsGetCredentials();\n\n if (!(awsCreds instanceof Credentials))\n return reject(Error('No AWS credentials found.'));\n\n let creds: SigningCreds = {\n accessKeyId: awsCreds.accessKeyId,\n secretAccessKey: awsCreds.secretAccessKey,\n sessionToken: awsCreds.sessionToken,\n };\n\n if (!this.validCredentials(creds))\n return reject(Error('Invalid AWS credentials found.'));\n if (!assumeRole) return resolve(creds);\n\n try {\n const params: AWS.STS.Types.AssumeRoleRequest = {\n RoleArn: assumeRole,\n RoleSessionName: 'backstage-login',\n };\n if (externalId) params.ExternalId = externalId;\n\n const assumedRole = await new AWS.STS().assumeRole(params).promise();\n\n if (!assumedRole.Credentials) {\n throw new Error(`No credentials returned for role ${assumeRole}`);\n }\n\n creds = {\n accessKeyId: assumedRole.Credentials.AccessKeyId,\n secretAccessKey: assumedRole.Credentials.SecretAccessKey,\n sessionToken: assumedRole.Credentials.SessionToken,\n };\n } catch (e) {\n console.warn(`There was an error assuming the role: ${e}`);\n return reject(Error(`Unable to assume role: ${e}`));\n }\n return resolve(creds);\n });\n }\n async getBearerToken(\n clusterName: string,\n assumeRole?: string,\n externalId?: string,\n ): Promise<string> {\n const credentials = await this.getCredentials(assumeRole, externalId);\n\n const request = {\n host: `sts.amazonaws.com`,\n path: `/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Expires=60`,\n headers: {\n 'x-k8s-aws-id': clusterName,\n },\n signQuery: true,\n };\n\n const signedRequest = sign(request, credentials);\n\n return pipe([\n (signed: any) => `https://${signed.host}${signed.path}`,\n base64,\n removePadding,\n makeUrlSafe,\n prepend('k8s-aws-v1.'),\n ])(signedRequest);\n }\n\n async decorateClusterDetailsWithAuth(\n clusterDetails: AWSClusterDetails,\n ): Promise<AWSClusterDetails> {\n const clusterDetailsWithAuthToken: AWSClusterDetails = Object.assign(\n {},\n clusterDetails,\n );\n\n clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken(\n clusterDetails.name,\n clusterDetails.assumeRole,\n clusterDetails.externalId,\n );\n return clusterDetailsWithAuthToken;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator';\nimport { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator';\nimport { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';\n\nexport class KubernetesAuthTranslatorGenerator {\n static getKubernetesAuthTranslatorInstance(\n authProvider: string,\n ): KubernetesAuthTranslator {\n switch (authProvider) {\n case 'google': {\n return new GoogleKubernetesAuthTranslator();\n }\n case 'aws': {\n return new AwsIamKubernetesAuthTranslator();\n }\n case 'serviceAccount': {\n return new ServiceAccountKubernetesAuthTranslator();\n }\n default: {\n throw new Error(\n `authProvider \"${authProvider}\" has no KubernetesAuthTranslator associated with it`,\n );\n }\n }\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Logger } from 'winston';\nimport {\n ClusterDetails,\n CustomResource,\n KubernetesFetcher,\n KubernetesObjectsProviderOptions,\n KubernetesServiceLocator,\n ObjectsByEntityRequest,\n ObjectToFetch,\n} from '../types/types';\nimport { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';\nimport { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator';\nimport {\n ClusterObjects,\n ObjectsByEntityResponse,\n} from '@backstage/plugin-kubernetes-common';\n\nexport const DEFAULT_OBJECTS: ObjectToFetch[] = [\n {\n group: '',\n apiVersion: 'v1',\n plural: 'pods',\n objectType: 'pods',\n },\n {\n group: '',\n apiVersion: 'v1',\n plural: 'services',\n objectType: 'services',\n },\n {\n group: '',\n apiVersion: 'v1',\n plural: 'configmaps',\n objectType: 'configmaps',\n },\n {\n group: 'apps',\n apiVersion: 'v1',\n plural: 'deployments',\n objectType: 'deployments',\n },\n {\n group: 'apps',\n apiVersion: 'v1',\n plural: 'replicasets',\n objectType: 'replicasets',\n },\n {\n group: 'autoscaling',\n apiVersion: 'v1',\n plural: 'horizontalpodautoscalers',\n objectType: 'horizontalpodautoscalers',\n },\n {\n group: 'batch',\n apiVersion: 'v1',\n plural: 'jobs',\n objectType: 'jobs',\n },\n {\n group: 'batch',\n apiVersion: 'v1',\n plural: 'cronjobs',\n objectType: 'cronjobs',\n },\n {\n group: 'networking.k8s.io',\n apiVersion: 'v1',\n plural: 'ingresses',\n objectType: 'ingresses',\n },\n];\n\nexport interface KubernetesFanOutHandlerOptions\n extends KubernetesObjectsProviderOptions {}\n\nexport interface KubernetesRequestBody extends ObjectsByEntityRequest {}\n\nexport class KubernetesFanOutHandler {\n private readonly logger: Logger;\n private readonly fetcher: KubernetesFetcher;\n private readonly serviceLocator: KubernetesServiceLocator;\n private readonly customResources: CustomResource[];\n private readonly objectTypesToFetch: Set<ObjectToFetch>;\n\n constructor({\n logger,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch = DEFAULT_OBJECTS,\n }: KubernetesFanOutHandlerOptions) {\n this.logger = logger;\n this.fetcher = fetcher;\n this.serviceLocator = serviceLocator;\n this.customResources = customResources;\n this.objectTypesToFetch = new Set(objectTypesToFetch);\n }\n\n async getKubernetesObjectsByEntity(\n requestBody: KubernetesRequestBody,\n ): Promise<ObjectsByEntityResponse> {\n const entityName =\n requestBody.entity?.metadata?.annotations?.[\n 'backstage.io/kubernetes-id'\n ] || requestBody.entity?.metadata?.name;\n\n const clusterDetails: ClusterDetails[] =\n await this.serviceLocator.getClustersByServiceId(entityName);\n\n // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them\n const promises: Promise<ClusterDetails>[] = clusterDetails.map(cd => {\n const kubernetesAuthTranslator: KubernetesAuthTranslator =\n KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(\n cd.authProvider,\n );\n return kubernetesAuthTranslator.decorateClusterDetailsWithAuth(\n cd,\n requestBody,\n );\n });\n const clusterDetailsDecoratedForAuth: ClusterDetails[] = await Promise.all(\n promises,\n );\n\n this.logger.info(\n `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth\n .map(c => c.name)\n .join(', ')}]`,\n );\n\n const labelSelector: string =\n requestBody.entity?.metadata?.annotations?.[\n 'backstage.io/kubernetes-label-selector'\n ] || `backstage.io/kubernetes-id=${entityName}`;\n\n return Promise.all(\n clusterDetailsDecoratedForAuth.map(clusterDetailsItem => {\n return this.fetcher\n .fetchObjectsForService({\n serviceId: entityName,\n clusterDetails: clusterDetailsItem,\n objectTypesToFetch: this.objectTypesToFetch,\n labelSelector,\n customResources: this.customResources,\n })\n .then(result => {\n const objects: ClusterObjects = {\n cluster: {\n name: clusterDetailsItem.name,\n },\n resources: result.responses,\n errors: result.errors,\n };\n if (clusterDetailsItem.dashboardUrl) {\n objects.cluster.dashboardUrl = clusterDetailsItem.dashboardUrl;\n }\n if (clusterDetailsItem.dashboardApp) {\n objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp;\n }\n return objects;\n });\n }),\n ).then(r => ({\n items: r.filter(\n item =>\n (item.errors !== undefined && item.errors.length >= 1) ||\n (item.resources !== undefined &&\n item.resources.length >= 1 &&\n item.resources.some(fr => fr.resources.length >= 1)),\n ),\n }));\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppsV1Api,\n AutoscalingV1Api,\n BatchV1Api,\n CoreV1Api,\n NetworkingV1beta1Api,\n} from '@kubernetes/client-node';\nimport lodash, { Dictionary } from 'lodash';\nimport { Logger } from 'winston';\nimport {\n ClusterDetails,\n FetchResponseWrapper,\n KubernetesFetcher,\n KubernetesObjectTypes,\n ObjectFetchParams,\n ObjectToFetch,\n} from '../types/types';\nimport {\n FetchResponse,\n KubernetesFetchError,\n KubernetesErrorTypes,\n} from '@backstage/plugin-kubernetes-common';\nimport { KubernetesClientProvider } from './KubernetesClientProvider';\n\nexport interface Clients {\n core: CoreV1Api;\n apps: AppsV1Api;\n autoscaling: AutoscalingV1Api;\n batch: BatchV1Api;\n networkingBeta1: NetworkingV1beta1Api;\n}\n\nexport interface KubernetesClientBasedFetcherOptions {\n kubernetesClientProvider: KubernetesClientProvider;\n logger: Logger;\n}\n\ntype FetchResult = FetchResponse | KubernetesFetchError;\n\nconst isError = (fr: FetchResult): fr is KubernetesFetchError =>\n fr.hasOwnProperty('errorType');\n\nfunction fetchResultsToResponseWrapper(\n results: FetchResult[],\n): FetchResponseWrapper {\n const groupBy: Dictionary<FetchResult[]> = lodash.groupBy(results, value => {\n return isError(value) ? 'errors' : 'responses';\n });\n\n return {\n errors: groupBy.errors ?? [],\n responses: groupBy.responses ?? [],\n } as FetchResponseWrapper; // TODO would be nice to get rid of this 'as'\n}\n\nconst statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => {\n switch (statusCode) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED_ERROR';\n case 500:\n return 'SYSTEM_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n};\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly kubernetesClientProvider: KubernetesClientProvider;\n private readonly logger: Logger;\n\n constructor({\n kubernetesClientProvider,\n logger,\n }: KubernetesClientBasedFetcherOptions) {\n this.kubernetesClientProvider = kubernetesClientProvider;\n this.logger = logger;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(toFetch => {\n return this.fetchResource(\n params.clusterDetails,\n toFetch,\n params.labelSelector ||\n `backstage.io/kubernetes-id=${params.serviceId}`,\n toFetch.objectType,\n ).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));\n });\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n private captureKubernetesErrorsRethrowOthers(e: any): KubernetesFetchError {\n if (e.response && e.response.statusCode) {\n this.logger.info(\n `statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname}`,\n );\n return {\n errorType: statusCodeToErrorType(e.response.statusCode),\n statusCode: e.response.statusCode,\n resourcePath: e.response.request.uri.pathname,\n };\n }\n throw e;\n }\n\n private fetchResource(\n clusterDetails: ClusterDetails,\n resource: ObjectToFetch,\n labelSelector: string,\n objectType: KubernetesObjectTypes,\n ): Promise<FetchResponse> {\n const customObjects =\n this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails);\n\n customObjects.addInterceptor((requestOptions: any) => {\n requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/');\n });\n\n return customObjects\n .listClusterCustomObject(\n resource.group,\n resource.apiVersion,\n resource.plural,\n '',\n '',\n '',\n labelSelector,\n )\n .then(r => {\n return {\n type: objectType,\n resources: (r.body as any).items,\n };\n });\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport { getCombinedClusterDetails } from '../cluster-locator';\nimport { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';\nimport {\n ClusterDetails,\n KubernetesObjectTypes,\n ServiceLocatorMethod,\n CustomResource,\n KubernetesObjectsProvider,\n ObjectsByEntityRequest,\n KubernetesClustersSupplier,\n KubernetesFetcher,\n KubernetesServiceLocator,\n KubernetesObjectsProviderOptions,\n} from '../types/types';\nimport { KubernetesClientProvider } from './KubernetesClientProvider';\nimport {\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\n\nexport interface KubernetesEnvironment {\n logger: Logger;\n config: Config;\n}\n\nexport class KubernetesBuilder {\n private clusterSupplier?: KubernetesClustersSupplier;\n private objectsProvider?: KubernetesObjectsProvider;\n private fetcher?: KubernetesFetcher;\n private serviceLocator?: KubernetesServiceLocator;\n\n static createBuilder(env: KubernetesEnvironment) {\n return new KubernetesBuilder(env);\n }\n\n constructor(protected readonly env: KubernetesEnvironment) {}\n\n public async build() {\n const logger = this.env.logger;\n\n logger.info('Initializing Kubernetes backend');\n\n const customResources = this.buildCustomResources();\n\n const fetcher = this.fetcher ?? this.buildFetcher();\n\n const clusterSupplier = this.clusterSupplier ?? this.buildClusterSupplier();\n\n const clusterDetails = await this.fetchClusterDetails(clusterSupplier);\n\n const serviceLocator =\n this.serviceLocator ??\n this.buildServiceLocator(this.getServiceLocatorMethod(), clusterDetails);\n\n const objectsProvider =\n this.objectsProvider ??\n this.buildObjectsProvider({\n logger,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch: this.getObjectTypesToFetch(),\n });\n\n const router = this.buildRouter(objectsProvider, clusterDetails);\n\n return {\n clusterDetails,\n clusterSupplier,\n customResources,\n fetcher,\n objectsProvider,\n router,\n serviceLocator,\n };\n }\n\n public setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n return this;\n }\n\n public setObjectsProvider(objectsProvider?: KubernetesObjectsProvider) {\n this.objectsProvider = objectsProvider;\n return this;\n }\n\n public setFetcher(fetcher?: KubernetesFetcher) {\n this.fetcher = fetcher;\n return this;\n }\n\n public setServiceLocator(serviceLocator?: KubernetesServiceLocator) {\n this.serviceLocator = serviceLocator;\n return this;\n }\n\n protected buildCustomResources() {\n const customResources: CustomResource[] = (\n this.env.config.getOptionalConfigArray('kubernetes.customResources') ?? []\n ).map(\n c =>\n ({\n group: c.getString('group'),\n apiVersion: c.getString('apiVersion'),\n plural: c.getString('plural'),\n objectType: 'customresources',\n } as CustomResource),\n );\n\n this.env.logger.info(\n `action=LoadingCustomResources numOfCustomResources=${customResources.length}`,\n );\n return customResources;\n }\n\n protected buildClusterSupplier(): KubernetesClustersSupplier {\n const config = this.env.config;\n return {\n getClusters() {\n return getCombinedClusterDetails(config);\n },\n };\n }\n\n protected buildObjectsProvider(\n options: KubernetesObjectsProviderOptions,\n ): KubernetesObjectsProvider {\n return new KubernetesFanOutHandler(options);\n }\n\n protected buildFetcher(): KubernetesFetcher {\n return new KubernetesClientBasedFetcher({\n kubernetesClientProvider: new KubernetesClientProvider(),\n logger: this.env.logger,\n });\n }\n\n protected buildServiceLocator(\n method: ServiceLocatorMethod,\n clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n switch (method) {\n case 'multiTenant':\n return this.buildMultiTenantServiceLocator(clusterDetails);\n case 'http':\n return this.buildHttpServiceLocator(clusterDetails);\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethod \"${method}\"`,\n );\n }\n }\n\n protected buildMultiTenantServiceLocator(\n clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n return new MultiTenantServiceLocator(clusterDetails);\n }\n\n protected buildHttpServiceLocator(\n _clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n throw new Error('not implemented');\n }\n\n protected buildRouter(\n objectsProvider: KubernetesObjectsProvider,\n clusterDetails: ClusterDetails[],\n ): express.Router {\n const logger = this.env.logger;\n const router = Router();\n router.use(express.json());\n\n router.post('/services/:serviceId', async (req, res) => {\n const serviceId = req.params.serviceId;\n const requestBody: ObjectsByEntityRequest = req.body;\n try {\n const response = await objectsProvider.getKubernetesObjectsByEntity(\n requestBody,\n );\n res.json(response);\n } catch (e) {\n logger.error(\n `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`,\n );\n res.status(500).json({ error: e.message });\n }\n });\n\n router.get('/clusters', async (_, res) => {\n res.json({\n items: clusterDetails.map(cd => ({\n name: cd.name,\n dashboardUrl: cd.dashboardUrl,\n authProvider: cd.authProvider,\n })),\n });\n });\n return router;\n }\n\n protected async fetchClusterDetails(\n clusterSupplier: KubernetesClustersSupplier,\n ) {\n const clusterDetails = await clusterSupplier.getClusters();\n\n this.env.logger.info(\n `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,\n );\n\n return clusterDetails;\n }\n\n protected getServiceLocatorMethod() {\n return this.env.config.getString(\n 'kubernetes.serviceLocatorMethod.type',\n ) as ServiceLocatorMethod;\n }\n\n protected getObjectTypesToFetch() {\n const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n let objectTypesToFetch;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = DEFAULT_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n return objectTypesToFetch;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { Logger } from 'winston';\nimport { KubernetesClustersSupplier } from '../types/types';\nimport express from 'express';\nimport { KubernetesBuilder } from './KubernetesBuilder';\n\nexport interface RouterOptions {\n logger: Logger;\n config: Config;\n clusterSupplier?: KubernetesClustersSupplier;\n}\n\n/**\n * creates and configure a new router for handling the kubernetes backend APIs\n * @param options - specifies the options required by this plugin\n * @returns a new router\n * @deprecated Please use the new KubernetesBuilder instead like this\n * ```\n * import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';\n * const { router } = await KubernetesBuilder.createBuilder({\n * logger,\n * config,\n * }).build();\n * ```\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { router } = await KubernetesBuilder.createBuilder(options)\n .setClusterSupplier(options.clusterSupplier)\n .build();\n return router;\n}\n"],"names":["container","ForwardedError","KubeConfig","CoreV1Api","AppsV1Api","AutoscalingV1Api","BatchV1Api","NetworkingV1beta1Api","CustomObjectsApi","AWS","Credentials","sign","lodash","Router","express"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/cluster-locator/ConfigClusterLocator.ts","../src/cluster-locator/GkeClusterLocator.ts","../src/cluster-locator/index.ts","../src/service-locator/MultiTenantServiceLocator.ts","../src/service/KubernetesClientProvider.ts","../src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts","../src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts","../src/service/KubernetesFanOutHandler.ts","../src/service/KubernetesFetcher.ts","../src/service/KubernetesBuilder.ts","../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\n\nexport class ConfigClusterLocator implements KubernetesClustersSupplier {\n private readonly clusterDetails: ClusterDetails[];\n\n constructor(clusterDetails: ClusterDetails[]) {\n this.clusterDetails = clusterDetails;\n }\n\n static fromConfig(config: Config): ConfigClusterLocator {\n // TODO: Add validation that authProvider is required and serviceAccountToken\n // is required if authProvider is serviceAccount\n return new ConfigClusterLocator(\n config.getConfigArray('clusters').map(c => {\n const authProvider = c.getString('authProvider');\n const clusterDetails: ClusterDetails = {\n name: c.getString('name'),\n url: c.getString('url'),\n serviceAccountToken: c.getOptionalString('serviceAccountToken'),\n skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false,\n skipMetricsLookup: c.getOptionalBoolean('skipMetricsLookup') ?? false,\n caData: c.getOptionalString('caData'),\n authProvider: authProvider,\n };\n const dashboardUrl = c.getOptionalString('dashboardUrl');\n if (dashboardUrl) {\n clusterDetails.dashboardUrl = dashboardUrl;\n }\n const dashboardApp = c.getOptionalString('dashboardApp');\n if (dashboardApp) {\n clusterDetails.dashboardApp = dashboardApp;\n }\n if (c.has('dashboardParameters')) {\n clusterDetails.dashboardParameters = c.get('dashboardParameters');\n }\n\n switch (authProvider) {\n case 'google': {\n return clusterDetails;\n }\n case 'aws': {\n const assumeRole = c.getOptionalString('assumeRole');\n const externalId = c.getOptionalString('externalId');\n\n return { assumeRole, externalId, ...clusterDetails };\n }\n case 'serviceAccount': {\n return clusterDetails;\n }\n default: {\n throw new Error(\n `authProvider \"${authProvider}\" has no config associated with it`,\n );\n }\n }\n }),\n );\n }\n\n async getClusters(): Promise<ClusterDetails[]> {\n return this.clusterDetails;\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport * as container from '@google-cloud/container';\nimport { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types';\n\ntype GkeClusterLocatorOptions = {\n projectId: string;\n region?: string;\n skipTLSVerify?: boolean;\n skipMetricsLookup?: boolean;\n exposeDashboard?: boolean;\n};\n\nexport class GkeClusterLocator implements KubernetesClustersSupplier {\n constructor(\n private readonly options: GkeClusterLocatorOptions,\n private readonly client: container.v1.ClusterManagerClient,\n ) {}\n\n static fromConfigWithClient(\n config: Config,\n client: container.v1.ClusterManagerClient,\n ): GkeClusterLocator {\n const options = {\n projectId: config.getString('projectId'),\n region: config.getOptionalString('region') ?? '-',\n skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false,\n skipMetricsLookup:\n config.getOptionalBoolean('skipMetricsLookup') ?? false,\n exposeDashboard: config.getOptionalBoolean('exposeDashboard') ?? false,\n };\n return new GkeClusterLocator(options, client);\n }\n\n static fromConfig(config: Config): GkeClusterLocator {\n return GkeClusterLocator.fromConfigWithClient(\n config,\n new container.v1.ClusterManagerClient(),\n );\n }\n\n // TODO pass caData into the object\n async getClusters(): Promise<GKEClusterDetails[]> {\n const {\n projectId,\n region,\n skipTLSVerify,\n skipMetricsLookup,\n exposeDashboard,\n } = this.options;\n const request = {\n parent: `projects/${projectId}/locations/${region}`,\n };\n\n try {\n const [response] = await this.client.listClusters(request);\n return (response.clusters ?? []).map(r => ({\n // TODO filter out clusters which don't have name or endpoint\n name: r.name ?? 'unknown',\n url: `https://${r.endpoint ?? ''}`,\n authProvider: 'google',\n skipTLSVerify,\n skipMetricsLookup,\n ...(exposeDashboard\n ? {\n dashboardApp: 'gke',\n dashboardParameters: {\n projectId,\n region,\n clusterName: r.name,\n },\n }\n : {}),\n }));\n } catch (e) {\n throw new ForwardedError(\n `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,\n e,\n );\n }\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ClusterDetails } from '../types/types';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\n\nexport const getCombinedClusterDetails = async (\n rootConfig: Config,\n): Promise<ClusterDetails[]> => {\n return Promise.all(\n rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n ).getClusters();\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n ).getClusters();\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n }),\n )\n .then(res => {\n return res.flat();\n })\n .catch(e => {\n throw e;\n });\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ClusterDetails, KubernetesServiceLocator } from '../types/types';\n\n// This locator assumes that every service is located on every cluster\n// Therefore it will always return all clusters provided\nexport class MultiTenantServiceLocator implements KubernetesServiceLocator {\n private readonly clusterDetails: ClusterDetails[];\n\n constructor(clusterDetails: ClusterDetails[]) {\n this.clusterDetails = clusterDetails;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async getClustersByServiceId(_serviceId: string): Promise<ClusterDetails[]> {\n return this.clusterDetails;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CoreV1Api,\n KubeConfig,\n Metrics,\n CustomObjectsApi,\n} from '@kubernetes/client-node';\nimport { ClusterDetails } from '../types/types';\n\nexport class KubernetesClientProvider {\n // visible for testing\n getKubeConfig(clusterDetails: ClusterDetails) {\n const cluster = {\n name: clusterDetails.name,\n server: clusterDetails.url,\n skipTLSVerify: clusterDetails.skipTLSVerify,\n caData: clusterDetails.caData,\n };\n\n // TODO configure\n const user = {\n name: 'backstage',\n token: clusterDetails.serviceAccountToken,\n };\n\n const context = {\n name: `${clusterDetails.name}`,\n user: user.name,\n cluster: cluster.name,\n };\n\n const kc = new KubeConfig();\n kc.loadFromOptions({\n clusters: [cluster],\n users: [user],\n contexts: [context],\n currentContext: context.name,\n });\n return kc;\n }\n\n getCoreClientByClusterDetails(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(CoreV1Api);\n }\n\n getMetricsClient(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return new Metrics(kc);\n }\n\n getCustomObjectsClient(clusterDetails: ClusterDetails) {\n const kc = this.getKubeConfig(clusterDetails);\n\n return kc.makeApiClient(CustomObjectsApi);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { GKEClusterDetails } from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\n\nexport class GoogleKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n async decorateClusterDetailsWithAuth(\n clusterDetails: GKEClusterDetails,\n requestBody: KubernetesRequestBody,\n ): Promise<GKEClusterDetails> {\n const clusterDetailsWithAuthToken: GKEClusterDetails = Object.assign(\n {},\n clusterDetails,\n );\n const authToken: string | undefined = requestBody.auth?.google;\n\n if (authToken) {\n clusterDetailsWithAuthToken.serviceAccountToken = authToken;\n } else {\n throw new Error(\n 'Google token not found under auth.google in request body',\n );\n }\n return clusterDetailsWithAuthToken;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { ServiceAccountClusterDetails } from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\n\nexport class ServiceAccountKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n async decorateClusterDetailsWithAuth(\n clusterDetails: ServiceAccountClusterDetails,\n // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read.\n // @ts-ignore-start\n requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars\n // @ts-ignore-end\n ): Promise<ServiceAccountClusterDetails> {\n return clusterDetails;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport AWS, { Credentials } from 'aws-sdk';\nimport { sign } from 'aws4';\nimport { AWSClusterDetails } from '../types/types';\nimport { KubernetesAuthTranslator } from './types';\n\nconst base64 = (str: string) =>\n Buffer.from(str.toString(), 'binary').toString('base64');\nconst prepend = (prep: string) => (str: string) => prep + str;\nconst replace =\n (search: string | RegExp, substitution: string) => (str: string) =>\n str.replace(search, substitution);\nconst pipe =\n (fns: ReadonlyArray<any>) =>\n (thing: string): string =>\n fns.reduce((val, fn) => fn(val), thing);\nconst removePadding = replace(/=+$/, '');\nconst makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]);\n\ntype SigningCreds = {\n accessKeyId: string | undefined;\n secretAccessKey: string | undefined;\n sessionToken: string | undefined;\n};\n\nexport class AwsIamKubernetesAuthTranslator\n implements KubernetesAuthTranslator\n{\n validCredentials(creds: SigningCreds): boolean {\n return (creds?.accessKeyId &&\n creds?.secretAccessKey &&\n creds?.sessionToken) as unknown as boolean;\n }\n\n awsGetCredentials = async (): Promise<Credentials> => {\n return new Promise((resolve, reject) => {\n AWS.config.getCredentials(err => {\n if (err) {\n return reject(err);\n }\n\n return resolve(AWS.config.credentials as Credentials);\n });\n });\n };\n\n async getCredentials(\n assumeRole?: string,\n externalId?: string,\n ): Promise<SigningCreds> {\n return new Promise<SigningCreds>(async (resolve, reject) => {\n const awsCreds = await this.awsGetCredentials();\n\n if (!(awsCreds instanceof Credentials))\n return reject(Error('No AWS credentials found.'));\n\n let creds: SigningCreds = {\n accessKeyId: awsCreds.accessKeyId,\n secretAccessKey: awsCreds.secretAccessKey,\n sessionToken: awsCreds.sessionToken,\n };\n\n if (!this.validCredentials(creds))\n return reject(Error('Invalid AWS credentials found.'));\n if (!assumeRole) return resolve(creds);\n\n try {\n const params: AWS.STS.Types.AssumeRoleRequest = {\n RoleArn: assumeRole,\n RoleSessionName: 'backstage-login',\n };\n if (externalId) params.ExternalId = externalId;\n\n const assumedRole = await new AWS.STS().assumeRole(params).promise();\n\n if (!assumedRole.Credentials) {\n throw new Error(`No credentials returned for role ${assumeRole}`);\n }\n\n creds = {\n accessKeyId: assumedRole.Credentials.AccessKeyId,\n secretAccessKey: assumedRole.Credentials.SecretAccessKey,\n sessionToken: assumedRole.Credentials.SessionToken,\n };\n } catch (e) {\n console.warn(`There was an error assuming the role: ${e}`);\n return reject(Error(`Unable to assume role: ${e}`));\n }\n return resolve(creds);\n });\n }\n async getBearerToken(\n clusterName: string,\n assumeRole?: string,\n externalId?: string,\n ): Promise<string> {\n const credentials = await this.getCredentials(assumeRole, externalId);\n\n const request = {\n host: `sts.amazonaws.com`,\n path: `/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Expires=60`,\n headers: {\n 'x-k8s-aws-id': clusterName,\n },\n signQuery: true,\n };\n\n const signedRequest = sign(request, credentials);\n\n return pipe([\n (signed: any) => `https://${signed.host}${signed.path}`,\n base64,\n removePadding,\n makeUrlSafe,\n prepend('k8s-aws-v1.'),\n ])(signedRequest);\n }\n\n async decorateClusterDetailsWithAuth(\n clusterDetails: AWSClusterDetails,\n ): Promise<AWSClusterDetails> {\n const clusterDetailsWithAuthToken: AWSClusterDetails = Object.assign(\n {},\n clusterDetails,\n );\n\n clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken(\n clusterDetails.name,\n clusterDetails.assumeRole,\n clusterDetails.externalId,\n );\n return clusterDetailsWithAuthToken;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { KubernetesAuthTranslator } from './types';\nimport { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator';\nimport { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator';\nimport { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';\n\nexport class KubernetesAuthTranslatorGenerator {\n static getKubernetesAuthTranslatorInstance(\n authProvider: string,\n ): KubernetesAuthTranslator {\n switch (authProvider) {\n case 'google': {\n return new GoogleKubernetesAuthTranslator();\n }\n case 'aws': {\n return new AwsIamKubernetesAuthTranslator();\n }\n case 'serviceAccount': {\n return new ServiceAccountKubernetesAuthTranslator();\n }\n default: {\n throw new Error(\n `authProvider \"${authProvider}\" has no KubernetesAuthTranslator associated with it`,\n );\n }\n }\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Logger } from 'winston';\nimport {\n ClusterDetails,\n CustomResource,\n KubernetesFetcher,\n KubernetesObjectsProviderOptions,\n KubernetesServiceLocator,\n ObjectsByEntityRequest,\n ObjectToFetch,\n} from '../types/types';\nimport { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';\nimport { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator';\nimport {\n ClientContainerStatus,\n ClientCurrentResourceUsage,\n ClientPodStatus,\n ClusterObjects,\n FetchResponse,\n ObjectsByEntityResponse,\n PodFetchResponse,\n} from '@backstage/plugin-kubernetes-common';\nimport {\n ContainerStatus,\n CurrentResourceUsage,\n PodStatus,\n} from '@kubernetes/client-node';\n\nexport const DEFAULT_OBJECTS: ObjectToFetch[] = [\n {\n group: '',\n apiVersion: 'v1',\n plural: 'pods',\n objectType: 'pods',\n },\n {\n group: '',\n apiVersion: 'v1',\n plural: 'services',\n objectType: 'services',\n },\n {\n group: '',\n apiVersion: 'v1',\n plural: 'configmaps',\n objectType: 'configmaps',\n },\n {\n group: 'apps',\n apiVersion: 'v1',\n plural: 'deployments',\n objectType: 'deployments',\n },\n {\n group: 'apps',\n apiVersion: 'v1',\n plural: 'replicasets',\n objectType: 'replicasets',\n },\n {\n group: 'autoscaling',\n apiVersion: 'v1',\n plural: 'horizontalpodautoscalers',\n objectType: 'horizontalpodautoscalers',\n },\n {\n group: 'batch',\n apiVersion: 'v1',\n plural: 'jobs',\n objectType: 'jobs',\n },\n {\n group: 'batch',\n apiVersion: 'v1',\n plural: 'cronjobs',\n objectType: 'cronjobs',\n },\n {\n group: 'networking.k8s.io',\n apiVersion: 'v1',\n plural: 'ingresses',\n objectType: 'ingresses',\n },\n];\n\nexport interface KubernetesFanOutHandlerOptions\n extends KubernetesObjectsProviderOptions {}\n\nexport interface KubernetesRequestBody extends ObjectsByEntityRequest {}\n\nconst isPodFetchResponse = (fr: FetchResponse): fr is PodFetchResponse =>\n fr.type === 'pods';\nconst isString = (str: string | undefined): str is string => str !== undefined;\n\nconst numberOrBigIntToNumberOrString = (\n value: number | BigInt,\n): number | string => {\n // @ts-ignore\n return typeof value === 'bigint' ? value.toString() : value;\n};\n\nconst toClientSafeResource = (\n current: CurrentResourceUsage,\n): ClientCurrentResourceUsage => {\n return {\n currentUsage: numberOrBigIntToNumberOrString(current.CurrentUsage),\n requestTotal: numberOrBigIntToNumberOrString(current.RequestTotal),\n limitTotal: numberOrBigIntToNumberOrString(current.LimitTotal),\n };\n};\n\nconst toClientSafeContainer = (\n container: ContainerStatus,\n): ClientContainerStatus => {\n return {\n container: container.Container,\n cpuUsage: toClientSafeResource(container.CPUUsage),\n memoryUsage: toClientSafeResource(container.MemoryUsage),\n };\n};\n\nconst toClientSafePodMetrics = (\n podMetrics: PodStatus[][],\n): ClientPodStatus[] => {\n return podMetrics.flat().map((pd: PodStatus): ClientPodStatus => {\n return {\n pod: pd.Pod,\n memory: toClientSafeResource(pd.Memory),\n cpu: toClientSafeResource(pd.CPU),\n containers: pd.Containers.map(toClientSafeContainer),\n };\n });\n};\n\nexport class KubernetesFanOutHandler {\n private readonly logger: Logger;\n private readonly fetcher: KubernetesFetcher;\n private readonly serviceLocator: KubernetesServiceLocator;\n private readonly customResources: CustomResource[];\n private readonly objectTypesToFetch: Set<ObjectToFetch>;\n\n constructor({\n logger,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch = DEFAULT_OBJECTS,\n }: KubernetesFanOutHandlerOptions) {\n this.logger = logger;\n this.fetcher = fetcher;\n this.serviceLocator = serviceLocator;\n this.customResources = customResources;\n this.objectTypesToFetch = new Set(objectTypesToFetch);\n }\n\n async getKubernetesObjectsByEntity(\n requestBody: KubernetesRequestBody,\n ): Promise<ObjectsByEntityResponse> {\n const entityName =\n requestBody.entity?.metadata?.annotations?.[\n 'backstage.io/kubernetes-id'\n ] || requestBody.entity?.metadata?.name;\n\n const clusterDetails: ClusterDetails[] =\n await this.serviceLocator.getClustersByServiceId(entityName);\n\n // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them\n const promises: Promise<ClusterDetails>[] = clusterDetails.map(cd => {\n const kubernetesAuthTranslator: KubernetesAuthTranslator =\n KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(\n cd.authProvider,\n );\n return kubernetesAuthTranslator.decorateClusterDetailsWithAuth(\n cd,\n requestBody,\n );\n });\n const clusterDetailsDecoratedForAuth: ClusterDetails[] = await Promise.all(\n promises,\n );\n\n this.logger.info(\n `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth\n .map(c => c.name)\n .join(', ')}]`,\n );\n\n const labelSelector: string =\n requestBody.entity?.metadata?.annotations?.[\n 'backstage.io/kubernetes-label-selector'\n ] || `backstage.io/kubernetes-id=${entityName}`;\n\n return Promise.all(\n clusterDetailsDecoratedForAuth.map(clusterDetailsItem => {\n return this.fetcher\n .fetchObjectsForService({\n serviceId: entityName,\n clusterDetails: clusterDetailsItem,\n objectTypesToFetch: this.objectTypesToFetch,\n labelSelector,\n customResources: this.customResources,\n })\n .then(result => {\n if (clusterDetailsItem.skipMetricsLookup) {\n return Promise.all([\n Promise.resolve(result),\n Promise.resolve([]),\n ]);\n }\n // TODO refactor, extract as method\n const namespaces: Set<string> = new Set<string>(\n result.responses\n .filter(isPodFetchResponse)\n .flatMap(r => r.resources)\n .map(p => p.metadata?.namespace)\n .filter(isString),\n );\n\n const podMetrics = Array.from(namespaces).map(ns =>\n this.fetcher.fetchPodMetricsByNamespace(clusterDetailsItem, ns),\n );\n\n return Promise.all([\n Promise.resolve(result),\n Promise.all(podMetrics),\n ]);\n })\n .then(([result, metrics]) => {\n const objects: ClusterObjects = {\n cluster: {\n name: clusterDetailsItem.name,\n },\n podMetrics: toClientSafePodMetrics(metrics),\n resources: result.responses,\n errors: result.errors,\n };\n if (clusterDetailsItem.dashboardUrl) {\n objects.cluster.dashboardUrl = clusterDetailsItem.dashboardUrl;\n }\n if (clusterDetailsItem.dashboardApp) {\n objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp;\n }\n if (clusterDetailsItem.dashboardParameters) {\n objects.cluster.dashboardParameters =\n clusterDetailsItem.dashboardParameters;\n }\n return objects;\n });\n }),\n ).then(r => ({\n items: r.filter(\n item =>\n (item.errors !== undefined && item.errors.length >= 1) ||\n (item.resources !== undefined &&\n item.resources.length >= 1 &&\n item.resources.some(fr => fr.resources.length >= 1)),\n ),\n }));\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CoreV1Api, topPods } from '@kubernetes/client-node';\nimport lodash, { Dictionary } from 'lodash';\nimport { Logger } from 'winston';\nimport {\n ClusterDetails,\n FetchResponseWrapper,\n KubernetesFetcher,\n KubernetesObjectTypes,\n ObjectFetchParams,\n ObjectToFetch,\n} from '../types/types';\nimport {\n FetchResponse,\n KubernetesFetchError,\n KubernetesErrorTypes,\n} from '@backstage/plugin-kubernetes-common';\nimport { KubernetesClientProvider } from './KubernetesClientProvider';\nimport { PodStatus } from '@kubernetes/client-node/dist/top';\n\nexport interface Clients {\n core: CoreV1Api;\n}\n\nexport interface KubernetesClientBasedFetcherOptions {\n kubernetesClientProvider: KubernetesClientProvider;\n logger: Logger;\n}\n\ntype FetchResult = FetchResponse | KubernetesFetchError;\n\nconst isError = (fr: FetchResult): fr is KubernetesFetchError =>\n fr.hasOwnProperty('errorType');\n\nfunction fetchResultsToResponseWrapper(\n results: FetchResult[],\n): FetchResponseWrapper {\n const groupBy: Dictionary<FetchResult[]> = lodash.groupBy(results, value => {\n return isError(value) ? 'errors' : 'responses';\n });\n\n return {\n errors: groupBy.errors ?? [],\n responses: groupBy.responses ?? [],\n } as FetchResponseWrapper; // TODO would be nice to get rid of this 'as'\n}\n\nconst statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => {\n switch (statusCode) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED_ERROR';\n case 500:\n return 'SYSTEM_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n};\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly kubernetesClientProvider: KubernetesClientProvider;\n private readonly logger: Logger;\n\n constructor({\n kubernetesClientProvider,\n logger,\n }: KubernetesClientBasedFetcherOptions) {\n this.kubernetesClientProvider = kubernetesClientProvider;\n this.logger = logger;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(toFetch => {\n return this.fetchResource(\n params.clusterDetails,\n toFetch,\n params.labelSelector ||\n `backstage.io/kubernetes-id=${params.serviceId}`,\n toFetch.objectType,\n ).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));\n });\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n fetchPodMetricsByNamespace(\n clusterDetails: ClusterDetails,\n namespace: string,\n ): Promise<PodStatus[]> {\n const metricsClient =\n this.kubernetesClientProvider.getMetricsClient(clusterDetails);\n const coreApi =\n this.kubernetesClientProvider.getCoreClientByClusterDetails(\n clusterDetails,\n );\n\n return topPods(coreApi, metricsClient, namespace);\n }\n\n private captureKubernetesErrorsRethrowOthers(e: any): KubernetesFetchError {\n if (e.response && e.response.statusCode) {\n this.logger.warn(\n `statusCode=${e.response.statusCode} for resource ${\n e.response.request.uri.pathname\n } body=[${JSON.stringify(e.response.body)}]`,\n );\n return {\n errorType: statusCodeToErrorType(e.response.statusCode),\n statusCode: e.response.statusCode,\n resourcePath: e.response.request.uri.pathname,\n };\n }\n throw e;\n }\n\n private fetchResource(\n clusterDetails: ClusterDetails,\n resource: ObjectToFetch,\n labelSelector: string,\n objectType: KubernetesObjectTypes,\n ): Promise<FetchResponse> {\n const customObjects =\n this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails);\n\n customObjects.addInterceptor((requestOptions: any) => {\n requestOptions.uri = requestOptions.uri.replace('/apis//v1/', '/api/v1/');\n });\n\n return customObjects\n .listClusterCustomObject(\n resource.group,\n resource.apiVersion,\n resource.plural,\n '',\n false,\n '',\n '',\n labelSelector,\n )\n .then(r => {\n return {\n type: objectType,\n resources: (r.body as any).items,\n };\n });\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Logger } from 'winston';\nimport { getCombinedClusterDetails } from '../cluster-locator';\nimport { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';\nimport {\n ClusterDetails,\n KubernetesObjectTypes,\n ServiceLocatorMethod,\n CustomResource,\n KubernetesObjectsProvider,\n ObjectsByEntityRequest,\n KubernetesClustersSupplier,\n KubernetesFetcher,\n KubernetesServiceLocator,\n KubernetesObjectsProviderOptions,\n} from '../types/types';\nimport { KubernetesClientProvider } from './KubernetesClientProvider';\nimport {\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\n\nexport interface KubernetesEnvironment {\n logger: Logger;\n config: Config;\n}\n\nexport class KubernetesBuilder {\n private clusterSupplier?: KubernetesClustersSupplier;\n private objectsProvider?: KubernetesObjectsProvider;\n private fetcher?: KubernetesFetcher;\n private serviceLocator?: KubernetesServiceLocator;\n\n static createBuilder(env: KubernetesEnvironment) {\n return new KubernetesBuilder(env);\n }\n\n constructor(protected readonly env: KubernetesEnvironment) {}\n\n public async build() {\n const logger = this.env.logger;\n\n logger.info('Initializing Kubernetes backend');\n\n const customResources = this.buildCustomResources();\n\n const fetcher = this.fetcher ?? this.buildFetcher();\n\n const clusterSupplier = this.clusterSupplier ?? this.buildClusterSupplier();\n\n const clusterDetails = await this.fetchClusterDetails(clusterSupplier);\n\n const serviceLocator =\n this.serviceLocator ??\n this.buildServiceLocator(this.getServiceLocatorMethod(), clusterDetails);\n\n const objectsProvider =\n this.objectsProvider ??\n this.buildObjectsProvider({\n logger,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch: this.getObjectTypesToFetch(),\n });\n\n const router = this.buildRouter(objectsProvider, clusterDetails);\n\n return {\n clusterDetails,\n clusterSupplier,\n customResources,\n fetcher,\n objectsProvider,\n router,\n serviceLocator,\n };\n }\n\n public setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n return this;\n }\n\n public setObjectsProvider(objectsProvider?: KubernetesObjectsProvider) {\n this.objectsProvider = objectsProvider;\n return this;\n }\n\n public setFetcher(fetcher?: KubernetesFetcher) {\n this.fetcher = fetcher;\n return this;\n }\n\n public setServiceLocator(serviceLocator?: KubernetesServiceLocator) {\n this.serviceLocator = serviceLocator;\n return this;\n }\n\n protected buildCustomResources() {\n const customResources: CustomResource[] = (\n this.env.config.getOptionalConfigArray('kubernetes.customResources') ?? []\n ).map(\n c =>\n ({\n group: c.getString('group'),\n apiVersion: c.getString('apiVersion'),\n plural: c.getString('plural'),\n objectType: 'customresources',\n } as CustomResource),\n );\n\n this.env.logger.info(\n `action=LoadingCustomResources numOfCustomResources=${customResources.length}`,\n );\n return customResources;\n }\n\n protected buildClusterSupplier(): KubernetesClustersSupplier {\n const config = this.env.config;\n return {\n getClusters() {\n return getCombinedClusterDetails(config);\n },\n };\n }\n\n protected buildObjectsProvider(\n options: KubernetesObjectsProviderOptions,\n ): KubernetesObjectsProvider {\n return new KubernetesFanOutHandler(options);\n }\n\n protected buildFetcher(): KubernetesFetcher {\n return new KubernetesClientBasedFetcher({\n kubernetesClientProvider: new KubernetesClientProvider(),\n logger: this.env.logger,\n });\n }\n\n protected buildServiceLocator(\n method: ServiceLocatorMethod,\n clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n switch (method) {\n case 'multiTenant':\n return this.buildMultiTenantServiceLocator(clusterDetails);\n case 'http':\n return this.buildHttpServiceLocator(clusterDetails);\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethod \"${method}\"`,\n );\n }\n }\n\n protected buildMultiTenantServiceLocator(\n clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n return new MultiTenantServiceLocator(clusterDetails);\n }\n\n protected buildHttpServiceLocator(\n _clusterDetails: ClusterDetails[],\n ): KubernetesServiceLocator {\n throw new Error('not implemented');\n }\n\n protected buildRouter(\n objectsProvider: KubernetesObjectsProvider,\n clusterDetails: ClusterDetails[],\n ): express.Router {\n const logger = this.env.logger;\n const router = Router();\n router.use(express.json());\n\n router.post('/services/:serviceId', async (req, res) => {\n const serviceId = req.params.serviceId;\n const requestBody: ObjectsByEntityRequest = req.body;\n try {\n const response = await objectsProvider.getKubernetesObjectsByEntity(\n requestBody,\n );\n res.json(response);\n } catch (e) {\n logger.error(\n `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`,\n );\n res.status(500).json({ error: e.message });\n }\n });\n\n router.get('/clusters', async (_, res) => {\n res.json({\n items: clusterDetails.map(cd => ({\n name: cd.name,\n dashboardUrl: cd.dashboardUrl,\n authProvider: cd.authProvider,\n })),\n });\n });\n return router;\n }\n\n protected async fetchClusterDetails(\n clusterSupplier: KubernetesClustersSupplier,\n ) {\n const clusterDetails = await clusterSupplier.getClusters();\n\n this.env.logger.info(\n `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,\n );\n\n return clusterDetails;\n }\n\n protected getServiceLocatorMethod() {\n return this.env.config.getString(\n 'kubernetes.serviceLocatorMethod.type',\n ) as ServiceLocatorMethod;\n }\n\n protected getObjectTypesToFetch() {\n const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const apiVersionOverrides = this.env.config.getOptionalConfig(\n 'kubernetes.apiVersionOverrides',\n );\n\n let objectTypesToFetch;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = DEFAULT_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n\n if (apiVersionOverrides) {\n objectTypesToFetch = objectTypesToFetch ?? DEFAULT_OBJECTS;\n\n for (const obj of objectTypesToFetch) {\n if (apiVersionOverrides.has(obj.objectType)) {\n obj.apiVersion = apiVersionOverrides.getString(obj.objectType);\n }\n }\n }\n\n return objectTypesToFetch;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { Logger } from 'winston';\nimport { KubernetesClustersSupplier } from '../types/types';\nimport express from 'express';\nimport { KubernetesBuilder } from './KubernetesBuilder';\n\nexport interface RouterOptions {\n logger: Logger;\n config: Config;\n clusterSupplier?: KubernetesClustersSupplier;\n}\n\n/**\n * creates and configure a new router for handling the kubernetes backend APIs\n * @param options - specifies the options required by this plugin\n * @returns a new router\n * @deprecated Please use the new KubernetesBuilder instead like this\n * ```\n * import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';\n * const { router } = await KubernetesBuilder.createBuilder({\n * logger,\n * config,\n * }).build();\n * ```\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { router } = await KubernetesBuilder.createBuilder(options)\n .setClusterSupplier(options.clusterSupplier)\n .build();\n return router;\n}\n"],"names":["container","ForwardedError","KubeConfig","CoreV1Api","Metrics","CustomObjectsApi","AWS","Credentials","sign","lodash","topPods","Router","express"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Config } from '@backstage/config';
2
2
  import { Logger } from 'winston';
3
+ import { JsonObject } from '@backstage/types';
3
4
  import { KubernetesFetchError, FetchResponse, KubernetesRequestBody, ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
5
+ import { PodStatus } from '@kubernetes/client-node/dist/top';
4
6
  import express from 'express';
5
7
 
6
8
  interface ObjectFetchParams {
@@ -12,6 +14,7 @@ interface ObjectFetchParams {
12
14
  }
13
15
  interface KubernetesFetcher {
14
16
  fetchObjectsForService(params: ObjectFetchParams): Promise<FetchResponseWrapper>;
17
+ fetchPodMetricsByNamespace(clusterDetails: ClusterDetails, namespace: string): Promise<PodStatus[]>;
15
18
  }
16
19
  interface FetchResponseWrapper {
17
20
  errors: KubernetesFetchError[];
@@ -43,6 +46,11 @@ interface ClusterDetails {
43
46
  authProvider: string;
44
47
  serviceAccountToken?: string | undefined;
45
48
  skipTLSVerify?: boolean;
49
+ /**
50
+ * Whether to skip the lookup to the metrics server to retrieve pod resource usage.
51
+ * It is not guaranteed that the Kubernetes distro has the metrics server installed.
52
+ */
53
+ skipMetricsLookup?: boolean;
46
54
  caData?: string | undefined;
47
55
  /**
48
56
  * Specifies the link to the Kubernetes dashboard managing this cluster.
@@ -51,6 +59,7 @@ interface ClusterDetails {
51
59
  * using the dashboardApp property, in order to properly format
52
60
  * links to kubernetes resources, otherwise it will assume that you're running the standard one.
53
61
  * @see dashboardApp
62
+ * @see dashboardParameters
54
63
  */
55
64
  dashboardUrl?: string;
56
65
  /**
@@ -69,6 +78,12 @@ interface ClusterDetails {
69
78
  * ```
70
79
  */
71
80
  dashboardApp?: string;
81
+ /**
82
+ * Specifies specific parameters used by some dashboard URL formatters.
83
+ * This is used by the GKE formatter which requires the project, region and cluster name.
84
+ * @see dashboardApp
85
+ */
86
+ dashboardParameters?: JsonObject;
72
87
  }
73
88
  interface GKEClusterDetails extends ClusterDetails {
74
89
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-kubernetes-backend",
3
3
  "description": "A Backstage backend plugin that integrates towards Kubernetes",
4
- "version": "0.3.19",
4
+ "version": "0.4.2",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -32,13 +32,13 @@
32
32
  "clean": "backstage-cli clean"
33
33
  },
34
34
  "dependencies": {
35
- "@backstage/backend-common": "^0.9.11",
35
+ "@backstage/backend-common": "^0.10.2",
36
36
  "@backstage/catalog-model": "^0.9.7",
37
37
  "@backstage/config": "^0.1.10",
38
38
  "@backstage/errors": "^0.1.5",
39
- "@backstage/plugin-kubernetes-common": "^0.1.6",
39
+ "@backstage/plugin-kubernetes-common": "^0.2.1",
40
40
  "@google-cloud/container": "^2.2.0",
41
- "@kubernetes/client-node": "^0.15.0",
41
+ "@kubernetes/client-node": "^0.16.0",
42
42
  "@types/express": "^4.17.6",
43
43
  "aws-sdk": "^2.840.0",
44
44
  "aws4": "^1.11.0",
@@ -55,7 +55,7 @@
55
55
  "yn": "^4.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@backstage/cli": "^0.9.1",
58
+ "@backstage/cli": "^0.10.5",
59
59
  "@types/aws4": "^1.5.1",
60
60
  "aws-sdk-mock": "^5.2.1",
61
61
  "bdd-lazy-var": "^2.6.0",
@@ -65,5 +65,5 @@
65
65
  "dist",
66
66
  "schema.d.ts"
67
67
  ],
68
- "gitHead": "85c127e436a24140bb3606f17f034f82aa9c7c0d"
68
+ "gitHead": "ffdb98aa2973366d48ff1774a7f892bc0c926e7e"
69
69
  }
package/schema.d.ts CHANGED
@@ -63,5 +63,23 @@ export interface Config {
63
63
  apiVersion: string;
64
64
  plural: string;
65
65
  }>;
66
+
67
+ /**
68
+ * (Optional) API Version Overrides
69
+ * If set, the specified api version will be used to make requests for the corresponding object.
70
+ * If running a legacy Kubernetes version, you may use this to override the default api versions
71
+ * that are not supported in your cluster.
72
+ */
73
+ apiVersionOverrides?: {
74
+ pods?: string;
75
+ services?: string;
76
+ configmaps?: string;
77
+ deployments?: string;
78
+ replicasets?: string;
79
+ horizontalpodautoscalers?: string;
80
+ cronjobs?: string;
81
+ jobs?: string;
82
+ ingresses?: string;
83
+ } & { [pluralKind: string]: string };
66
84
  };
67
85
  }