@backstage/plugin-kubernetes-backend 0.7.0-next.2 → 0.7.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -7,11 +7,14 @@ var Router = require('express-promise-router');
7
7
  var luxon = require('luxon');
8
8
  var errors = require('@backstage/errors');
9
9
  var container = require('@google-cloud/container');
10
+ var catalogClient = require('@backstage/catalog-client');
11
+ var catalogModel = require('@backstage/catalog-model');
10
12
  var clientNode = require('@kubernetes/client-node');
11
13
  var AWS = require('aws-sdk');
12
14
  var aws4 = require('aws4');
13
15
  var identity = require('@azure/identity');
14
16
  var lodash = require('lodash');
17
+ var pluginAuthNode = require('@backstage/plugin-auth-node');
15
18
 
16
19
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
17
20
 
@@ -44,56 +47,60 @@ class ConfigClusterLocator {
44
47
  this.clusterDetails = clusterDetails;
45
48
  }
46
49
  static fromConfig(config) {
47
- return new ConfigClusterLocator(config.getConfigArray("clusters").map((c) => {
48
- var _a, _b;
49
- const authProvider = c.getString("authProvider");
50
- const clusterDetails = {
51
- name: c.getString("name"),
52
- url: c.getString("url"),
53
- serviceAccountToken: c.getOptionalString("serviceAccountToken"),
54
- skipTLSVerify: (_a = c.getOptionalBoolean("skipTLSVerify")) != null ? _a : false,
55
- skipMetricsLookup: (_b = c.getOptionalBoolean("skipMetricsLookup")) != null ? _b : false,
56
- caData: c.getOptionalString("caData"),
57
- authProvider
58
- };
59
- const dashboardUrl = c.getOptionalString("dashboardUrl");
60
- if (dashboardUrl) {
61
- clusterDetails.dashboardUrl = dashboardUrl;
62
- }
63
- const dashboardApp = c.getOptionalString("dashboardApp");
64
- if (dashboardApp) {
65
- clusterDetails.dashboardApp = dashboardApp;
66
- }
67
- if (c.has("dashboardParameters")) {
68
- clusterDetails.dashboardParameters = c.get("dashboardParameters");
69
- }
70
- switch (authProvider) {
71
- case "google": {
72
- return clusterDetails;
73
- }
74
- case "aws": {
75
- const assumeRole = c.getOptionalString("assumeRole");
76
- const externalId = c.getOptionalString("externalId");
77
- return { assumeRole, externalId, ...clusterDetails };
78
- }
79
- case "azure": {
80
- return clusterDetails;
81
- }
82
- case "oidc": {
83
- const oidcTokenProvider = c.getString("oidcTokenProvider");
84
- return { oidcTokenProvider, ...clusterDetails };
50
+ return new ConfigClusterLocator(
51
+ config.getConfigArray("clusters").map((c) => {
52
+ var _a, _b;
53
+ const authProvider = c.getString("authProvider");
54
+ const clusterDetails = {
55
+ name: c.getString("name"),
56
+ url: c.getString("url"),
57
+ serviceAccountToken: c.getOptionalString("serviceAccountToken"),
58
+ skipTLSVerify: (_a = c.getOptionalBoolean("skipTLSVerify")) != null ? _a : false,
59
+ skipMetricsLookup: (_b = c.getOptionalBoolean("skipMetricsLookup")) != null ? _b : false,
60
+ caData: c.getOptionalString("caData"),
61
+ authProvider
62
+ };
63
+ const dashboardUrl = c.getOptionalString("dashboardUrl");
64
+ if (dashboardUrl) {
65
+ clusterDetails.dashboardUrl = dashboardUrl;
85
66
  }
86
- case "serviceAccount": {
87
- return clusterDetails;
67
+ const dashboardApp = c.getOptionalString("dashboardApp");
68
+ if (dashboardApp) {
69
+ clusterDetails.dashboardApp = dashboardApp;
88
70
  }
89
- case "googleServiceAccount": {
90
- return clusterDetails;
71
+ if (c.has("dashboardParameters")) {
72
+ clusterDetails.dashboardParameters = c.get("dashboardParameters");
91
73
  }
92
- default: {
93
- throw new Error(`authProvider "${authProvider}" has no config associated with it`);
74
+ switch (authProvider) {
75
+ case "google": {
76
+ return clusterDetails;
77
+ }
78
+ case "aws": {
79
+ const assumeRole = c.getOptionalString("assumeRole");
80
+ const externalId = c.getOptionalString("externalId");
81
+ return { assumeRole, externalId, ...clusterDetails };
82
+ }
83
+ case "azure": {
84
+ return clusterDetails;
85
+ }
86
+ case "oidc": {
87
+ const oidcTokenProvider = c.getString("oidcTokenProvider");
88
+ return { oidcTokenProvider, ...clusterDetails };
89
+ }
90
+ case "serviceAccount": {
91
+ return clusterDetails;
92
+ }
93
+ case "googleServiceAccount": {
94
+ return clusterDetails;
95
+ }
96
+ default: {
97
+ throw new Error(
98
+ `authProvider "${authProvider}" has no config associated with it`
99
+ );
100
+ }
94
101
  }
95
- }
96
- }));
102
+ })
103
+ );
97
104
  }
98
105
  async getClusters() {
99
106
  return this.clusterDetails;
@@ -147,12 +154,19 @@ class GkeClusterLocator {
147
154
  };
148
155
  const gkeClusterLocator = new GkeClusterLocator(options, client);
149
156
  if (refreshInterval) {
150
- runPeriodically(() => gkeClusterLocator.refreshClusters(), refreshInterval.toMillis());
157
+ runPeriodically(
158
+ () => gkeClusterLocator.refreshClusters(),
159
+ refreshInterval.toMillis()
160
+ );
151
161
  }
152
162
  return gkeClusterLocator;
153
163
  }
154
164
  static fromConfig(config, refreshInterval = void 0) {
155
- return GkeClusterLocator.fromConfigWithClient(config, new container__namespace.v1.ClusterManagerClient(), refreshInterval);
165
+ return GkeClusterLocator.fromConfigWithClient(
166
+ config,
167
+ new container__namespace.v1.ClusterManagerClient(),
168
+ refreshInterval
169
+ );
156
170
  }
157
171
  async getClusters() {
158
172
  var _a;
@@ -203,33 +217,96 @@ class GkeClusterLocator {
203
217
  });
204
218
  this.hasClusterDetails = true;
205
219
  } catch (e) {
206
- throw new errors.ForwardedError(`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`, e);
220
+ throw new errors.ForwardedError(
221
+ `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
222
+ e
223
+ );
207
224
  }
208
225
  }
209
226
  }
210
227
 
228
+ class CatalogClusterLocator {
229
+ constructor(catalogClient) {
230
+ this.catalogClient = catalogClient;
231
+ }
232
+ static fromConfig(catalogApi) {
233
+ return new CatalogClusterLocator(catalogApi);
234
+ }
235
+ async getClusters() {
236
+ const apiServerKey = `metadata.annotations.${catalogModel.ANNOTATION_KUBERNETES_API_SERVER}`;
237
+ const apiServerCaKey = `metadata.annotations.${catalogModel.ANNOTATION_KUBERNETES_API_SERVER_CA}`;
238
+ const authProviderKey = `metadata.annotations.${catalogModel.ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;
239
+ const filter = {
240
+ kind: "Resource",
241
+ "spec.type": "kubernetes-cluster",
242
+ [apiServerKey]: catalogClient.CATALOG_FILTER_EXISTS,
243
+ [apiServerCaKey]: catalogClient.CATALOG_FILTER_EXISTS,
244
+ [authProviderKey]: catalogClient.CATALOG_FILTER_EXISTS
245
+ };
246
+ const clusters = await this.catalogClient.getEntities({
247
+ filter: [filter]
248
+ });
249
+ return clusters.items.map((entity) => {
250
+ const clusterDetails = {
251
+ name: entity.metadata.name,
252
+ url: entity.metadata.annotations[catalogModel.ANNOTATION_KUBERNETES_API_SERVER],
253
+ caData: entity.metadata.annotations[catalogModel.ANNOTATION_KUBERNETES_API_SERVER_CA],
254
+ authProvider: entity.metadata.annotations[catalogModel.ANNOTATION_KUBERNETES_AUTH_PROVIDER]
255
+ };
256
+ return clusterDetails;
257
+ });
258
+ }
259
+ }
260
+
261
+ class LocalKubectlProxyClusterLocator {
262
+ constructor() {
263
+ this.clusterDetails = [
264
+ {
265
+ name: "local",
266
+ url: "http:/localhost:8001",
267
+ authProvider: "localKubectlProxy",
268
+ skipMetricsLookup: true
269
+ }
270
+ ];
271
+ }
272
+ async getClusters() {
273
+ return this.clusterDetails;
274
+ }
275
+ }
276
+
211
277
  class CombinedClustersSupplier {
212
278
  constructor(clusterSuppliers) {
213
279
  this.clusterSuppliers = clusterSuppliers;
214
280
  }
215
281
  async getClusters() {
216
- return await Promise.all(this.clusterSuppliers.map((supplier) => supplier.getClusters())).then((res) => {
282
+ return await Promise.all(
283
+ this.clusterSuppliers.map((supplier) => supplier.getClusters())
284
+ ).then((res) => {
217
285
  return res.flat();
218
286
  }).catch((e) => {
219
287
  throw e;
220
288
  });
221
289
  }
222
290
  }
223
- const getCombinedClusterSupplier = (rootConfig, refreshInterval = void 0) => {
291
+ const getCombinedClusterSupplier = (rootConfig, catalogClient, refreshInterval = void 0) => {
224
292
  const clusterSuppliers = rootConfig.getConfigArray("kubernetes.clusterLocatorMethods").map((clusterLocatorMethod) => {
225
293
  const type = clusterLocatorMethod.getString("type");
226
294
  switch (type) {
295
+ case "catalog":
296
+ return CatalogClusterLocator.fromConfig(catalogClient);
297
+ case "localKubectlProxy":
298
+ return new LocalKubectlProxyClusterLocator();
227
299
  case "config":
228
300
  return ConfigClusterLocator.fromConfig(clusterLocatorMethod);
229
301
  case "gke":
230
- return GkeClusterLocator.fromConfig(clusterLocatorMethod, refreshInterval);
302
+ return GkeClusterLocator.fromConfig(
303
+ clusterLocatorMethod,
304
+ refreshInterval
305
+ );
231
306
  default:
232
- throw new Error(`Unsupported kubernetes.clusterLocatorMethods: "${type}"`);
307
+ throw new Error(
308
+ `Unsupported kubernetes.clusterLocatorMethods: "${type}"`
309
+ );
233
310
  }
234
311
  });
235
312
  return new CombinedClustersSupplier(clusterSuppliers);
@@ -290,12 +367,17 @@ class KubernetesClientProvider {
290
367
 
291
368
  class GoogleKubernetesAuthTranslator {
292
369
  async decorateClusterDetailsWithAuth(clusterDetails, authConfig) {
293
- const clusterDetailsWithAuthToken = Object.assign({}, clusterDetails);
370
+ const clusterDetailsWithAuthToken = Object.assign(
371
+ {},
372
+ clusterDetails
373
+ );
294
374
  const authToken = authConfig.google;
295
375
  if (authToken) {
296
376
  clusterDetailsWithAuthToken.serviceAccountToken = authToken;
297
377
  } else {
298
- throw new Error("Google token not found under auth.google in request body");
378
+ throw new Error(
379
+ "Google token not found under auth.google in request body"
380
+ );
299
381
  }
300
382
  return clusterDetailsWithAuthToken;
301
383
  }
@@ -377,21 +459,33 @@ class AwsIamKubernetesAuthTranslator {
377
459
  return `k8s-aws-v1.${urlSafeBase64Url}`;
378
460
  }
379
461
  async decorateClusterDetailsWithAuth(clusterDetails) {
380
- const clusterDetailsWithAuthToken = Object.assign({}, clusterDetails);
381
- clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken(clusterDetails.name, clusterDetails.assumeRole, clusterDetails.externalId);
462
+ const clusterDetailsWithAuthToken = Object.assign(
463
+ {},
464
+ clusterDetails
465
+ );
466
+ clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken(
467
+ clusterDetails.name,
468
+ clusterDetails.assumeRole,
469
+ clusterDetails.externalId
470
+ );
382
471
  return clusterDetailsWithAuthToken;
383
472
  }
384
473
  }
385
474
 
386
475
  class GoogleServiceAccountAuthTranslator {
387
476
  async decorateClusterDetailsWithAuth(clusterDetails) {
388
- const clusterDetailsWithAuthToken = Object.assign({}, clusterDetails);
477
+ const clusterDetailsWithAuthToken = Object.assign(
478
+ {},
479
+ clusterDetails
480
+ );
389
481
  const client = new container__namespace.v1.ClusterManagerClient();
390
482
  const accessToken = await client.auth.getAccessToken();
391
483
  if (accessToken) {
392
484
  clusterDetailsWithAuthToken.serviceAccountToken = accessToken;
393
485
  } else {
394
- throw new Error("Unable to obtain access token for the current Google Application Default Credentials");
486
+ throw new Error(
487
+ "Unable to obtain access token for the current Google Application Default Credentials"
488
+ );
395
489
  }
396
490
  return clusterDetailsWithAuthToken;
397
491
  }
@@ -405,7 +499,10 @@ class AzureIdentityKubernetesAuthTranslator {
405
499
  this.accessToken = { token: "", expiresOnTimestamp: 0 };
406
500
  }
407
501
  async decorateClusterDetailsWithAuth(clusterDetails) {
408
- const clusterDetailsWithAuthToken = Object.assign({}, clusterDetails);
502
+ const clusterDetailsWithAuthToken = Object.assign(
503
+ {},
504
+ clusterDetails
505
+ );
409
506
  clusterDetailsWithAuthToken.serviceAccountToken = await this.getToken();
410
507
  return clusterDetailsWithAuthToken;
411
508
  }
@@ -449,16 +546,23 @@ class AzureIdentityKubernetesAuthTranslator {
449
546
  class OidcKubernetesAuthTranslator {
450
547
  async decorateClusterDetailsWithAuth(clusterDetails, authConfig) {
451
548
  var _a;
452
- const clusterDetailsWithAuthToken = Object.assign({}, clusterDetails);
549
+ const clusterDetailsWithAuthToken = Object.assign(
550
+ {},
551
+ clusterDetails
552
+ );
453
553
  const { oidcTokenProvider } = clusterDetails;
454
554
  if (!oidcTokenProvider || oidcTokenProvider === "") {
455
- throw new Error(`oidc authProvider requires a configured oidcTokenProvider`);
555
+ throw new Error(
556
+ `oidc authProvider requires a configured oidcTokenProvider`
557
+ );
456
558
  }
457
559
  const authToken = (_a = authConfig.oidc) == null ? void 0 : _a[oidcTokenProvider];
458
560
  if (authToken) {
459
561
  clusterDetailsWithAuthToken.serviceAccountToken = authToken;
460
562
  } else {
461
- throw new Error(`Auth token not found under oidc.${oidcTokenProvider} in request body`);
563
+ throw new Error(
564
+ `Auth token not found under oidc.${oidcTokenProvider} in request body`
565
+ );
462
566
  }
463
567
  return clusterDetailsWithAuthToken;
464
568
  }
@@ -485,8 +589,13 @@ class KubernetesAuthTranslatorGenerator {
485
589
  case "oidc": {
486
590
  return new OidcKubernetesAuthTranslator();
487
591
  }
592
+ case "localKubectlProxy": {
593
+ return new NoopKubernetesAuthTranslator();
594
+ }
488
595
  default: {
489
- throw new Error(`authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`);
596
+ throw new Error(
597
+ `authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`
598
+ );
490
599
  }
491
600
  }
492
601
  }
@@ -609,45 +718,66 @@ class KubernetesFanOutHandler {
609
718
  auth,
610
719
  customResources
611
720
  }) {
612
- return this.fanOutRequests(entity, auth, /* @__PURE__ */ new Set(), customResources);
721
+ return this.fanOutRequests(
722
+ entity,
723
+ auth,
724
+ /* @__PURE__ */ new Set(),
725
+ customResources
726
+ );
613
727
  }
614
728
  async getKubernetesObjectsByEntity({
615
729
  entity,
616
730
  auth
617
731
  }) {
618
- return this.fanOutRequests(entity, auth, this.objectTypesToFetch, this.customResources);
732
+ return this.fanOutRequests(
733
+ entity,
734
+ auth,
735
+ this.objectTypesToFetch,
736
+ this.customResources
737
+ );
619
738
  }
620
739
  async fanOutRequests(entity, auth, objectTypesToFetch, customResources) {
621
740
  var _a, _b, _c, _d, _e, _f, _g;
622
741
  const entityName = ((_b = (_a = entity.metadata) == null ? void 0 : _a.annotations) == null ? void 0 : _b["backstage.io/kubernetes-id"]) || ((_c = entity.metadata) == null ? void 0 : _c.name);
623
742
  const clusterDetailsDecoratedForAuth = await this.decorateClusterDetailsWithAuth(entity, auth);
624
- this.logger.info(`entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth.map((c) => c.name).join(", ")}]`);
743
+ this.logger.info(
744
+ `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth.map((c) => c.name).join(", ")}]`
745
+ );
625
746
  const labelSelector = ((_e = (_d = entity.metadata) == null ? void 0 : _d.annotations) == null ? void 0 : _e["backstage.io/kubernetes-label-selector"]) || `backstage.io/kubernetes-id=${entityName}`;
626
747
  const namespace = (_g = (_f = entity.metadata) == null ? void 0 : _f.annotations) == null ? void 0 : _g["backstage.io/kubernetes-namespace"];
627
- return Promise.all(clusterDetailsDecoratedForAuth.map((clusterDetailsItem) => {
628
- return this.fetcher.fetchObjectsForService({
629
- serviceId: entityName,
630
- clusterDetails: clusterDetailsItem,
631
- objectTypesToFetch,
632
- labelSelector,
633
- customResources: customResources.map((c) => ({
634
- ...c,
635
- objectType: "customresources"
636
- })),
637
- namespace
638
- }).then((result) => this.getMetricsForPods(clusterDetailsItem, result)).then((r) => this.toClusterObjects(clusterDetailsItem, r));
639
- })).then(this.toObjectsByEntityResponse);
748
+ return Promise.all(
749
+ clusterDetailsDecoratedForAuth.map((clusterDetailsItem) => {
750
+ return this.fetcher.fetchObjectsForService({
751
+ serviceId: entityName,
752
+ clusterDetails: clusterDetailsItem,
753
+ objectTypesToFetch,
754
+ labelSelector,
755
+ customResources: customResources.map((c) => ({
756
+ ...c,
757
+ objectType: "customresources"
758
+ })),
759
+ namespace
760
+ }).then((result) => this.getMetricsForPods(clusterDetailsItem, result)).then((r) => this.toClusterObjects(clusterDetailsItem, r));
761
+ })
762
+ ).then(this.toObjectsByEntityResponse);
640
763
  }
641
764
  async decorateClusterDetailsWithAuth(entity, auth) {
642
765
  const clusterDetails = await (await this.serviceLocator.getClustersByEntity(entity)).clusters;
643
- return await Promise.all(clusterDetails.map((cd) => {
644
- const kubernetesAuthTranslator = this.getAuthTranslator(cd.authProvider);
645
- return kubernetesAuthTranslator.decorateClusterDetailsWithAuth(cd, auth);
646
- }));
766
+ return await Promise.all(
767
+ clusterDetails.map((cd) => {
768
+ const kubernetesAuthTranslator = this.getAuthTranslator(cd.authProvider);
769
+ return kubernetesAuthTranslator.decorateClusterDetailsWithAuth(
770
+ cd,
771
+ auth
772
+ );
773
+ })
774
+ );
647
775
  }
648
776
  toObjectsByEntityResponse(clusterObjects) {
649
777
  return {
650
- items: clusterObjects.filter((item) => item.errors !== void 0 && item.errors.length >= 1 || item.resources !== void 0 && item.resources.length >= 1 && item.resources.some((fr) => fr.resources.length >= 1))
778
+ items: clusterObjects.filter(
779
+ (item) => item.errors !== void 0 && item.errors.length >= 1 || item.resources !== void 0 && item.resources.length >= 1 && item.resources.some((fr) => fr.resources.length >= 1)
780
+ )
651
781
  };
652
782
  }
653
783
  toClusterObjects(clusterDetails, [result, metrics]) {
@@ -674,20 +804,27 @@ class KubernetesFanOutHandler {
674
804
  if (clusterDetails.skipMetricsLookup) {
675
805
  return [result, []];
676
806
  }
677
- const namespaces = new Set(result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => {
678
- var _a;
679
- return (_a = p.metadata) == null ? void 0 : _a.namespace;
680
- }).filter(isString));
681
- const podMetrics = Array.from(namespaces).map((ns) => this.fetcher.fetchPodMetricsByNamespace(clusterDetails, ns));
807
+ const namespaces = new Set(
808
+ result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => {
809
+ var _a;
810
+ return (_a = p.metadata) == null ? void 0 : _a.namespace;
811
+ }).filter(isString)
812
+ );
813
+ const podMetrics = Array.from(namespaces).map(
814
+ (ns) => this.fetcher.fetchPodMetricsByNamespace(clusterDetails, ns)
815
+ );
682
816
  return Promise.all([result, Promise.all(podMetrics)]);
683
817
  }
684
818
  getAuthTranslator(provider) {
685
819
  if (this.authTranslators[provider]) {
686
820
  return this.authTranslators[provider];
687
821
  }
688
- this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(provider, {
689
- logger: this.logger
690
- });
822
+ this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(
823
+ provider,
824
+ {
825
+ logger: this.logger
826
+ }
827
+ );
691
828
  return this.authTranslators[provider];
692
829
  }
693
830
  }
@@ -725,18 +862,28 @@ class KubernetesClientBasedFetcher {
725
862
  }
726
863
  fetchObjectsForService(params) {
727
864
  const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map((toFetch) => {
728
- return this.fetchResource(params.clusterDetails, toFetch, params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`, toFetch.objectType, params.namespace).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));
865
+ return this.fetchResource(
866
+ params.clusterDetails,
867
+ toFetch,
868
+ params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`,
869
+ toFetch.objectType,
870
+ params.namespace
871
+ ).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));
729
872
  });
730
873
  return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
731
874
  }
732
875
  fetchPodMetricsByNamespace(clusterDetails, namespace) {
733
876
  const metricsClient = this.kubernetesClientProvider.getMetricsClient(clusterDetails);
734
- const coreApi = this.kubernetesClientProvider.getCoreClientByClusterDetails(clusterDetails);
877
+ const coreApi = this.kubernetesClientProvider.getCoreClientByClusterDetails(
878
+ clusterDetails
879
+ );
735
880
  return clientNode.topPods(coreApi, metricsClient, namespace);
736
881
  }
737
882
  captureKubernetesErrorsRethrowOthers(e) {
738
883
  if (e.response && e.response.statusCode) {
739
- this.logger.warn(`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname} body=[${JSON.stringify(e.response.body)}]`);
884
+ this.logger.warn(
885
+ `statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname} body=[${JSON.stringify(e.response.body)}]`
886
+ );
740
887
  return {
741
888
  errorType: statusCodeToErrorType(e.response.statusCode),
742
889
  statusCode: e.response.statusCode,
@@ -751,14 +898,33 @@ class KubernetesClientBasedFetcher {
751
898
  requestOptions.uri = requestOptions.uri.replace("/apis//v1/", "/api/v1/");
752
899
  });
753
900
  if (namespace) {
754
- return customObjects.listNamespacedCustomObject(resource.group, resource.apiVersion, namespace, resource.plural, "", false, "", "", labelSelector).then((r) => {
901
+ return customObjects.listNamespacedCustomObject(
902
+ resource.group,
903
+ resource.apiVersion,
904
+ namespace,
905
+ resource.plural,
906
+ "",
907
+ false,
908
+ "",
909
+ "",
910
+ labelSelector
911
+ ).then((r) => {
755
912
  return {
756
913
  type: objectType,
757
914
  resources: r.body.items
758
915
  };
759
916
  });
760
917
  }
761
- return customObjects.listClusterCustomObject(resource.group, resource.apiVersion, resource.plural, "", false, "", "", labelSelector).then((r) => {
918
+ return customObjects.listClusterCustomObject(
919
+ resource.group,
920
+ resource.apiVersion,
921
+ resource.plural,
922
+ "",
923
+ false,
924
+ "",
925
+ "",
926
+ labelSelector
927
+ ).then((r) => {
762
928
  return {
763
929
  type: objectType,
764
930
  resources: r.body.items
@@ -767,6 +933,62 @@ class KubernetesClientBasedFetcher {
767
933
  }
768
934
  }
769
935
 
936
+ const addResourceRoutesToRouter = (router, catalogApi, objectsProvider) => {
937
+ const getEntityByReq = async (req) => {
938
+ const rawEntityRef = req.body.entityRef;
939
+ if (rawEntityRef && typeof rawEntityRef !== "string") {
940
+ throw new errors.InputError(`entity query must be a string`);
941
+ } else if (!rawEntityRef) {
942
+ throw new errors.InputError("entity is a required field");
943
+ }
944
+ let entityRef = void 0;
945
+ try {
946
+ entityRef = catalogModel.parseEntityRef(rawEntityRef);
947
+ } catch (error) {
948
+ throw new errors.InputError(`Invalid entity ref, ${error}`);
949
+ }
950
+ const token = pluginAuthNode.getBearerTokenFromAuthorizationHeader(
951
+ req.headers.authorization
952
+ );
953
+ if (!token) {
954
+ throw new errors.AuthenticationError("No Backstage token");
955
+ }
956
+ const entity = await catalogApi.getEntityByRef(entityRef, {
957
+ token
958
+ });
959
+ if (!entity) {
960
+ throw new errors.InputError(
961
+ `Entity ref missing, ${catalogModel.stringifyEntityRef(entityRef)}`
962
+ );
963
+ }
964
+ return entity;
965
+ };
966
+ router.post("/resources/workloads/query", async (req, res) => {
967
+ const entity = await getEntityByReq(req);
968
+ const response = await objectsProvider.getKubernetesObjectsByEntity({
969
+ entity,
970
+ auth: req.body.auth
971
+ });
972
+ res.json(response);
973
+ });
974
+ router.post("/resources/custom/query", async (req, res) => {
975
+ const entity = await getEntityByReq(req);
976
+ if (!req.body.customResources) {
977
+ throw new errors.InputError("customResources is a required field");
978
+ } else if (!Array.isArray(req.body.customResources)) {
979
+ throw new errors.InputError("customResources must be an array");
980
+ } else if (req.body.customResources.length === 0) {
981
+ throw new errors.InputError("at least 1 customResource is required");
982
+ }
983
+ const response = await objectsProvider.getCustomResourcesByEntity({
984
+ entity,
985
+ customResources: req.body.customResources,
986
+ auth: req.body.auth
987
+ });
988
+ res.json(response);
989
+ });
990
+ };
991
+
770
992
  class KubernetesBuilder {
771
993
  constructor(env) {
772
994
  this.env = env;
@@ -786,7 +1008,9 @@ class KubernetesBuilder {
786
1008
  if (process.env.NODE_ENV !== "development") {
787
1009
  throw new Error("Kubernetes configuration is missing");
788
1010
  }
789
- logger.warn("Failed to initialize kubernetes backend: kubernetes config is missing");
1011
+ logger.warn(
1012
+ "Failed to initialize kubernetes backend: kubernetes config is missing"
1013
+ );
790
1014
  return {
791
1015
  router: Router__default["default"]()
792
1016
  };
@@ -802,7 +1026,11 @@ class KubernetesBuilder {
802
1026
  customResources,
803
1027
  objectTypesToFetch: this.getObjectTypesToFetch()
804
1028
  });
805
- const router = this.buildRouter(objectsProvider, clusterSupplier);
1029
+ const router = this.buildRouter(
1030
+ objectsProvider,
1031
+ clusterSupplier,
1032
+ this.env.catalogApi
1033
+ );
806
1034
  return {
807
1035
  clusterSupplier,
808
1036
  customResources,
@@ -834,18 +1062,26 @@ class KubernetesBuilder {
834
1062
  }
835
1063
  buildCustomResources() {
836
1064
  var _a;
837
- const customResources = ((_a = this.env.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map((c) => ({
838
- group: c.getString("group"),
839
- apiVersion: c.getString("apiVersion"),
840
- plural: c.getString("plural"),
841
- objectType: "customresources"
842
- }));
843
- this.env.logger.info(`action=LoadingCustomResources numOfCustomResources=${customResources.length}`);
1065
+ const customResources = ((_a = this.env.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map(
1066
+ (c) => ({
1067
+ group: c.getString("group"),
1068
+ apiVersion: c.getString("apiVersion"),
1069
+ plural: c.getString("plural"),
1070
+ objectType: "customresources"
1071
+ })
1072
+ );
1073
+ this.env.logger.info(
1074
+ `action=LoadingCustomResources numOfCustomResources=${customResources.length}`
1075
+ );
844
1076
  return customResources;
845
1077
  }
846
1078
  buildClusterSupplier(refreshInterval) {
847
1079
  const config = this.env.config;
848
- return getCombinedClusterSupplier(config, refreshInterval);
1080
+ return getCombinedClusterSupplier(
1081
+ config,
1082
+ this.env.catalogApi,
1083
+ refreshInterval
1084
+ );
849
1085
  }
850
1086
  buildObjectsProvider(options) {
851
1087
  return new KubernetesFanOutHandler(options);
@@ -863,7 +1099,9 @@ class KubernetesBuilder {
863
1099
  case "http":
864
1100
  return this.buildHttpServiceLocator(clusterSupplier);
865
1101
  default:
866
- throw new Error(`Unsupported kubernetes.clusterLocatorMethod "${method}"`);
1102
+ throw new Error(
1103
+ `Unsupported kubernetes.clusterLocatorMethod "${method}"`
1104
+ );
867
1105
  }
868
1106
  }
869
1107
  buildMultiTenantServiceLocator(clusterSupplier) {
@@ -872,7 +1110,7 @@ class KubernetesBuilder {
872
1110
  buildHttpServiceLocator(_clusterSupplier) {
873
1111
  throw new Error("not implemented");
874
1112
  }
875
- buildRouter(objectsProvider, clusterSupplier) {
1113
+ buildRouter(objectsProvider, clusterSupplier, catalogApi) {
876
1114
  const logger = this.env.logger;
877
1115
  const router = Router__default["default"]();
878
1116
  router.use(express__default["default"].json());
@@ -886,7 +1124,9 @@ class KubernetesBuilder {
886
1124
  });
887
1125
  res.json(response);
888
1126
  } catch (e) {
889
- logger.error(`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`);
1127
+ logger.error(
1128
+ `action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`
1129
+ );
890
1130
  res.status(500).json({ error: e.message });
891
1131
  }
892
1132
  });
@@ -901,22 +1141,33 @@ class KubernetesBuilder {
901
1141
  }))
902
1142
  });
903
1143
  });
1144
+ addResourceRoutesToRouter(router, catalogApi, objectsProvider);
904
1145
  return router;
905
1146
  }
906
1147
  async fetchClusterDetails(clusterSupplier) {
907
1148
  const clusterDetails = await clusterSupplier.getClusters();
908
- this.env.logger.info(`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`);
1149
+ this.env.logger.info(
1150
+ `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`
1151
+ );
909
1152
  return clusterDetails;
910
1153
  }
911
1154
  getServiceLocatorMethod() {
912
- return this.env.config.getString("kubernetes.serviceLocatorMethod.type");
1155
+ return this.env.config.getString(
1156
+ "kubernetes.serviceLocatorMethod.type"
1157
+ );
913
1158
  }
914
1159
  getObjectTypesToFetch() {
915
- const objectTypesToFetchStrings = this.env.config.getOptionalStringArray("kubernetes.objectTypes");
916
- const apiVersionOverrides = this.env.config.getOptionalConfig("kubernetes.apiVersionOverrides");
1160
+ const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(
1161
+ "kubernetes.objectTypes"
1162
+ );
1163
+ const apiVersionOverrides = this.env.config.getOptionalConfig(
1164
+ "kubernetes.apiVersionOverrides"
1165
+ );
917
1166
  let objectTypesToFetch;
918
1167
  if (objectTypesToFetchStrings) {
919
- objectTypesToFetch = DEFAULT_OBJECTS.filter((obj) => objectTypesToFetchStrings.includes(obj.objectType));
1168
+ objectTypesToFetch = DEFAULT_OBJECTS.filter(
1169
+ (obj) => objectTypesToFetchStrings.includes(obj.objectType)
1170
+ );
920
1171
  }
921
1172
  if (apiVersionOverrides) {
922
1173
  objectTypesToFetch = objectTypesToFetch != null ? objectTypesToFetch : DEFAULT_OBJECTS;