@backstage/plugin-kubernetes-backend 0.7.0-next.3 → 0.7.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/dist/index.cjs.js +288 -118
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/package.json +10 -10
package/dist/index.cjs.js
CHANGED
|
@@ -7,12 +7,13 @@ 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');
|
|
15
|
-
var catalogModel = require('@backstage/catalog-model');
|
|
16
17
|
var pluginAuthNode = require('@backstage/plugin-auth-node');
|
|
17
18
|
|
|
18
19
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -46,56 +47,60 @@ class ConfigClusterLocator {
|
|
|
46
47
|
this.clusterDetails = clusterDetails;
|
|
47
48
|
}
|
|
48
49
|
static fromConfig(config) {
|
|
49
|
-
return new ConfigClusterLocator(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const dashboardApp = c.getOptionalString("dashboardApp");
|
|
66
|
-
if (dashboardApp) {
|
|
67
|
-
clusterDetails.dashboardApp = dashboardApp;
|
|
68
|
-
}
|
|
69
|
-
if (c.has("dashboardParameters")) {
|
|
70
|
-
clusterDetails.dashboardParameters = c.get("dashboardParameters");
|
|
71
|
-
}
|
|
72
|
-
switch (authProvider) {
|
|
73
|
-
case "google": {
|
|
74
|
-
return clusterDetails;
|
|
75
|
-
}
|
|
76
|
-
case "aws": {
|
|
77
|
-
const assumeRole = c.getOptionalString("assumeRole");
|
|
78
|
-
const externalId = c.getOptionalString("externalId");
|
|
79
|
-
return { assumeRole, externalId, ...clusterDetails };
|
|
80
|
-
}
|
|
81
|
-
case "azure": {
|
|
82
|
-
return clusterDetails;
|
|
83
|
-
}
|
|
84
|
-
case "oidc": {
|
|
85
|
-
const oidcTokenProvider = c.getString("oidcTokenProvider");
|
|
86
|
-
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;
|
|
87
66
|
}
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
const dashboardApp = c.getOptionalString("dashboardApp");
|
|
68
|
+
if (dashboardApp) {
|
|
69
|
+
clusterDetails.dashboardApp = dashboardApp;
|
|
90
70
|
}
|
|
91
|
-
|
|
92
|
-
|
|
71
|
+
if (c.has("dashboardParameters")) {
|
|
72
|
+
clusterDetails.dashboardParameters = c.get("dashboardParameters");
|
|
93
73
|
}
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
}
|
|
96
101
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
102
|
+
})
|
|
103
|
+
);
|
|
99
104
|
}
|
|
100
105
|
async getClusters() {
|
|
101
106
|
return this.clusterDetails;
|
|
@@ -149,12 +154,19 @@ class GkeClusterLocator {
|
|
|
149
154
|
};
|
|
150
155
|
const gkeClusterLocator = new GkeClusterLocator(options, client);
|
|
151
156
|
if (refreshInterval) {
|
|
152
|
-
runPeriodically(
|
|
157
|
+
runPeriodically(
|
|
158
|
+
() => gkeClusterLocator.refreshClusters(),
|
|
159
|
+
refreshInterval.toMillis()
|
|
160
|
+
);
|
|
153
161
|
}
|
|
154
162
|
return gkeClusterLocator;
|
|
155
163
|
}
|
|
156
164
|
static fromConfig(config, refreshInterval = void 0) {
|
|
157
|
-
return GkeClusterLocator.fromConfigWithClient(
|
|
165
|
+
return GkeClusterLocator.fromConfigWithClient(
|
|
166
|
+
config,
|
|
167
|
+
new container__namespace.v1.ClusterManagerClient(),
|
|
168
|
+
refreshInterval
|
|
169
|
+
);
|
|
158
170
|
}
|
|
159
171
|
async getClusters() {
|
|
160
172
|
var _a;
|
|
@@ -205,11 +217,47 @@ class GkeClusterLocator {
|
|
|
205
217
|
});
|
|
206
218
|
this.hasClusterDetails = true;
|
|
207
219
|
} catch (e) {
|
|
208
|
-
throw new errors.ForwardedError(
|
|
220
|
+
throw new errors.ForwardedError(
|
|
221
|
+
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
|
|
222
|
+
e
|
|
223
|
+
);
|
|
209
224
|
}
|
|
210
225
|
}
|
|
211
226
|
}
|
|
212
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
|
+
|
|
213
261
|
class LocalKubectlProxyClusterLocator {
|
|
214
262
|
constructor() {
|
|
215
263
|
this.clusterDetails = [
|
|
@@ -231,25 +279,34 @@ class CombinedClustersSupplier {
|
|
|
231
279
|
this.clusterSuppliers = clusterSuppliers;
|
|
232
280
|
}
|
|
233
281
|
async getClusters() {
|
|
234
|
-
return await Promise.all(
|
|
282
|
+
return await Promise.all(
|
|
283
|
+
this.clusterSuppliers.map((supplier) => supplier.getClusters())
|
|
284
|
+
).then((res) => {
|
|
235
285
|
return res.flat();
|
|
236
286
|
}).catch((e) => {
|
|
237
287
|
throw e;
|
|
238
288
|
});
|
|
239
289
|
}
|
|
240
290
|
}
|
|
241
|
-
const getCombinedClusterSupplier = (rootConfig, refreshInterval = void 0) => {
|
|
291
|
+
const getCombinedClusterSupplier = (rootConfig, catalogClient, refreshInterval = void 0) => {
|
|
242
292
|
const clusterSuppliers = rootConfig.getConfigArray("kubernetes.clusterLocatorMethods").map((clusterLocatorMethod) => {
|
|
243
293
|
const type = clusterLocatorMethod.getString("type");
|
|
244
294
|
switch (type) {
|
|
295
|
+
case "catalog":
|
|
296
|
+
return CatalogClusterLocator.fromConfig(catalogClient);
|
|
245
297
|
case "localKubectlProxy":
|
|
246
298
|
return new LocalKubectlProxyClusterLocator();
|
|
247
299
|
case "config":
|
|
248
300
|
return ConfigClusterLocator.fromConfig(clusterLocatorMethod);
|
|
249
301
|
case "gke":
|
|
250
|
-
return GkeClusterLocator.fromConfig(
|
|
302
|
+
return GkeClusterLocator.fromConfig(
|
|
303
|
+
clusterLocatorMethod,
|
|
304
|
+
refreshInterval
|
|
305
|
+
);
|
|
251
306
|
default:
|
|
252
|
-
throw new Error(
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Unsupported kubernetes.clusterLocatorMethods: "${type}"`
|
|
309
|
+
);
|
|
253
310
|
}
|
|
254
311
|
});
|
|
255
312
|
return new CombinedClustersSupplier(clusterSuppliers);
|
|
@@ -310,12 +367,17 @@ class KubernetesClientProvider {
|
|
|
310
367
|
|
|
311
368
|
class GoogleKubernetesAuthTranslator {
|
|
312
369
|
async decorateClusterDetailsWithAuth(clusterDetails, authConfig) {
|
|
313
|
-
const clusterDetailsWithAuthToken = Object.assign(
|
|
370
|
+
const clusterDetailsWithAuthToken = Object.assign(
|
|
371
|
+
{},
|
|
372
|
+
clusterDetails
|
|
373
|
+
);
|
|
314
374
|
const authToken = authConfig.google;
|
|
315
375
|
if (authToken) {
|
|
316
376
|
clusterDetailsWithAuthToken.serviceAccountToken = authToken;
|
|
317
377
|
} else {
|
|
318
|
-
throw new Error(
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Google token not found under auth.google in request body"
|
|
380
|
+
);
|
|
319
381
|
}
|
|
320
382
|
return clusterDetailsWithAuthToken;
|
|
321
383
|
}
|
|
@@ -397,21 +459,33 @@ class AwsIamKubernetesAuthTranslator {
|
|
|
397
459
|
return `k8s-aws-v1.${urlSafeBase64Url}`;
|
|
398
460
|
}
|
|
399
461
|
async decorateClusterDetailsWithAuth(clusterDetails) {
|
|
400
|
-
const clusterDetailsWithAuthToken = Object.assign(
|
|
401
|
-
|
|
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
|
+
);
|
|
402
471
|
return clusterDetailsWithAuthToken;
|
|
403
472
|
}
|
|
404
473
|
}
|
|
405
474
|
|
|
406
475
|
class GoogleServiceAccountAuthTranslator {
|
|
407
476
|
async decorateClusterDetailsWithAuth(clusterDetails) {
|
|
408
|
-
const clusterDetailsWithAuthToken = Object.assign(
|
|
477
|
+
const clusterDetailsWithAuthToken = Object.assign(
|
|
478
|
+
{},
|
|
479
|
+
clusterDetails
|
|
480
|
+
);
|
|
409
481
|
const client = new container__namespace.v1.ClusterManagerClient();
|
|
410
482
|
const accessToken = await client.auth.getAccessToken();
|
|
411
483
|
if (accessToken) {
|
|
412
484
|
clusterDetailsWithAuthToken.serviceAccountToken = accessToken;
|
|
413
485
|
} else {
|
|
414
|
-
throw new Error(
|
|
486
|
+
throw new Error(
|
|
487
|
+
"Unable to obtain access token for the current Google Application Default Credentials"
|
|
488
|
+
);
|
|
415
489
|
}
|
|
416
490
|
return clusterDetailsWithAuthToken;
|
|
417
491
|
}
|
|
@@ -425,7 +499,10 @@ class AzureIdentityKubernetesAuthTranslator {
|
|
|
425
499
|
this.accessToken = { token: "", expiresOnTimestamp: 0 };
|
|
426
500
|
}
|
|
427
501
|
async decorateClusterDetailsWithAuth(clusterDetails) {
|
|
428
|
-
const clusterDetailsWithAuthToken = Object.assign(
|
|
502
|
+
const clusterDetailsWithAuthToken = Object.assign(
|
|
503
|
+
{},
|
|
504
|
+
clusterDetails
|
|
505
|
+
);
|
|
429
506
|
clusterDetailsWithAuthToken.serviceAccountToken = await this.getToken();
|
|
430
507
|
return clusterDetailsWithAuthToken;
|
|
431
508
|
}
|
|
@@ -469,16 +546,23 @@ class AzureIdentityKubernetesAuthTranslator {
|
|
|
469
546
|
class OidcKubernetesAuthTranslator {
|
|
470
547
|
async decorateClusterDetailsWithAuth(clusterDetails, authConfig) {
|
|
471
548
|
var _a;
|
|
472
|
-
const clusterDetailsWithAuthToken = Object.assign(
|
|
549
|
+
const clusterDetailsWithAuthToken = Object.assign(
|
|
550
|
+
{},
|
|
551
|
+
clusterDetails
|
|
552
|
+
);
|
|
473
553
|
const { oidcTokenProvider } = clusterDetails;
|
|
474
554
|
if (!oidcTokenProvider || oidcTokenProvider === "") {
|
|
475
|
-
throw new Error(
|
|
555
|
+
throw new Error(
|
|
556
|
+
`oidc authProvider requires a configured oidcTokenProvider`
|
|
557
|
+
);
|
|
476
558
|
}
|
|
477
559
|
const authToken = (_a = authConfig.oidc) == null ? void 0 : _a[oidcTokenProvider];
|
|
478
560
|
if (authToken) {
|
|
479
561
|
clusterDetailsWithAuthToken.serviceAccountToken = authToken;
|
|
480
562
|
} else {
|
|
481
|
-
throw new Error(
|
|
563
|
+
throw new Error(
|
|
564
|
+
`Auth token not found under oidc.${oidcTokenProvider} in request body`
|
|
565
|
+
);
|
|
482
566
|
}
|
|
483
567
|
return clusterDetailsWithAuthToken;
|
|
484
568
|
}
|
|
@@ -509,7 +593,9 @@ class KubernetesAuthTranslatorGenerator {
|
|
|
509
593
|
return new NoopKubernetesAuthTranslator();
|
|
510
594
|
}
|
|
511
595
|
default: {
|
|
512
|
-
throw new Error(
|
|
596
|
+
throw new Error(
|
|
597
|
+
`authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`
|
|
598
|
+
);
|
|
513
599
|
}
|
|
514
600
|
}
|
|
515
601
|
}
|
|
@@ -632,45 +718,61 @@ class KubernetesFanOutHandler {
|
|
|
632
718
|
auth,
|
|
633
719
|
customResources
|
|
634
720
|
}) {
|
|
635
|
-
return this.fanOutRequests(
|
|
721
|
+
return this.fanOutRequests(
|
|
722
|
+
entity,
|
|
723
|
+
auth,
|
|
724
|
+
/* @__PURE__ */ new Set(),
|
|
725
|
+
customResources
|
|
726
|
+
);
|
|
636
727
|
}
|
|
637
728
|
async getKubernetesObjectsByEntity({
|
|
638
729
|
entity,
|
|
639
730
|
auth
|
|
640
731
|
}) {
|
|
641
|
-
return this.fanOutRequests(entity, auth, this.objectTypesToFetch
|
|
732
|
+
return this.fanOutRequests(entity, auth, this.objectTypesToFetch);
|
|
642
733
|
}
|
|
643
734
|
async fanOutRequests(entity, auth, objectTypesToFetch, customResources) {
|
|
644
735
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
645
736
|
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);
|
|
646
737
|
const clusterDetailsDecoratedForAuth = await this.decorateClusterDetailsWithAuth(entity, auth);
|
|
647
|
-
this.logger.info(
|
|
738
|
+
this.logger.info(
|
|
739
|
+
`entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth.map((c) => c.name).join(", ")}]`
|
|
740
|
+
);
|
|
648
741
|
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}`;
|
|
649
742
|
const namespace = (_g = (_f = entity.metadata) == null ? void 0 : _f.annotations) == null ? void 0 : _g["backstage.io/kubernetes-namespace"];
|
|
650
|
-
return Promise.all(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
743
|
+
return Promise.all(
|
|
744
|
+
clusterDetailsDecoratedForAuth.map((clusterDetailsItem) => {
|
|
745
|
+
return this.fetcher.fetchObjectsForService({
|
|
746
|
+
serviceId: entityName,
|
|
747
|
+
clusterDetails: clusterDetailsItem,
|
|
748
|
+
objectTypesToFetch,
|
|
749
|
+
labelSelector,
|
|
750
|
+
customResources: (customResources || clusterDetailsItem.customResources || this.customResources).map((c) => ({
|
|
751
|
+
...c,
|
|
752
|
+
objectType: "customresources"
|
|
753
|
+
})),
|
|
754
|
+
namespace
|
|
755
|
+
}).then((result) => this.getMetricsForPods(clusterDetailsItem, result)).then((r) => this.toClusterObjects(clusterDetailsItem, r));
|
|
756
|
+
})
|
|
757
|
+
).then(this.toObjectsByEntityResponse);
|
|
663
758
|
}
|
|
664
759
|
async decorateClusterDetailsWithAuth(entity, auth) {
|
|
665
760
|
const clusterDetails = await (await this.serviceLocator.getClustersByEntity(entity)).clusters;
|
|
666
|
-
return await Promise.all(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
761
|
+
return await Promise.all(
|
|
762
|
+
clusterDetails.map((cd) => {
|
|
763
|
+
const kubernetesAuthTranslator = this.getAuthTranslator(cd.authProvider);
|
|
764
|
+
return kubernetesAuthTranslator.decorateClusterDetailsWithAuth(
|
|
765
|
+
cd,
|
|
766
|
+
auth
|
|
767
|
+
);
|
|
768
|
+
})
|
|
769
|
+
);
|
|
670
770
|
}
|
|
671
771
|
toObjectsByEntityResponse(clusterObjects) {
|
|
672
772
|
return {
|
|
673
|
-
items: clusterObjects.filter(
|
|
773
|
+
items: clusterObjects.filter(
|
|
774
|
+
(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)
|
|
775
|
+
)
|
|
674
776
|
};
|
|
675
777
|
}
|
|
676
778
|
toClusterObjects(clusterDetails, [result, metrics]) {
|
|
@@ -697,20 +799,27 @@ class KubernetesFanOutHandler {
|
|
|
697
799
|
if (clusterDetails.skipMetricsLookup) {
|
|
698
800
|
return [result, []];
|
|
699
801
|
}
|
|
700
|
-
const namespaces = new Set(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
802
|
+
const namespaces = new Set(
|
|
803
|
+
result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => {
|
|
804
|
+
var _a;
|
|
805
|
+
return (_a = p.metadata) == null ? void 0 : _a.namespace;
|
|
806
|
+
}).filter(isString)
|
|
807
|
+
);
|
|
808
|
+
const podMetrics = Array.from(namespaces).map(
|
|
809
|
+
(ns) => this.fetcher.fetchPodMetricsByNamespace(clusterDetails, ns)
|
|
810
|
+
);
|
|
705
811
|
return Promise.all([result, Promise.all(podMetrics)]);
|
|
706
812
|
}
|
|
707
813
|
getAuthTranslator(provider) {
|
|
708
814
|
if (this.authTranslators[provider]) {
|
|
709
815
|
return this.authTranslators[provider];
|
|
710
816
|
}
|
|
711
|
-
this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(
|
|
712
|
-
|
|
713
|
-
|
|
817
|
+
this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance(
|
|
818
|
+
provider,
|
|
819
|
+
{
|
|
820
|
+
logger: this.logger
|
|
821
|
+
}
|
|
822
|
+
);
|
|
714
823
|
return this.authTranslators[provider];
|
|
715
824
|
}
|
|
716
825
|
}
|
|
@@ -748,18 +857,28 @@ class KubernetesClientBasedFetcher {
|
|
|
748
857
|
}
|
|
749
858
|
fetchObjectsForService(params) {
|
|
750
859
|
const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map((toFetch) => {
|
|
751
|
-
return this.fetchResource(
|
|
860
|
+
return this.fetchResource(
|
|
861
|
+
params.clusterDetails,
|
|
862
|
+
toFetch,
|
|
863
|
+
params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`,
|
|
864
|
+
toFetch.objectType,
|
|
865
|
+
params.namespace
|
|
866
|
+
).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));
|
|
752
867
|
});
|
|
753
868
|
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
|
|
754
869
|
}
|
|
755
870
|
fetchPodMetricsByNamespace(clusterDetails, namespace) {
|
|
756
871
|
const metricsClient = this.kubernetesClientProvider.getMetricsClient(clusterDetails);
|
|
757
|
-
const coreApi = this.kubernetesClientProvider.getCoreClientByClusterDetails(
|
|
872
|
+
const coreApi = this.kubernetesClientProvider.getCoreClientByClusterDetails(
|
|
873
|
+
clusterDetails
|
|
874
|
+
);
|
|
758
875
|
return clientNode.topPods(coreApi, metricsClient, namespace);
|
|
759
876
|
}
|
|
760
877
|
captureKubernetesErrorsRethrowOthers(e) {
|
|
761
878
|
if (e.response && e.response.statusCode) {
|
|
762
|
-
this.logger.warn(
|
|
879
|
+
this.logger.warn(
|
|
880
|
+
`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname} body=[${JSON.stringify(e.response.body)}]`
|
|
881
|
+
);
|
|
763
882
|
return {
|
|
764
883
|
errorType: statusCodeToErrorType(e.response.statusCode),
|
|
765
884
|
statusCode: e.response.statusCode,
|
|
@@ -774,14 +893,33 @@ class KubernetesClientBasedFetcher {
|
|
|
774
893
|
requestOptions.uri = requestOptions.uri.replace("/apis//v1/", "/api/v1/");
|
|
775
894
|
});
|
|
776
895
|
if (namespace) {
|
|
777
|
-
return customObjects.listNamespacedCustomObject(
|
|
896
|
+
return customObjects.listNamespacedCustomObject(
|
|
897
|
+
resource.group,
|
|
898
|
+
resource.apiVersion,
|
|
899
|
+
namespace,
|
|
900
|
+
resource.plural,
|
|
901
|
+
"",
|
|
902
|
+
false,
|
|
903
|
+
"",
|
|
904
|
+
"",
|
|
905
|
+
labelSelector
|
|
906
|
+
).then((r) => {
|
|
778
907
|
return {
|
|
779
908
|
type: objectType,
|
|
780
909
|
resources: r.body.items
|
|
781
910
|
};
|
|
782
911
|
});
|
|
783
912
|
}
|
|
784
|
-
return customObjects.listClusterCustomObject(
|
|
913
|
+
return customObjects.listClusterCustomObject(
|
|
914
|
+
resource.group,
|
|
915
|
+
resource.apiVersion,
|
|
916
|
+
resource.plural,
|
|
917
|
+
"",
|
|
918
|
+
false,
|
|
919
|
+
"",
|
|
920
|
+
"",
|
|
921
|
+
labelSelector
|
|
922
|
+
).then((r) => {
|
|
785
923
|
return {
|
|
786
924
|
type: objectType,
|
|
787
925
|
resources: r.body.items
|
|
@@ -804,7 +942,9 @@ const addResourceRoutesToRouter = (router, catalogApi, objectsProvider) => {
|
|
|
804
942
|
} catch (error) {
|
|
805
943
|
throw new errors.InputError(`Invalid entity ref, ${error}`);
|
|
806
944
|
}
|
|
807
|
-
const token = pluginAuthNode.getBearerTokenFromAuthorizationHeader(
|
|
945
|
+
const token = pluginAuthNode.getBearerTokenFromAuthorizationHeader(
|
|
946
|
+
req.headers.authorization
|
|
947
|
+
);
|
|
808
948
|
if (!token) {
|
|
809
949
|
throw new errors.AuthenticationError("No Backstage token");
|
|
810
950
|
}
|
|
@@ -812,7 +952,9 @@ const addResourceRoutesToRouter = (router, catalogApi, objectsProvider) => {
|
|
|
812
952
|
token
|
|
813
953
|
});
|
|
814
954
|
if (!entity) {
|
|
815
|
-
throw new errors.InputError(
|
|
955
|
+
throw new errors.InputError(
|
|
956
|
+
`Entity ref missing, ${catalogModel.stringifyEntityRef(entityRef)}`
|
|
957
|
+
);
|
|
816
958
|
}
|
|
817
959
|
return entity;
|
|
818
960
|
};
|
|
@@ -861,7 +1003,9 @@ class KubernetesBuilder {
|
|
|
861
1003
|
if (process.env.NODE_ENV !== "development") {
|
|
862
1004
|
throw new Error("Kubernetes configuration is missing");
|
|
863
1005
|
}
|
|
864
|
-
logger.warn(
|
|
1006
|
+
logger.warn(
|
|
1007
|
+
"Failed to initialize kubernetes backend: kubernetes config is missing"
|
|
1008
|
+
);
|
|
865
1009
|
return {
|
|
866
1010
|
router: Router__default["default"]()
|
|
867
1011
|
};
|
|
@@ -877,7 +1021,11 @@ class KubernetesBuilder {
|
|
|
877
1021
|
customResources,
|
|
878
1022
|
objectTypesToFetch: this.getObjectTypesToFetch()
|
|
879
1023
|
});
|
|
880
|
-
const router = this.buildRouter(
|
|
1024
|
+
const router = this.buildRouter(
|
|
1025
|
+
objectsProvider,
|
|
1026
|
+
clusterSupplier,
|
|
1027
|
+
this.env.catalogApi
|
|
1028
|
+
);
|
|
881
1029
|
return {
|
|
882
1030
|
clusterSupplier,
|
|
883
1031
|
customResources,
|
|
@@ -909,18 +1057,26 @@ class KubernetesBuilder {
|
|
|
909
1057
|
}
|
|
910
1058
|
buildCustomResources() {
|
|
911
1059
|
var _a;
|
|
912
|
-
const customResources = ((_a = this.env.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1060
|
+
const customResources = ((_a = this.env.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map(
|
|
1061
|
+
(c) => ({
|
|
1062
|
+
group: c.getString("group"),
|
|
1063
|
+
apiVersion: c.getString("apiVersion"),
|
|
1064
|
+
plural: c.getString("plural"),
|
|
1065
|
+
objectType: "customresources"
|
|
1066
|
+
})
|
|
1067
|
+
);
|
|
1068
|
+
this.env.logger.info(
|
|
1069
|
+
`action=LoadingCustomResources numOfCustomResources=${customResources.length}`
|
|
1070
|
+
);
|
|
919
1071
|
return customResources;
|
|
920
1072
|
}
|
|
921
1073
|
buildClusterSupplier(refreshInterval) {
|
|
922
1074
|
const config = this.env.config;
|
|
923
|
-
return getCombinedClusterSupplier(
|
|
1075
|
+
return getCombinedClusterSupplier(
|
|
1076
|
+
config,
|
|
1077
|
+
this.env.catalogApi,
|
|
1078
|
+
refreshInterval
|
|
1079
|
+
);
|
|
924
1080
|
}
|
|
925
1081
|
buildObjectsProvider(options) {
|
|
926
1082
|
return new KubernetesFanOutHandler(options);
|
|
@@ -938,7 +1094,9 @@ class KubernetesBuilder {
|
|
|
938
1094
|
case "http":
|
|
939
1095
|
return this.buildHttpServiceLocator(clusterSupplier);
|
|
940
1096
|
default:
|
|
941
|
-
throw new Error(
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`Unsupported kubernetes.clusterLocatorMethod "${method}"`
|
|
1099
|
+
);
|
|
942
1100
|
}
|
|
943
1101
|
}
|
|
944
1102
|
buildMultiTenantServiceLocator(clusterSupplier) {
|
|
@@ -961,7 +1119,9 @@ class KubernetesBuilder {
|
|
|
961
1119
|
});
|
|
962
1120
|
res.json(response);
|
|
963
1121
|
} catch (e) {
|
|
964
|
-
logger.error(
|
|
1122
|
+
logger.error(
|
|
1123
|
+
`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`
|
|
1124
|
+
);
|
|
965
1125
|
res.status(500).json({ error: e.message });
|
|
966
1126
|
}
|
|
967
1127
|
});
|
|
@@ -981,18 +1141,28 @@ class KubernetesBuilder {
|
|
|
981
1141
|
}
|
|
982
1142
|
async fetchClusterDetails(clusterSupplier) {
|
|
983
1143
|
const clusterDetails = await clusterSupplier.getClusters();
|
|
984
|
-
this.env.logger.info(
|
|
1144
|
+
this.env.logger.info(
|
|
1145
|
+
`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`
|
|
1146
|
+
);
|
|
985
1147
|
return clusterDetails;
|
|
986
1148
|
}
|
|
987
1149
|
getServiceLocatorMethod() {
|
|
988
|
-
return this.env.config.getString(
|
|
1150
|
+
return this.env.config.getString(
|
|
1151
|
+
"kubernetes.serviceLocatorMethod.type"
|
|
1152
|
+
);
|
|
989
1153
|
}
|
|
990
1154
|
getObjectTypesToFetch() {
|
|
991
|
-
const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(
|
|
992
|
-
|
|
1155
|
+
const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(
|
|
1156
|
+
"kubernetes.objectTypes"
|
|
1157
|
+
);
|
|
1158
|
+
const apiVersionOverrides = this.env.config.getOptionalConfig(
|
|
1159
|
+
"kubernetes.apiVersionOverrides"
|
|
1160
|
+
);
|
|
993
1161
|
let objectTypesToFetch;
|
|
994
1162
|
if (objectTypesToFetchStrings) {
|
|
995
|
-
objectTypesToFetch = DEFAULT_OBJECTS.filter(
|
|
1163
|
+
objectTypesToFetch = DEFAULT_OBJECTS.filter(
|
|
1164
|
+
(obj) => objectTypesToFetchStrings.includes(obj.objectType)
|
|
1165
|
+
);
|
|
996
1166
|
}
|
|
997
1167
|
if (apiVersionOverrides) {
|
|
998
1168
|
objectTypesToFetch = objectTypesToFetch != null ? objectTypesToFetch : DEFAULT_OBJECTS;
|