@backstage/plugin-kubernetes-backend 0.3.15 → 0.3.19
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 +234 -140
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +102 -33
- package/package.json +10 -8
- package/schema.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @backstage/plugin-kubernetes-backend
|
|
2
2
|
|
|
3
|
+
## 0.3.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 37dc844728: Include CronJobs and Jobs as default objects returned by the kubernetes backend and add/update relevant types.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/errors@0.1.5
|
|
10
|
+
- @backstage/plugin-kubernetes-common@0.1.6
|
|
11
|
+
- @backstage/backend-common@0.9.11
|
|
12
|
+
|
|
13
|
+
## 0.3.18
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- b61c50a12f: Fix Kubernetes plugin custom objects lookup regression
|
|
18
|
+
- c57b075d18: add caData support for kubernetes client config
|
|
19
|
+
- 36e67d2f24: Internal updates to apply more strict checks to throw errors.
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @backstage/backend-common@0.9.7
|
|
22
|
+
- @backstage/errors@0.1.3
|
|
23
|
+
- @backstage/catalog-model@0.9.5
|
|
24
|
+
|
|
25
|
+
## 0.3.17
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- 89bcf90b66: Refactor kubernetes fetcher to reduce boilerplate code
|
|
30
|
+
- a982e166c5: Enable customization of services used by the kubernetes backend plugin
|
|
31
|
+
|
|
32
|
+
The createRouter function has been deprecated in favor of a KubernetesBuilder object.
|
|
33
|
+
Here's how you should upgrade your projects when configuring the Kubernetes backend plugin.
|
|
34
|
+
in your `packages/backend/src/plugins/kubernetes.ts` file for instance:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
|
|
38
|
+
import { PluginEnvironment } from '../types';
|
|
39
|
+
|
|
40
|
+
export default async function createPlugin({
|
|
41
|
+
logger,
|
|
42
|
+
config,
|
|
43
|
+
}: PluginEnvironment) {
|
|
44
|
+
const { router } = await KubernetesBuilder.createBuilder({
|
|
45
|
+
logger,
|
|
46
|
+
config,
|
|
47
|
+
}).build();
|
|
48
|
+
return router;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 0.3.16
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- febddedcb2: Bump `lodash` to remediate `SNYK-JS-LODASH-590103` security vulnerability
|
|
57
|
+
- 7a0c334707: Provide access to the Kubernetes dashboard when viewing a specific resource
|
|
58
|
+
- Updated dependencies
|
|
59
|
+
- @backstage/catalog-model@0.9.3
|
|
60
|
+
- @backstage/backend-common@0.9.4
|
|
61
|
+
- @backstage/config@0.1.10
|
|
62
|
+
- @backstage/plugin-kubernetes-common@0.1.4
|
|
63
|
+
|
|
3
64
|
## 0.3.15
|
|
4
65
|
|
|
5
66
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var express = require('express');
|
|
6
6
|
var Router = require('express-promise-router');
|
|
7
|
+
var errors = require('@backstage/errors');
|
|
7
8
|
var container = require('@google-cloud/container');
|
|
8
9
|
var clientNode = require('@kubernetes/client-node');
|
|
9
10
|
var AWS = require('aws-sdk');
|
|
@@ -51,8 +52,17 @@ class ConfigClusterLocator {
|
|
|
51
52
|
url: c.getString("url"),
|
|
52
53
|
serviceAccountToken: c.getOptionalString("serviceAccountToken"),
|
|
53
54
|
skipTLSVerify: (_a = c.getOptionalBoolean("skipTLSVerify")) != null ? _a : false,
|
|
55
|
+
caData: c.getOptionalString("caData"),
|
|
54
56
|
authProvider
|
|
55
57
|
};
|
|
58
|
+
const dashboardUrl = c.getOptionalString("dashboardUrl");
|
|
59
|
+
if (dashboardUrl) {
|
|
60
|
+
clusterDetails.dashboardUrl = dashboardUrl;
|
|
61
|
+
}
|
|
62
|
+
const dashboardApp = c.getOptionalString("dashboardApp");
|
|
63
|
+
if (dashboardApp) {
|
|
64
|
+
clusterDetails.dashboardApp = dashboardApp;
|
|
65
|
+
}
|
|
56
66
|
switch (authProvider) {
|
|
57
67
|
case "google": {
|
|
58
68
|
return clusterDetails;
|
|
@@ -111,7 +121,7 @@ class GkeClusterLocator {
|
|
|
111
121
|
};
|
|
112
122
|
});
|
|
113
123
|
} catch (e) {
|
|
114
|
-
throw new
|
|
124
|
+
throw new errors.ForwardedError(`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`, e);
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
}
|
|
@@ -148,7 +158,8 @@ class KubernetesClientProvider {
|
|
|
148
158
|
const cluster = {
|
|
149
159
|
name: clusterDetails.name,
|
|
150
160
|
server: clusterDetails.url,
|
|
151
|
-
skipTLSVerify: clusterDetails.skipTLSVerify
|
|
161
|
+
skipTLSVerify: clusterDetails.skipTLSVerify,
|
|
162
|
+
caData: clusterDetails.caData
|
|
152
163
|
};
|
|
153
164
|
const user = {
|
|
154
165
|
name: "backstage",
|
|
@@ -180,6 +191,10 @@ class KubernetesClientProvider {
|
|
|
180
191
|
const kc = this.getKubeConfig(clusterDetails);
|
|
181
192
|
return kc.makeApiClient(clientNode.AutoscalingV1Api);
|
|
182
193
|
}
|
|
194
|
+
getBatchClientByClusterDetails(clusterDetails) {
|
|
195
|
+
const kc = this.getKubeConfig(clusterDetails);
|
|
196
|
+
return kc.makeApiClient(clientNode.BatchV1Api);
|
|
197
|
+
}
|
|
183
198
|
getNetworkingBeta1Client(clusterDetails) {
|
|
184
199
|
const kc = this.getKubeConfig(clusterDetails);
|
|
185
200
|
return kc.makeApiClient(clientNode.NetworkingV1beta1Api);
|
|
@@ -315,13 +330,60 @@ class KubernetesAuthTranslatorGenerator {
|
|
|
315
330
|
}
|
|
316
331
|
|
|
317
332
|
const DEFAULT_OBJECTS = [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
333
|
+
{
|
|
334
|
+
group: "",
|
|
335
|
+
apiVersion: "v1",
|
|
336
|
+
plural: "pods",
|
|
337
|
+
objectType: "pods"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
group: "",
|
|
341
|
+
apiVersion: "v1",
|
|
342
|
+
plural: "services",
|
|
343
|
+
objectType: "services"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
group: "",
|
|
347
|
+
apiVersion: "v1",
|
|
348
|
+
plural: "configmaps",
|
|
349
|
+
objectType: "configmaps"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
group: "apps",
|
|
353
|
+
apiVersion: "v1",
|
|
354
|
+
plural: "deployments",
|
|
355
|
+
objectType: "deployments"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
group: "apps",
|
|
359
|
+
apiVersion: "v1",
|
|
360
|
+
plural: "replicasets",
|
|
361
|
+
objectType: "replicasets"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
group: "autoscaling",
|
|
365
|
+
apiVersion: "v1",
|
|
366
|
+
plural: "horizontalpodautoscalers",
|
|
367
|
+
objectType: "horizontalpodautoscalers"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
group: "batch",
|
|
371
|
+
apiVersion: "v1",
|
|
372
|
+
plural: "jobs",
|
|
373
|
+
objectType: "jobs"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
group: "batch",
|
|
377
|
+
apiVersion: "v1",
|
|
378
|
+
plural: "cronjobs",
|
|
379
|
+
objectType: "cronjobs"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
group: "networking.k8s.io",
|
|
383
|
+
apiVersion: "v1",
|
|
384
|
+
plural: "ingresses",
|
|
385
|
+
objectType: "ingresses"
|
|
386
|
+
}
|
|
325
387
|
];
|
|
326
388
|
class KubernetesFanOutHandler {
|
|
327
389
|
constructor({
|
|
@@ -335,7 +397,7 @@ class KubernetesFanOutHandler {
|
|
|
335
397
|
this.fetcher = fetcher;
|
|
336
398
|
this.serviceLocator = serviceLocator;
|
|
337
399
|
this.customResources = customResources;
|
|
338
|
-
this.objectTypesToFetch = objectTypesToFetch;
|
|
400
|
+
this.objectTypesToFetch = new Set(objectTypesToFetch);
|
|
339
401
|
}
|
|
340
402
|
async getKubernetesObjectsByEntity(requestBody) {
|
|
341
403
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -352,17 +414,24 @@ class KubernetesFanOutHandler {
|
|
|
352
414
|
return this.fetcher.fetchObjectsForService({
|
|
353
415
|
serviceId: entityName,
|
|
354
416
|
clusterDetails: clusterDetailsItem,
|
|
355
|
-
objectTypesToFetch:
|
|
417
|
+
objectTypesToFetch: this.objectTypesToFetch,
|
|
356
418
|
labelSelector,
|
|
357
419
|
customResources: this.customResources
|
|
358
420
|
}).then((result) => {
|
|
359
|
-
|
|
421
|
+
const objects = {
|
|
360
422
|
cluster: {
|
|
361
423
|
name: clusterDetailsItem.name
|
|
362
424
|
},
|
|
363
425
|
resources: result.responses,
|
|
364
426
|
errors: result.errors
|
|
365
427
|
};
|
|
428
|
+
if (clusterDetailsItem.dashboardUrl) {
|
|
429
|
+
objects.cluster.dashboardUrl = clusterDetailsItem.dashboardUrl;
|
|
430
|
+
}
|
|
431
|
+
if (clusterDetailsItem.dashboardApp) {
|
|
432
|
+
objects.cluster.dashboardApp = clusterDetailsItem.dashboardApp;
|
|
433
|
+
}
|
|
434
|
+
return objects;
|
|
366
435
|
});
|
|
367
436
|
})).then((r) => ({
|
|
368
437
|
items: r.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))
|
|
@@ -393,16 +462,6 @@ const statusCodeToErrorType = (statusCode) => {
|
|
|
393
462
|
return "UNKNOWN_ERROR";
|
|
394
463
|
}
|
|
395
464
|
};
|
|
396
|
-
const captureKubernetesErrorsRethrowOthers = (e) => {
|
|
397
|
-
if (e.response && e.response.statusCode) {
|
|
398
|
-
return {
|
|
399
|
-
errorType: statusCodeToErrorType(e.response.statusCode),
|
|
400
|
-
statusCode: e.response.statusCode,
|
|
401
|
-
resourcePath: e.response.request.uri.pathname
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
throw e;
|
|
405
|
-
};
|
|
406
465
|
class KubernetesClientBasedFetcher {
|
|
407
466
|
constructor({
|
|
408
467
|
kubernetesClientProvider,
|
|
@@ -412,145 +471,180 @@ class KubernetesClientBasedFetcher {
|
|
|
412
471
|
this.logger = logger;
|
|
413
472
|
}
|
|
414
473
|
fetchObjectsForService(params) {
|
|
415
|
-
const fetchResults = Array.from(params.objectTypesToFetch).map((
|
|
416
|
-
return this.
|
|
417
|
-
});
|
|
418
|
-
const customObjectsFetchResults = params.customResources.map((cr) => {
|
|
419
|
-
return this.fetchCustomResource(params.clusterDetails, cr, params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`).catch(captureKubernetesErrorsRethrowOthers);
|
|
474
|
+
const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map((toFetch) => {
|
|
475
|
+
return this.fetchResource(params.clusterDetails, toFetch, params.labelSelector || `backstage.io/kubernetes-id=${params.serviceId}`, toFetch.objectType).catch(this.captureKubernetesErrorsRethrowOthers.bind(this));
|
|
420
476
|
});
|
|
421
|
-
return Promise.all(fetchResults
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return this.fetchConfigMapsForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
432
|
-
case "deployments":
|
|
433
|
-
return this.fetchDeploymentsForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
434
|
-
case "replicasets":
|
|
435
|
-
return this.fetchReplicaSetsForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
436
|
-
case "services":
|
|
437
|
-
return this.fetchServicesForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
438
|
-
case "horizontalpodautoscalers":
|
|
439
|
-
return this.fetchHorizontalPodAutoscalersForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
440
|
-
case "ingresses":
|
|
441
|
-
return this.fetchIngressesForService(clusterDetails, labelSelector).then((r) => ({type, resources: r}));
|
|
442
|
-
default:
|
|
443
|
-
throw new Error(`unrecognised type=${type}`);
|
|
477
|
+
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
|
|
478
|
+
}
|
|
479
|
+
captureKubernetesErrorsRethrowOthers(e) {
|
|
480
|
+
if (e.response && e.response.statusCode) {
|
|
481
|
+
this.logger.info(`statusCode=${e.response.statusCode} for resource ${e.response.request.uri.pathname}`);
|
|
482
|
+
return {
|
|
483
|
+
errorType: statusCodeToErrorType(e.response.statusCode),
|
|
484
|
+
statusCode: e.response.statusCode,
|
|
485
|
+
resourcePath: e.response.request.uri.pathname
|
|
486
|
+
};
|
|
444
487
|
}
|
|
488
|
+
throw e;
|
|
445
489
|
}
|
|
446
|
-
|
|
490
|
+
fetchResource(clusterDetails, resource, labelSelector, objectType) {
|
|
447
491
|
const customObjects = this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails);
|
|
448
|
-
|
|
449
|
-
|
|
492
|
+
customObjects.addInterceptor((requestOptions) => {
|
|
493
|
+
requestOptions.uri = requestOptions.uri.replace("/apis//v1/", "/api/v1/");
|
|
494
|
+
});
|
|
495
|
+
return customObjects.listClusterCustomObject(resource.group, resource.apiVersion, resource.plural, "", "", "", labelSelector).then((r) => {
|
|
496
|
+
return {
|
|
497
|
+
type: objectType,
|
|
498
|
+
resources: r.body.items
|
|
499
|
+
};
|
|
450
500
|
});
|
|
451
501
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
class KubernetesBuilder {
|
|
505
|
+
constructor(env) {
|
|
506
|
+
this.env = env;
|
|
507
|
+
}
|
|
508
|
+
static createBuilder(env) {
|
|
509
|
+
return new KubernetesBuilder(env);
|
|
510
|
+
}
|
|
511
|
+
async build() {
|
|
512
|
+
var _a, _b, _c, _d;
|
|
513
|
+
const logger = this.env.logger;
|
|
514
|
+
logger.info("Initializing Kubernetes backend");
|
|
515
|
+
const customResources = this.buildCustomResources();
|
|
516
|
+
const fetcher = (_a = this.fetcher) != null ? _a : this.buildFetcher();
|
|
517
|
+
const clusterSupplier = (_b = this.clusterSupplier) != null ? _b : this.buildClusterSupplier();
|
|
518
|
+
const clusterDetails = await this.fetchClusterDetails(clusterSupplier);
|
|
519
|
+
const serviceLocator = (_c = this.serviceLocator) != null ? _c : this.buildServiceLocator(this.getServiceLocatorMethod(), clusterDetails);
|
|
520
|
+
const objectsProvider = (_d = this.objectsProvider) != null ? _d : this.buildObjectsProvider({
|
|
521
|
+
logger,
|
|
522
|
+
fetcher,
|
|
523
|
+
serviceLocator,
|
|
524
|
+
customResources,
|
|
525
|
+
objectTypesToFetch: this.getObjectTypesToFetch()
|
|
460
526
|
});
|
|
527
|
+
const router = this.buildRouter(objectsProvider, clusterDetails);
|
|
528
|
+
return {
|
|
529
|
+
clusterDetails,
|
|
530
|
+
clusterSupplier,
|
|
531
|
+
customResources,
|
|
532
|
+
fetcher,
|
|
533
|
+
objectsProvider,
|
|
534
|
+
router,
|
|
535
|
+
serviceLocator
|
|
536
|
+
};
|
|
461
537
|
}
|
|
462
|
-
|
|
463
|
-
|
|
538
|
+
setClusterSupplier(clusterSupplier) {
|
|
539
|
+
this.clusterSupplier = clusterSupplier;
|
|
540
|
+
return this;
|
|
464
541
|
}
|
|
465
|
-
|
|
466
|
-
|
|
542
|
+
setObjectsProvider(objectsProvider) {
|
|
543
|
+
this.objectsProvider = objectsProvider;
|
|
544
|
+
return this;
|
|
467
545
|
}
|
|
468
|
-
|
|
469
|
-
|
|
546
|
+
setFetcher(fetcher) {
|
|
547
|
+
this.fetcher = fetcher;
|
|
548
|
+
return this;
|
|
470
549
|
}
|
|
471
|
-
|
|
472
|
-
|
|
550
|
+
setServiceLocator(serviceLocator) {
|
|
551
|
+
this.serviceLocator = serviceLocator;
|
|
552
|
+
return this;
|
|
473
553
|
}
|
|
474
|
-
|
|
475
|
-
|
|
554
|
+
buildCustomResources() {
|
|
555
|
+
var _a;
|
|
556
|
+
const customResources = ((_a = this.env.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map((c) => ({
|
|
557
|
+
group: c.getString("group"),
|
|
558
|
+
apiVersion: c.getString("apiVersion"),
|
|
559
|
+
plural: c.getString("plural"),
|
|
560
|
+
objectType: "customresources"
|
|
561
|
+
}));
|
|
562
|
+
this.env.logger.info(`action=LoadingCustomResources numOfCustomResources=${customResources.length}`);
|
|
563
|
+
return customResources;
|
|
476
564
|
}
|
|
477
|
-
|
|
478
|
-
|
|
565
|
+
buildClusterSupplier() {
|
|
566
|
+
const config = this.env.config;
|
|
567
|
+
return {
|
|
568
|
+
getClusters() {
|
|
569
|
+
return getCombinedClusterDetails(config);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
479
572
|
}
|
|
480
|
-
|
|
481
|
-
return
|
|
573
|
+
buildObjectsProvider(options) {
|
|
574
|
+
return new KubernetesFanOutHandler(options);
|
|
482
575
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
case "multiTenant":
|
|
489
|
-
return new MultiTenantServiceLocator(clusterDetails);
|
|
490
|
-
case "http":
|
|
491
|
-
throw new Error("not implemented");
|
|
492
|
-
default:
|
|
493
|
-
throw new Error(`Unsupported kubernetes.clusterLocatorMethod "${serviceLocatorMethod}"`);
|
|
576
|
+
buildFetcher() {
|
|
577
|
+
return new KubernetesClientBasedFetcher({
|
|
578
|
+
kubernetesClientProvider: new KubernetesClientProvider(),
|
|
579
|
+
logger: this.env.logger
|
|
580
|
+
});
|
|
494
581
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const response = await kubernetesFanOutHandler.getKubernetesObjectsByEntity(requestBody);
|
|
504
|
-
res.json(response);
|
|
505
|
-
} catch (e) {
|
|
506
|
-
logger.error(`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`);
|
|
507
|
-
res.status(500).json({error: e.message});
|
|
582
|
+
buildServiceLocator(method, clusterDetails) {
|
|
583
|
+
switch (method) {
|
|
584
|
+
case "multiTenant":
|
|
585
|
+
return this.buildMultiTenantServiceLocator(clusterDetails);
|
|
586
|
+
case "http":
|
|
587
|
+
return this.buildHttpServiceLocator(clusterDetails);
|
|
588
|
+
default:
|
|
589
|
+
throw new Error(`Unsupported kubernetes.clusterLocatorMethod "${method}"`);
|
|
508
590
|
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
591
|
+
}
|
|
592
|
+
buildMultiTenantServiceLocator(clusterDetails) {
|
|
593
|
+
return new MultiTenantServiceLocator(clusterDetails);
|
|
594
|
+
}
|
|
595
|
+
buildHttpServiceLocator(_clusterDetails) {
|
|
596
|
+
throw new Error("not implemented");
|
|
597
|
+
}
|
|
598
|
+
buildRouter(objectsProvider, clusterDetails) {
|
|
599
|
+
const logger = this.env.logger;
|
|
600
|
+
const router = Router__default['default']();
|
|
601
|
+
router.use(express__default['default'].json());
|
|
602
|
+
router.post("/services/:serviceId", async (req, res) => {
|
|
603
|
+
const serviceId = req.params.serviceId;
|
|
604
|
+
const requestBody = req.body;
|
|
605
|
+
try {
|
|
606
|
+
const response = await objectsProvider.getKubernetesObjectsByEntity(requestBody);
|
|
607
|
+
res.json(response);
|
|
608
|
+
} catch (e) {
|
|
609
|
+
logger.error(`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`);
|
|
610
|
+
res.status(500).json({error: e.message});
|
|
611
|
+
}
|
|
516
612
|
});
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
613
|
+
router.get("/clusters", async (_, res) => {
|
|
614
|
+
res.json({
|
|
615
|
+
items: clusterDetails.map((cd) => ({
|
|
616
|
+
name: cd.name,
|
|
617
|
+
dashboardUrl: cd.dashboardUrl,
|
|
618
|
+
authProvider: cd.authProvider
|
|
619
|
+
}))
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
return router;
|
|
623
|
+
}
|
|
624
|
+
async fetchClusterDetails(clusterSupplier) {
|
|
625
|
+
const clusterDetails = await clusterSupplier.getClusters();
|
|
626
|
+
this.env.logger.info(`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`);
|
|
627
|
+
return clusterDetails;
|
|
628
|
+
}
|
|
629
|
+
getServiceLocatorMethod() {
|
|
630
|
+
return this.env.config.getString("kubernetes.serviceLocatorMethod.type");
|
|
631
|
+
}
|
|
632
|
+
getObjectTypesToFetch() {
|
|
633
|
+
const objectTypesToFetchStrings = this.env.config.getOptionalStringArray("kubernetes.objectTypes");
|
|
634
|
+
let objectTypesToFetch;
|
|
635
|
+
if (objectTypesToFetchStrings) {
|
|
636
|
+
objectTypesToFetch = DEFAULT_OBJECTS.filter((obj) => objectTypesToFetchStrings.includes(obj.objectType));
|
|
637
|
+
}
|
|
638
|
+
return objectTypesToFetch;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
520
642
|
async function createRouter(options) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
logger.info("Initializing Kubernetes backend");
|
|
524
|
-
const customResources = ((_a = options.config.getOptionalConfigArray("kubernetes.customResources")) != null ? _a : []).map((c) => ({
|
|
525
|
-
group: c.getString("group"),
|
|
526
|
-
apiVersion: c.getString("apiVersion"),
|
|
527
|
-
plural: c.getString("plural")
|
|
528
|
-
}));
|
|
529
|
-
logger.info(`action=LoadingCustomResources numOfCustomResources=${customResources.length}`);
|
|
530
|
-
const fetcher = new KubernetesClientBasedFetcher({
|
|
531
|
-
kubernetesClientProvider: new KubernetesClientProvider(),
|
|
532
|
-
logger
|
|
533
|
-
});
|
|
534
|
-
let clusterDetails;
|
|
535
|
-
if (options.clusterSupplier) {
|
|
536
|
-
clusterDetails = await options.clusterSupplier.getClusters();
|
|
537
|
-
} else {
|
|
538
|
-
clusterDetails = await getCombinedClusterDetails(options.config);
|
|
539
|
-
}
|
|
540
|
-
logger.info(`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`);
|
|
541
|
-
const serviceLocator = getServiceLocator(options.config, clusterDetails);
|
|
542
|
-
const objectTypesToFetch = options.config.getOptionalStringArray("kubernetes.objectTypes");
|
|
543
|
-
const kubernetesFanOutHandler = new KubernetesFanOutHandler({
|
|
544
|
-
logger,
|
|
545
|
-
fetcher,
|
|
546
|
-
serviceLocator,
|
|
547
|
-
customResources,
|
|
548
|
-
objectTypesToFetch
|
|
549
|
-
});
|
|
550
|
-
return makeRouter(logger, kubernetesFanOutHandler, clusterDetails);
|
|
643
|
+
const {router} = await KubernetesBuilder.createBuilder(options).setClusterSupplier(options.clusterSupplier).build();
|
|
644
|
+
return router;
|
|
551
645
|
}
|
|
552
646
|
|
|
553
647
|
exports.DEFAULT_OBJECTS = DEFAULT_OBJECTS;
|
|
648
|
+
exports.KubernetesBuilder = KubernetesBuilder;
|
|
554
649
|
exports.createRouter = createRouter;
|
|
555
|
-
exports.makeRouter = makeRouter;
|
|
556
650
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -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/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 = {\n name: c.getString('name'),\n url: c.getString('url'),\n serviceAccountToken: c.getOptionalString('serviceAccountToken'),\n skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false,\n authProvider: authProvider,\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 * 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 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 Error(\n `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region} : [${e.message}]`,\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 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 };\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 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 KubernetesObjectTypes,\n KubernetesServiceLocator,\n} from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\nimport { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';\nimport { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator';\n\nexport const DEFAULT_OBJECTS: KubernetesObjectTypes[] = [\n 'pods',\n 'services',\n 'configmaps',\n 'deployments',\n 'replicasets',\n 'horizontalpodautoscalers',\n 'ingresses',\n];\n\nexport interface KubernetesFanOutHandlerOptions {\n logger: Logger;\n fetcher: KubernetesFetcher;\n serviceLocator: KubernetesServiceLocator;\n customResources: CustomResource[];\n objectTypesToFetch?: KubernetesObjectTypes[];\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: KubernetesObjectTypes[];\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 = objectTypesToFetch;\n }\n\n async getKubernetesObjectsByEntity(requestBody: KubernetesRequestBody) {\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: new Set(this.objectTypesToFetch),\n labelSelector,\n customResources: this.customResources,\n })\n .then(result => {\n return {\n cluster: {\n name: clusterDetailsItem.name,\n },\n resources: result.responses,\n errors: result.errors,\n };\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 CoreV1Api,\n ExtensionsV1beta1Ingress,\n NetworkingV1beta1Api,\n V1ConfigMap,\n V1Deployment,\n V1HorizontalPodAutoscaler,\n V1Pod,\n V1ReplicaSet,\n} from '@kubernetes/client-node';\nimport { V1Service } from '@kubernetes/client-node/dist/gen/model/v1Service';\nimport http from 'http';\nimport lodash, { Dictionary } from 'lodash';\nimport { Logger } from 'winston';\nimport {\n ClusterDetails,\n FetchResponseWrapper,\n KubernetesFetcher,\n KubernetesObjectTypes,\n ObjectFetchParams,\n CustomResource,\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 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\nconst captureKubernetesErrorsRethrowOthers = (e: any): KubernetesFetchError => {\n if (e.response && e.response.statusCode) {\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\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).map(type => {\n return this.fetchByObjectType(\n params.clusterDetails,\n type,\n params.labelSelector ||\n `backstage.io/kubernetes-id=${params.serviceId}`,\n ).catch(captureKubernetesErrorsRethrowOthers);\n });\n\n const customObjectsFetchResults = params.customResources.map(cr => {\n return this.fetchCustomResource(\n params.clusterDetails,\n cr,\n params.labelSelector ||\n `backstage.io/kubernetes-id=${params.serviceId}`,\n ).catch(captureKubernetesErrorsRethrowOthers);\n });\n\n return Promise.all(fetchResults.concat(customObjectsFetchResults)).then(\n fetchResultsToResponseWrapper,\n );\n }\n\n // TODO could probably do with a tidy up\n private fetchByObjectType(\n clusterDetails: ClusterDetails,\n type: KubernetesObjectTypes,\n labelSelector: string,\n ): Promise<FetchResponse> {\n switch (type) {\n case 'pods':\n return this.fetchPodsForService(clusterDetails, labelSelector).then(\n r => ({\n type: type,\n resources: r,\n }),\n );\n case 'configmaps':\n return this.fetchConfigMapsForService(\n clusterDetails,\n labelSelector,\n ).then(r => ({ type: type, resources: r }));\n case 'deployments':\n return this.fetchDeploymentsForService(\n clusterDetails,\n labelSelector,\n ).then(r => ({ type: type, resources: r }));\n case 'replicasets':\n return this.fetchReplicaSetsForService(\n clusterDetails,\n labelSelector,\n ).then(r => ({ type: type, resources: r }));\n case 'services':\n return this.fetchServicesForService(clusterDetails, labelSelector).then(\n r => ({ type: type, resources: r }),\n );\n case 'horizontalpodautoscalers':\n return this.fetchHorizontalPodAutoscalersForService(\n clusterDetails,\n labelSelector,\n ).then(r => ({ type: type, resources: r }));\n case 'ingresses':\n return this.fetchIngressesForService(\n clusterDetails,\n labelSelector,\n ).then(r => ({ type: type, resources: r }));\n default:\n // unrecognised type\n throw new Error(`unrecognised type=${type}`);\n }\n }\n\n private fetchCustomResource(\n clusterDetails: ClusterDetails,\n customResource: CustomResource,\n labelSelector: string,\n ): Promise<FetchResponse> {\n const customObjects =\n this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails);\n\n return customObjects\n .listClusterCustomObject(\n customResource.group,\n customResource.apiVersion,\n customResource.plural,\n '',\n '',\n '',\n labelSelector,\n )\n .then(r => {\n return { type: 'customresources', resources: (r.body as any).items };\n });\n }\n\n private singleClusterFetch<T>(\n clusterDetails: ClusterDetails,\n fn: (\n client: Clients,\n ) => Promise<{ body: { items: Array<T> }; response: http.IncomingMessage }>,\n ): Promise<Array<T>> {\n const core =\n this.kubernetesClientProvider.getCoreClientByClusterDetails(\n clusterDetails,\n );\n const apps =\n this.kubernetesClientProvider.getAppsClientByClusterDetails(\n clusterDetails,\n );\n const autoscaling =\n this.kubernetesClientProvider.getAutoscalingClientByClusterDetails(\n clusterDetails,\n );\n const networkingBeta1 =\n this.kubernetesClientProvider.getNetworkingBeta1Client(clusterDetails);\n\n this.logger.debug(`calling cluster=${clusterDetails.name}`);\n return fn({ core, apps, autoscaling, networkingBeta1 }).then(({ body }) => {\n return body.items;\n });\n }\n\n private fetchServicesForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1Service>> {\n return this.singleClusterFetch<V1Service>(clusterDetails, ({ core }) =>\n core.listServiceForAllNamespaces(false, '', '', labelSelector),\n );\n }\n\n private fetchPodsForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1Pod>> {\n return this.singleClusterFetch<V1Pod>(clusterDetails, ({ core }) =>\n core.listPodForAllNamespaces(false, '', '', labelSelector),\n );\n }\n\n private fetchConfigMapsForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1ConfigMap>> {\n return this.singleClusterFetch<V1Pod>(clusterDetails, ({ core }) =>\n core.listConfigMapForAllNamespaces(false, '', '', labelSelector),\n );\n }\n\n private fetchDeploymentsForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1Deployment>> {\n return this.singleClusterFetch<V1Deployment>(clusterDetails, ({ apps }) =>\n apps.listDeploymentForAllNamespaces(false, '', '', labelSelector),\n );\n }\n\n private fetchReplicaSetsForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1ReplicaSet>> {\n return this.singleClusterFetch<V1ReplicaSet>(clusterDetails, ({ apps }) =>\n apps.listReplicaSetForAllNamespaces(false, '', '', labelSelector),\n );\n }\n\n private fetchHorizontalPodAutoscalersForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<V1HorizontalPodAutoscaler>> {\n return this.singleClusterFetch<V1HorizontalPodAutoscaler>(\n clusterDetails,\n ({ autoscaling }) =>\n autoscaling.listHorizontalPodAutoscalerForAllNamespaces(\n false,\n '',\n '',\n labelSelector,\n ),\n );\n }\n\n private fetchIngressesForService(\n clusterDetails: ClusterDetails,\n labelSelector: string,\n ): Promise<Array<ExtensionsV1beta1Ingress>> {\n return this.singleClusterFetch<ExtensionsV1beta1Ingress>(\n clusterDetails,\n ({ networkingBeta1 }) =>\n networkingBeta1.listIngressForAllNamespaces(\n false,\n '',\n '',\n labelSelector,\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 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 KubernetesClustersSupplier,\n KubernetesObjectTypes,\n KubernetesServiceLocator,\n ServiceLocatorMethod,\n CustomResource,\n} from '../types/types';\nimport { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';\nimport { KubernetesClientProvider } from './KubernetesClientProvider';\nimport { KubernetesFanOutHandler } from './KubernetesFanOutHandler';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\n\nexport interface RouterOptions {\n logger: Logger;\n config: Config;\n clusterSupplier?: KubernetesClustersSupplier;\n}\n\nconst getServiceLocator = (\n config: Config,\n clusterDetails: ClusterDetails[],\n): KubernetesServiceLocator => {\n const serviceLocatorMethod = config.getString(\n 'kubernetes.serviceLocatorMethod.type',\n ) as ServiceLocatorMethod;\n\n switch (serviceLocatorMethod) {\n case 'multiTenant':\n return new MultiTenantServiceLocator(clusterDetails);\n case 'http':\n throw new Error('not implemented');\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethod \"${serviceLocatorMethod}\"`,\n );\n }\n};\n\nexport const makeRouter = (\n logger: Logger,\n kubernetesFanOutHandler: KubernetesFanOutHandler,\n clusterDetails: ClusterDetails[],\n): express.Router => {\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: KubernetesRequestBody = req.body;\n try {\n const response =\n await kubernetesFanOutHandler.getKubernetesObjectsByEntity(requestBody);\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 authProvider: cd.authProvider,\n })),\n });\n });\n return router;\n};\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const logger = options.logger;\n\n logger.info('Initializing Kubernetes backend');\n\n const customResources: CustomResource[] = (\n options.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 } as CustomResource),\n );\n\n logger.info(\n `action=LoadingCustomResources numOfCustomResources=${customResources.length}`,\n );\n\n const fetcher = new KubernetesClientBasedFetcher({\n kubernetesClientProvider: new KubernetesClientProvider(),\n logger,\n });\n\n let clusterDetails: ClusterDetails[];\n\n if (options.clusterSupplier) {\n clusterDetails = await options.clusterSupplier.getClusters();\n } else {\n clusterDetails = await getCombinedClusterDetails(options.config);\n }\n\n logger.info(\n `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,\n );\n\n const serviceLocator = getServiceLocator(options.config, clusterDetails);\n const objectTypesToFetch = options.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const kubernetesFanOutHandler = new KubernetesFanOutHandler({\n logger,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n });\n\n return makeRouter(logger, kubernetesFanOutHandler, clusterDetails);\n}\n"],"names":["container","KubeConfig","CoreV1Api","AppsV1Api","AutoscalingV1Api","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 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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { Config } from '@backstage/config';
|
|
2
|
-
import express from 'express';
|
|
3
2
|
import { Logger } from 'winston';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import { KubernetesFetchError, FetchResponse, KubernetesRequestBody, ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
|
|
4
|
+
import express from 'express';
|
|
6
5
|
|
|
7
|
-
interface CustomResource {
|
|
8
|
-
group: string;
|
|
9
|
-
apiVersion: string;
|
|
10
|
-
plural: string;
|
|
11
|
-
}
|
|
12
6
|
interface ObjectFetchParams {
|
|
13
7
|
serviceId: string;
|
|
14
8
|
clusterDetails: AWSClusterDetails | GKEClusterDetails | ServiceAccountClusterDetails | ClusterDetails;
|
|
15
|
-
objectTypesToFetch: Set<
|
|
9
|
+
objectTypesToFetch: Set<ObjectToFetch>;
|
|
16
10
|
labelSelector: string;
|
|
17
11
|
customResources: CustomResource[];
|
|
18
12
|
}
|
|
@@ -23,7 +17,16 @@ interface FetchResponseWrapper {
|
|
|
23
17
|
errors: KubernetesFetchError[];
|
|
24
18
|
responses: FetchResponse[];
|
|
25
19
|
}
|
|
26
|
-
|
|
20
|
+
interface ObjectToFetch {
|
|
21
|
+
objectType: KubernetesObjectTypes;
|
|
22
|
+
group: string;
|
|
23
|
+
apiVersion: string;
|
|
24
|
+
plural: string;
|
|
25
|
+
}
|
|
26
|
+
interface CustomResource extends ObjectToFetch {
|
|
27
|
+
objectType: 'customresources';
|
|
28
|
+
}
|
|
29
|
+
declare type KubernetesObjectTypes = 'pods' | 'services' | 'configmaps' | 'deployments' | 'replicasets' | 'horizontalpodautoscalers' | 'jobs' | 'cronjobs' | 'ingresses' | 'customresources';
|
|
27
30
|
interface KubernetesClustersSupplier {
|
|
28
31
|
getClusters(): Promise<ClusterDetails[]>;
|
|
29
32
|
}
|
|
@@ -32,11 +35,40 @@ interface KubernetesServiceLocator {
|
|
|
32
35
|
}
|
|
33
36
|
declare type ServiceLocatorMethod = 'multiTenant' | 'http';
|
|
34
37
|
interface ClusterDetails {
|
|
38
|
+
/**
|
|
39
|
+
* Specifies the name of the Kubernetes cluster.
|
|
40
|
+
*/
|
|
35
41
|
name: string;
|
|
36
42
|
url: string;
|
|
37
43
|
authProvider: string;
|
|
38
44
|
serviceAccountToken?: string | undefined;
|
|
39
45
|
skipTLSVerify?: boolean;
|
|
46
|
+
caData?: string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Specifies the link to the Kubernetes dashboard managing this cluster.
|
|
49
|
+
* @remarks
|
|
50
|
+
* Note that you should specify the app used for the dashboard
|
|
51
|
+
* using the dashboardApp property, in order to properly format
|
|
52
|
+
* links to kubernetes resources, otherwise it will assume that you're running the standard one.
|
|
53
|
+
* @see dashboardApp
|
|
54
|
+
*/
|
|
55
|
+
dashboardUrl?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Specifies the app that provides the Kubernetes dashboard.
|
|
58
|
+
* This will be used for formatting links to kubernetes objects inside the dashboard.
|
|
59
|
+
* @remarks
|
|
60
|
+
* The existing apps are: standard, rancher, openshift, gke, aks, eks
|
|
61
|
+
* Note that it will default to the regular dashboard provided by the Kubernetes project (standard).
|
|
62
|
+
* Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary.
|
|
63
|
+
* @defaultValue standard
|
|
64
|
+
* @see dashboardUrl
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { clusterLinksFormatters } from '@backstage/plugin-kubernetes';
|
|
68
|
+
* clusterLinksFormatters.myDashboard = (options) => ...;
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
dashboardApp?: string;
|
|
40
72
|
}
|
|
41
73
|
interface GKEClusterDetails extends ClusterDetails {
|
|
42
74
|
}
|
|
@@ -46,31 +78,16 @@ interface AWSClusterDetails extends ClusterDetails {
|
|
|
46
78
|
assumeRole?: string;
|
|
47
79
|
externalId?: string;
|
|
48
80
|
}
|
|
49
|
-
|
|
50
|
-
declare const DEFAULT_OBJECTS: KubernetesObjectTypes[];
|
|
51
|
-
interface KubernetesFanOutHandlerOptions {
|
|
81
|
+
interface KubernetesObjectsProviderOptions {
|
|
52
82
|
logger: Logger;
|
|
53
83
|
fetcher: KubernetesFetcher;
|
|
54
84
|
serviceLocator: KubernetesServiceLocator;
|
|
55
85
|
customResources: CustomResource[];
|
|
56
|
-
objectTypesToFetch?:
|
|
57
|
-
}
|
|
58
|
-
declare
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
private readonly serviceLocator;
|
|
62
|
-
private readonly customResources;
|
|
63
|
-
private readonly objectTypesToFetch;
|
|
64
|
-
constructor({ logger, fetcher, serviceLocator, customResources, objectTypesToFetch, }: KubernetesFanOutHandlerOptions);
|
|
65
|
-
getKubernetesObjectsByEntity(requestBody: KubernetesRequestBody): Promise<{
|
|
66
|
-
items: {
|
|
67
|
-
cluster: {
|
|
68
|
-
name: string;
|
|
69
|
-
};
|
|
70
|
-
resources: _backstage_plugin_kubernetes_common.FetchResponse[];
|
|
71
|
-
errors: _backstage_plugin_kubernetes_common.KubernetesFetchError[];
|
|
72
|
-
}[];
|
|
73
|
-
}>;
|
|
86
|
+
objectTypesToFetch?: ObjectToFetch[];
|
|
87
|
+
}
|
|
88
|
+
declare type ObjectsByEntityRequest = KubernetesRequestBody;
|
|
89
|
+
interface KubernetesObjectsProvider {
|
|
90
|
+
getKubernetesObjectsByEntity(request: ObjectsByEntityRequest): Promise<ObjectsByEntityResponse>;
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
interface RouterOptions {
|
|
@@ -78,7 +95,59 @@ interface RouterOptions {
|
|
|
78
95
|
config: Config;
|
|
79
96
|
clusterSupplier?: KubernetesClustersSupplier;
|
|
80
97
|
}
|
|
81
|
-
|
|
98
|
+
/**
|
|
99
|
+
* creates and configure a new router for handling the kubernetes backend APIs
|
|
100
|
+
* @param options - specifies the options required by this plugin
|
|
101
|
+
* @returns a new router
|
|
102
|
+
* @deprecated Please use the new KubernetesBuilder instead like this
|
|
103
|
+
* ```
|
|
104
|
+
* import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
|
|
105
|
+
* const { router } = await KubernetesBuilder.createBuilder({
|
|
106
|
+
* logger,
|
|
107
|
+
* config,
|
|
108
|
+
* }).build();
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
82
111
|
declare function createRouter(options: RouterOptions): Promise<express.Router>;
|
|
83
112
|
|
|
84
|
-
|
|
113
|
+
interface KubernetesEnvironment {
|
|
114
|
+
logger: Logger;
|
|
115
|
+
config: Config;
|
|
116
|
+
}
|
|
117
|
+
declare class KubernetesBuilder {
|
|
118
|
+
protected readonly env: KubernetesEnvironment;
|
|
119
|
+
private clusterSupplier?;
|
|
120
|
+
private objectsProvider?;
|
|
121
|
+
private fetcher?;
|
|
122
|
+
private serviceLocator?;
|
|
123
|
+
static createBuilder(env: KubernetesEnvironment): KubernetesBuilder;
|
|
124
|
+
constructor(env: KubernetesEnvironment);
|
|
125
|
+
build(): Promise<{
|
|
126
|
+
clusterDetails: ClusterDetails[];
|
|
127
|
+
clusterSupplier: KubernetesClustersSupplier;
|
|
128
|
+
customResources: CustomResource[];
|
|
129
|
+
fetcher: KubernetesFetcher;
|
|
130
|
+
objectsProvider: KubernetesObjectsProvider;
|
|
131
|
+
router: express.Router;
|
|
132
|
+
serviceLocator: KubernetesServiceLocator;
|
|
133
|
+
}>;
|
|
134
|
+
setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier): this;
|
|
135
|
+
setObjectsProvider(objectsProvider?: KubernetesObjectsProvider): this;
|
|
136
|
+
setFetcher(fetcher?: KubernetesFetcher): this;
|
|
137
|
+
setServiceLocator(serviceLocator?: KubernetesServiceLocator): this;
|
|
138
|
+
protected buildCustomResources(): CustomResource[];
|
|
139
|
+
protected buildClusterSupplier(): KubernetesClustersSupplier;
|
|
140
|
+
protected buildObjectsProvider(options: KubernetesObjectsProviderOptions): KubernetesObjectsProvider;
|
|
141
|
+
protected buildFetcher(): KubernetesFetcher;
|
|
142
|
+
protected buildServiceLocator(method: ServiceLocatorMethod, clusterDetails: ClusterDetails[]): KubernetesServiceLocator;
|
|
143
|
+
protected buildMultiTenantServiceLocator(clusterDetails: ClusterDetails[]): KubernetesServiceLocator;
|
|
144
|
+
protected buildHttpServiceLocator(_clusterDetails: ClusterDetails[]): KubernetesServiceLocator;
|
|
145
|
+
protected buildRouter(objectsProvider: KubernetesObjectsProvider, clusterDetails: ClusterDetails[]): express.Router;
|
|
146
|
+
protected fetchClusterDetails(clusterSupplier: KubernetesClustersSupplier): Promise<ClusterDetails[]>;
|
|
147
|
+
protected getServiceLocatorMethod(): ServiceLocatorMethod;
|
|
148
|
+
protected getObjectTypesToFetch(): ObjectToFetch[] | undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
declare const DEFAULT_OBJECTS: ObjectToFetch[];
|
|
152
|
+
|
|
153
|
+
export { AWSClusterDetails, ClusterDetails, CustomResource, DEFAULT_OBJECTS, FetchResponseWrapper, GKEClusterDetails, KubernetesBuilder, KubernetesClustersSupplier, KubernetesEnvironment, KubernetesFetcher, KubernetesObjectTypes, KubernetesObjectsProvider, KubernetesObjectsProviderOptions, KubernetesServiceLocator, ObjectFetchParams, ObjectToFetch, ObjectsByEntityRequest, RouterOptions, ServiceAccountClusterDetails, ServiceLocatorMethod, createRouter };
|
package/package.json
CHANGED
|
@@ -1,6 +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
5
|
"main": "dist/index.cjs.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"license": "Apache-2.0",
|
|
@@ -31,10 +32,11 @@
|
|
|
31
32
|
"clean": "backstage-cli clean"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@backstage/backend-common": "^0.9.
|
|
35
|
-
"@backstage/catalog-model": "^0.9.
|
|
36
|
-
"@backstage/config": "^0.1.
|
|
37
|
-
"@backstage/
|
|
35
|
+
"@backstage/backend-common": "^0.9.11",
|
|
36
|
+
"@backstage/catalog-model": "^0.9.7",
|
|
37
|
+
"@backstage/config": "^0.1.10",
|
|
38
|
+
"@backstage/errors": "^0.1.5",
|
|
39
|
+
"@backstage/plugin-kubernetes-common": "^0.1.6",
|
|
38
40
|
"@google-cloud/container": "^2.2.0",
|
|
39
41
|
"@kubernetes/client-node": "^0.15.0",
|
|
40
42
|
"@types/express": "^4.17.6",
|
|
@@ -46,14 +48,14 @@
|
|
|
46
48
|
"express-promise-router": "^4.1.0",
|
|
47
49
|
"fs-extra": "9.1.0",
|
|
48
50
|
"helmet": "^4.0.0",
|
|
49
|
-
"lodash": "^4.17.
|
|
51
|
+
"lodash": "^4.17.21",
|
|
50
52
|
"morgan": "^1.10.0",
|
|
51
53
|
"stream-buffers": "^3.0.2",
|
|
52
54
|
"winston": "^3.2.1",
|
|
53
55
|
"yn": "^4.0.0"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
|
-
"@backstage/cli": "^0.
|
|
58
|
+
"@backstage/cli": "^0.9.1",
|
|
57
59
|
"@types/aws4": "^1.5.1",
|
|
58
60
|
"aws-sdk-mock": "^5.2.1",
|
|
59
61
|
"bdd-lazy-var": "^2.6.0",
|
|
@@ -63,5 +65,5 @@
|
|
|
63
65
|
"dist",
|
|
64
66
|
"schema.d.ts"
|
|
65
67
|
],
|
|
66
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "85c127e436a24140bb3606f17f034f82aa9c7c0d"
|
|
67
69
|
}
|