@backstage/plugin-kubernetes-backend 0.18.6 → 0.18.7-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/alpha/package.json +1 -1
  3. package/dist/alpha.cjs.js +3 -150
  4. package/dist/alpha.cjs.js.map +1 -1
  5. package/dist/auth/AksStrategy.cjs.js +17 -0
  6. package/dist/auth/AksStrategy.cjs.js.map +1 -0
  7. package/dist/auth/AnonymousStrategy.cjs.js +16 -0
  8. package/dist/auth/AnonymousStrategy.cjs.js.map +1 -0
  9. package/dist/auth/AwsIamStrategy.cjs.js +80 -0
  10. package/dist/auth/AwsIamStrategy.cjs.js.map +1 -0
  11. package/dist/auth/AzureIdentityStrategy.cjs.js +58 -0
  12. package/dist/auth/AzureIdentityStrategy.cjs.js.map +1 -0
  13. package/dist/auth/DispatchStrategy.cjs.js +37 -0
  14. package/dist/auth/DispatchStrategy.cjs.js.map +1 -0
  15. package/dist/auth/GoogleServiceAccountStrategy.cjs.js +45 -0
  16. package/dist/auth/GoogleServiceAccountStrategy.cjs.js.map +1 -0
  17. package/dist/auth/GoogleStrategy.cjs.js +22 -0
  18. package/dist/auth/GoogleStrategy.cjs.js.map +1 -0
  19. package/dist/auth/OidcStrategy.cjs.js +34 -0
  20. package/dist/auth/OidcStrategy.cjs.js.map +1 -0
  21. package/dist/auth/ServiceAccountStrategy.cjs.js +33 -0
  22. package/dist/auth/ServiceAccountStrategy.cjs.js.map +1 -0
  23. package/dist/cluster-locator/CatalogClusterLocator.cjs.js +73 -0
  24. package/dist/cluster-locator/CatalogClusterLocator.cjs.js.map +1 -0
  25. package/dist/cluster-locator/ConfigClusterLocator.cjs.js +100 -0
  26. package/dist/cluster-locator/ConfigClusterLocator.cjs.js.map +1 -0
  27. package/dist/cluster-locator/GkeClusterLocator.cjs.js +126 -0
  28. package/dist/cluster-locator/GkeClusterLocator.cjs.js.map +1 -0
  29. package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js +35 -0
  30. package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js.map +1 -0
  31. package/dist/cluster-locator/index.cjs.js +67 -0
  32. package/dist/cluster-locator/index.cjs.js.map +1 -0
  33. package/dist/index.cjs.js +31 -1904
  34. package/dist/index.cjs.js.map +1 -1
  35. package/dist/package.json.cjs.js +156 -0
  36. package/dist/package.json.cjs.js.map +1 -0
  37. package/dist/plugin.cjs.js +155 -0
  38. package/dist/plugin.cjs.js.map +1 -0
  39. package/dist/routes/resourcesRoutes.cjs.js +65 -0
  40. package/dist/routes/resourcesRoutes.cjs.js.map +1 -0
  41. package/dist/service/KubernetesBuilder.cjs.js +367 -0
  42. package/dist/service/KubernetesBuilder.cjs.js.map +1 -0
  43. package/dist/service/KubernetesFanOutHandler.cjs.js +254 -0
  44. package/dist/service/KubernetesFanOutHandler.cjs.js.map +1 -0
  45. package/dist/service/KubernetesFetcher.cjs.js +231 -0
  46. package/dist/service/KubernetesFetcher.cjs.js.map +1 -0
  47. package/dist/service/KubernetesProxy.cjs.js +195 -0
  48. package/dist/service/KubernetesProxy.cjs.js.map +1 -0
  49. package/dist/service/router.cjs.js +11 -0
  50. package/dist/service/router.cjs.js.map +1 -0
  51. package/dist/service/runPeriodically.cjs.js +29 -0
  52. package/dist/service/runPeriodically.cjs.js.map +1 -0
  53. package/dist/service-locator/CatalogRelationServiceLocator.cjs.js +31 -0
  54. package/dist/service-locator/CatalogRelationServiceLocator.cjs.js.map +1 -0
  55. package/dist/service-locator/MultiTenantServiceLocator.cjs.js +15 -0
  56. package/dist/service-locator/MultiTenantServiceLocator.cjs.js.map +1 -0
  57. package/dist/service-locator/SingleTenantServiceLocator.cjs.js +24 -0
  58. package/dist/service-locator/SingleTenantServiceLocator.cjs.js.map +1 -0
  59. package/package.json +21 -20
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ var clientNode = require('@kubernetes/client-node');
4
+ var fs = require('fs-extra');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
9
+
10
+ class ServiceAccountStrategy {
11
+ async getCredential(clusterDetails) {
12
+ const token = clusterDetails.authMetadata.serviceAccountToken;
13
+ if (token) {
14
+ return { type: "bearer token", token };
15
+ }
16
+ const kc = new clientNode.KubeConfig();
17
+ kc.loadFromCluster();
18
+ const user = kc.getCurrentUser();
19
+ return {
20
+ type: "bearer token",
21
+ token: fs__default.default.readFileSync(user.authProvider.config.tokenFile).toString()
22
+ };
23
+ }
24
+ validateCluster() {
25
+ return [];
26
+ }
27
+ presentAuthMetadata(_authMetadata) {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ exports.ServiceAccountStrategy = ServiceAccountStrategy;
33
+ //# sourceMappingURL=ServiceAccountStrategy.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServiceAccountStrategy.cjs.js","sources":["../../src/auth/ServiceAccountStrategy.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 {\n AuthMetadata,\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { KubeConfig, User } from '@kubernetes/client-node';\nimport fs from 'fs-extra';\n\n/**\n *\n * @public\n */\nexport class ServiceAccountStrategy implements AuthenticationStrategy {\n public async getCredential(\n clusterDetails: ClusterDetails,\n ): Promise<KubernetesCredential> {\n const token = clusterDetails.authMetadata.serviceAccountToken;\n if (token) {\n return { type: 'bearer token', token };\n }\n const kc = new KubeConfig();\n kc.loadFromCluster();\n // loadFromCluster is guaranteed to populate the user\n const user = kc.getCurrentUser() as User;\n\n return {\n type: 'bearer token',\n token: fs.readFileSync(user.authProvider.config.tokenFile).toString(),\n };\n }\n\n public validateCluster(): Error[] {\n return [];\n }\n\n public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {\n return {};\n }\n}\n"],"names":["KubeConfig","fs"],"mappings":";;;;;;;;;AA6BO,MAAM,sBAAyD,CAAA;AAAA,EACpE,MAAa,cACX,cAC+B,EAAA;AAC/B,IAAM,MAAA,KAAA,GAAQ,eAAe,YAAa,CAAA,mBAAA,CAAA;AAC1C,IAAA,IAAI,KAAO,EAAA;AACT,MAAO,OAAA,EAAE,IAAM,EAAA,cAAA,EAAgB,KAAM,EAAA,CAAA;AAAA,KACvC;AACA,IAAM,MAAA,EAAA,GAAK,IAAIA,qBAAW,EAAA,CAAA;AAC1B,IAAA,EAAA,CAAG,eAAgB,EAAA,CAAA;AAEnB,IAAM,MAAA,IAAA,GAAO,GAAG,cAAe,EAAA,CAAA;AAE/B,IAAO,OAAA;AAAA,MACL,IAAM,EAAA,cAAA;AAAA,MACN,KAAA,EAAOC,oBAAG,YAAa,CAAA,IAAA,CAAK,aAAa,MAAO,CAAA,SAAS,EAAE,QAAS,EAAA;AAAA,KACtE,CAAA;AAAA,GACF;AAAA,EAEO,eAA2B,GAAA;AAChC,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AAAA,EAEO,oBAAoB,aAA2C,EAAA;AACpE,IAAA,OAAO,EAAC,CAAA;AAAA,GACV;AACF;;;;"}
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var catalogClient = require('@backstage/catalog-client');
4
+ var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
5
+
6
+ function isObject(obj) {
7
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
8
+ }
9
+ class CatalogClusterLocator {
10
+ catalogClient;
11
+ auth;
12
+ constructor(catalogClient, auth) {
13
+ this.catalogClient = catalogClient;
14
+ this.auth = auth;
15
+ }
16
+ static fromConfig(catalogApi, auth) {
17
+ return new CatalogClusterLocator(catalogApi, auth);
18
+ }
19
+ async getClusters(options) {
20
+ const apiServerKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER}`;
21
+ const apiServerCaKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA}`;
22
+ const authProviderKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;
23
+ const filter = {
24
+ kind: "Resource",
25
+ "spec.type": "kubernetes-cluster",
26
+ [apiServerKey]: catalogClient.CATALOG_FILTER_EXISTS,
27
+ [apiServerCaKey]: catalogClient.CATALOG_FILTER_EXISTS,
28
+ [authProviderKey]: catalogClient.CATALOG_FILTER_EXISTS
29
+ };
30
+ const clusters = await this.catalogClient.getEntities(
31
+ {
32
+ filter: [filter]
33
+ },
34
+ options?.credentials ? {
35
+ token: (await this.auth.getPluginRequestToken({
36
+ onBehalfOf: options.credentials,
37
+ targetPluginId: "catalog"
38
+ })).token
39
+ } : void 0
40
+ );
41
+ return clusters.items.map((entity) => {
42
+ const annotations = entity.metadata.annotations;
43
+ const clusterDetails = {
44
+ name: entity.metadata.name,
45
+ title: entity.metadata.title,
46
+ url: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER],
47
+ authMetadata: annotations,
48
+ caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
49
+ skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
50
+ skipTLSVerify: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true",
51
+ dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
52
+ dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
53
+ dashboardParameters: this.getDashboardParameters(annotations)
54
+ };
55
+ return clusterDetails;
56
+ });
57
+ }
58
+ getDashboardParameters(annotations) {
59
+ const dashboardParamsString = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];
60
+ if (dashboardParamsString) {
61
+ try {
62
+ const dashboardParams = JSON.parse(dashboardParamsString);
63
+ return isObject(dashboardParams) ? dashboardParams : void 0;
64
+ } catch {
65
+ return void 0;
66
+ }
67
+ }
68
+ return void 0;
69
+ }
70
+ }
71
+
72
+ exports.CatalogClusterLocator = CatalogClusterLocator;
73
+ //# sourceMappingURL=CatalogClusterLocator.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CatalogClusterLocator.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocator.ts"],"sourcesContent":["/*\n * Copyright 2022 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 AuthService,\n BackstageCredentials,\n} from '@backstage/backend-plugin-api';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\nimport { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client';\nimport {\n ANNOTATION_KUBERNETES_API_SERVER,\n ANNOTATION_KUBERNETES_API_SERVER_CA,\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP,\n ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY,\n ANNOTATION_KUBERNETES_DASHBOARD_URL,\n ANNOTATION_KUBERNETES_DASHBOARD_APP,\n ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS,\n} from '@backstage/plugin-kubernetes-common';\nimport { JsonObject } from '@backstage/types';\n\nfunction isObject(obj: unknown): obj is JsonObject {\n return typeof obj === 'object' && obj !== null && !Array.isArray(obj);\n}\n\nexport class CatalogClusterLocator implements KubernetesClustersSupplier {\n private catalogClient: CatalogApi;\n private auth: AuthService;\n\n constructor(catalogClient: CatalogApi, auth: AuthService) {\n this.catalogClient = catalogClient;\n this.auth = auth;\n }\n\n static fromConfig(\n catalogApi: CatalogApi,\n auth: AuthService,\n ): CatalogClusterLocator {\n return new CatalogClusterLocator(catalogApi, auth);\n }\n\n async getClusters(options?: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;\n const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;\n const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;\n\n const filter: Record<string, symbol | string> = {\n kind: 'Resource',\n 'spec.type': 'kubernetes-cluster',\n [apiServerKey]: CATALOG_FILTER_EXISTS,\n [apiServerCaKey]: CATALOG_FILTER_EXISTS,\n [authProviderKey]: CATALOG_FILTER_EXISTS,\n };\n\n const clusters = await this.catalogClient.getEntities(\n {\n filter: [filter],\n },\n options?.credentials\n ? {\n token: (\n await this.auth.getPluginRequestToken({\n onBehalfOf: options.credentials,\n targetPluginId: 'catalog',\n })\n ).token,\n }\n : undefined,\n );\n return clusters.items.map(entity => {\n const annotations = entity.metadata.annotations!;\n const clusterDetails: ClusterDetails = {\n name: entity.metadata.name,\n title: entity.metadata.title,\n url: annotations[ANNOTATION_KUBERNETES_API_SERVER],\n authMetadata: annotations,\n caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],\n skipMetricsLookup:\n annotations[ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === 'true',\n skipTLSVerify:\n annotations[ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === 'true',\n dashboardUrl: annotations[ANNOTATION_KUBERNETES_DASHBOARD_URL],\n dashboardApp: annotations[ANNOTATION_KUBERNETES_DASHBOARD_APP],\n dashboardParameters: this.getDashboardParameters(annotations),\n };\n\n return clusterDetails;\n });\n }\n\n private getDashboardParameters(\n annotations: Record<string, string>,\n ): JsonObject | undefined {\n const dashboardParamsString =\n annotations[ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];\n if (dashboardParamsString) {\n try {\n const dashboardParams = JSON.parse(dashboardParamsString);\n return isObject(dashboardParams) ? dashboardParams : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_API_SERVER","ANNOTATION_KUBERNETES_API_SERVER_CA","ANNOTATION_KUBERNETES_AUTH_PROVIDER","CATALOG_FILTER_EXISTS","ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP","ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY","ANNOTATION_KUBERNETES_DASHBOARD_URL","ANNOTATION_KUBERNETES_DASHBOARD_APP","ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS"],"mappings":";;;;;AAkCA,SAAS,SAAS,GAAiC,EAAA;AACjD,EAAO,OAAA,OAAO,QAAQ,QAAY,IAAA,GAAA,KAAQ,QAAQ,CAAC,KAAA,CAAM,QAAQ,GAAG,CAAA,CAAA;AACtE,CAAA;AAEO,MAAM,qBAA4D,CAAA;AAAA,EAC/D,aAAA,CAAA;AAAA,EACA,IAAA,CAAA;AAAA,EAER,WAAA,CAAY,eAA2B,IAAmB,EAAA;AACxD,IAAA,IAAA,CAAK,aAAgB,GAAA,aAAA,CAAA;AACrB,IAAA,IAAA,CAAK,IAAO,GAAA,IAAA,CAAA;AAAA,GACd;AAAA,EAEA,OAAO,UACL,CAAA,UAAA,EACA,IACuB,EAAA;AACvB,IAAO,OAAA,IAAI,qBAAsB,CAAA,UAAA,EAAY,IAAI,CAAA,CAAA;AAAA,GACnD;AAAA,EAEA,MAAM,YAAY,OAEY,EAAA;AAC5B,IAAM,MAAA,YAAA,GAAe,wBAAwBA,uDAAgC,CAAA,CAAA,CAAA;AAC7E,IAAM,MAAA,cAAA,GAAiB,wBAAwBC,0DAAmC,CAAA,CAAA,CAAA;AAClF,IAAM,MAAA,eAAA,GAAkB,wBAAwBC,0DAAmC,CAAA,CAAA,CAAA;AAEnF,IAAA,MAAM,MAA0C,GAAA;AAAA,MAC9C,IAAM,EAAA,UAAA;AAAA,MACN,WAAa,EAAA,oBAAA;AAAA,MACb,CAAC,YAAY,GAAGC,mCAAA;AAAA,MAChB,CAAC,cAAc,GAAGA,mCAAA;AAAA,MAClB,CAAC,eAAe,GAAGA,mCAAA;AAAA,KACrB,CAAA;AAEA,IAAM,MAAA,QAAA,GAAW,MAAM,IAAA,CAAK,aAAc,CAAA,WAAA;AAAA,MACxC;AAAA,QACE,MAAA,EAAQ,CAAC,MAAM,CAAA;AAAA,OACjB;AAAA,MACA,SAAS,WACL,GAAA;AAAA,QACE,KACE,EAAA,CAAA,MAAM,IAAK,CAAA,IAAA,CAAK,qBAAsB,CAAA;AAAA,UACpC,YAAY,OAAQ,CAAA,WAAA;AAAA,UACpB,cAAgB,EAAA,SAAA;AAAA,SACjB,CACD,EAAA,KAAA;AAAA,OAEJ,GAAA,KAAA,CAAA;AAAA,KACN,CAAA;AACA,IAAO,OAAA,QAAA,CAAS,KAAM,CAAA,GAAA,CAAI,CAAU,MAAA,KAAA;AAClC,MAAM,MAAA,WAAA,GAAc,OAAO,QAAS,CAAA,WAAA,CAAA;AACpC,MAAA,MAAM,cAAiC,GAAA;AAAA,QACrC,IAAA,EAAM,OAAO,QAAS,CAAA,IAAA;AAAA,QACtB,KAAA,EAAO,OAAO,QAAS,CAAA,KAAA;AAAA,QACvB,GAAA,EAAK,YAAYH,uDAAgC,CAAA;AAAA,QACjD,YAAc,EAAA,WAAA;AAAA,QACd,MAAA,EAAQ,YAAYC,0DAAmC,CAAA;AAAA,QACvD,iBAAA,EACE,WAAY,CAAAG,gEAAyC,CAAM,KAAA,MAAA;AAAA,QAC7D,aAAA,EACE,WAAY,CAAAC,4DAAqC,CAAM,KAAA,MAAA;AAAA,QACzD,YAAA,EAAc,YAAYC,0DAAmC,CAAA;AAAA,QAC7D,YAAA,EAAc,YAAYC,0DAAmC,CAAA;AAAA,QAC7D,mBAAA,EAAqB,IAAK,CAAA,sBAAA,CAAuB,WAAW,CAAA;AAAA,OAC9D,CAAA;AAEA,MAAO,OAAA,cAAA,CAAA;AAAA,KACR,CAAA,CAAA;AAAA,GACH;AAAA,EAEQ,uBACN,WACwB,EAAA;AACxB,IAAM,MAAA,qBAAA,GACJ,YAAYC,iEAA0C,CAAA,CAAA;AACxD,IAAA,IAAI,qBAAuB,EAAA;AACzB,MAAI,IAAA;AACF,QAAM,MAAA,eAAA,GAAkB,IAAK,CAAA,KAAA,CAAM,qBAAqB,CAAA,CAAA;AACxD,QAAO,OAAA,QAAA,CAAS,eAAe,CAAA,GAAI,eAAkB,GAAA,KAAA,CAAA,CAAA;AAAA,OAC/C,CAAA,MAAA;AACN,QAAO,OAAA,KAAA,CAAA,CAAA;AAAA,OACT;AAAA,KACF;AACA,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACT;AACF;;;;"}
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
4
+
5
+ class ConfigClusterLocator {
6
+ clusterDetails;
7
+ constructor(clusterDetails) {
8
+ this.clusterDetails = clusterDetails;
9
+ }
10
+ static fromConfig(config, authStrategy) {
11
+ const clusterNames = /* @__PURE__ */ new Set();
12
+ return new ConfigClusterLocator(
13
+ config.getConfigArray("clusters").map((c) => {
14
+ const authMetadataBlock = c.getOptional("authMetadata");
15
+ const name = c.getString("name");
16
+ if (clusterNames.has(name)) {
17
+ throw new Error(`Duplicate cluster name '${name}'`);
18
+ }
19
+ clusterNames.add(name);
20
+ const authProvider = authMetadataBlock?.[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER] ?? c.getOptionalString("authProvider");
21
+ if (!authProvider) {
22
+ throw new Error(
23
+ `cluster '${name}' has no auth provider configured; this must be specified via the 'authProvider' or 'authMetadata.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`
24
+ );
25
+ }
26
+ const title = c.getOptionalString("title");
27
+ const clusterDetails = {
28
+ name,
29
+ ...title && { title },
30
+ url: c.getString("url"),
31
+ skipTLSVerify: c.getOptionalBoolean("skipTLSVerify") ?? false,
32
+ skipMetricsLookup: c.getOptionalBoolean("skipMetricsLookup") ?? false,
33
+ caData: c.getOptionalString("caData"),
34
+ caFile: c.getOptionalString("caFile"),
35
+ authMetadata: {
36
+ [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider,
37
+ ...ConfigClusterLocator.parseAuthMetadata(c),
38
+ ...authMetadataBlock
39
+ }
40
+ };
41
+ const customResources = c.getOptionalConfigArray("customResources");
42
+ if (customResources) {
43
+ clusterDetails.customResources = customResources.map((cr) => {
44
+ return {
45
+ group: cr.getString("group"),
46
+ apiVersion: cr.getString("apiVersion"),
47
+ plural: cr.getString("plural")
48
+ };
49
+ });
50
+ }
51
+ const dashboardUrl = c.getOptionalString("dashboardUrl");
52
+ if (dashboardUrl) {
53
+ clusterDetails.dashboardUrl = dashboardUrl;
54
+ }
55
+ const dashboardApp = c.getOptionalString("dashboardApp");
56
+ if (dashboardApp) {
57
+ clusterDetails.dashboardApp = dashboardApp;
58
+ }
59
+ if (c.has("dashboardParameters")) {
60
+ clusterDetails.dashboardParameters = c.get("dashboardParameters");
61
+ }
62
+ const validationErrors = authStrategy.validateCluster(
63
+ clusterDetails.authMetadata
64
+ );
65
+ if (validationErrors.length !== 0) {
66
+ throw new Error(
67
+ `Invalid cluster '${clusterDetails.name}': ${validationErrors.map((e) => e.message).join(", ")}`
68
+ );
69
+ }
70
+ return clusterDetails;
71
+ })
72
+ );
73
+ }
74
+ static parseAuthMetadata(clusterConfig) {
75
+ const serviceAccountToken = clusterConfig.getOptionalString(
76
+ "serviceAccountToken"
77
+ );
78
+ const assumeRole = clusterConfig.getOptionalString("assumeRole");
79
+ const externalId = clusterConfig.getOptionalString("externalId");
80
+ const oidcTokenProvider = clusterConfig.getOptionalString("oidcTokenProvider");
81
+ return serviceAccountToken || assumeRole || externalId || oidcTokenProvider ? {
82
+ ...serviceAccountToken && { serviceAccountToken },
83
+ ...assumeRole && {
84
+ [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: assumeRole
85
+ },
86
+ ...externalId && {
87
+ [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: externalId
88
+ },
89
+ ...oidcTokenProvider && {
90
+ [pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: oidcTokenProvider
91
+ }
92
+ } : void 0;
93
+ }
94
+ async getClusters() {
95
+ return this.clusterDetails;
96
+ }
97
+ }
98
+
99
+ exports.ConfigClusterLocator = ConfigClusterLocator;
100
+ //# sourceMappingURL=ConfigClusterLocator.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigClusterLocator.cjs.js","sources":["../../src/cluster-locator/ConfigClusterLocator.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 {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE,\n ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID,\n ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,\n} from '@backstage/plugin-kubernetes-common';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\nimport { AuthenticationStrategy } from '../auth';\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(\n config: Config,\n authStrategy: AuthenticationStrategy,\n ): ConfigClusterLocator {\n const clusterNames = new Set();\n return new ConfigClusterLocator(\n config.getConfigArray('clusters').map(c => {\n const authMetadataBlock = c.getOptional<{\n [ANNOTATION_KUBERNETES_AUTH_PROVIDER]?: string;\n }>('authMetadata');\n const name = c.getString('name');\n if (clusterNames.has(name)) {\n throw new Error(`Duplicate cluster name '${name}'`);\n }\n clusterNames.add(name);\n const authProvider =\n authMetadataBlock?.[ANNOTATION_KUBERNETES_AUTH_PROVIDER] ??\n c.getOptionalString('authProvider');\n if (!authProvider) {\n throw new Error(\n `cluster '${name}' has no auth provider configured; this must be ` +\n `specified via the 'authProvider' or ` +\n `'authMetadata.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`,\n );\n }\n const title = c.getOptionalString('title');\n const clusterDetails: ClusterDetails = {\n name,\n ...(title && { title }),\n url: c.getString('url'),\n skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false,\n skipMetricsLookup: c.getOptionalBoolean('skipMetricsLookup') ?? false,\n caData: c.getOptionalString('caData'),\n caFile: c.getOptionalString('caFile'),\n authMetadata: {\n [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider,\n ...ConfigClusterLocator.parseAuthMetadata(c),\n ...authMetadataBlock,\n },\n };\n\n const customResources = c.getOptionalConfigArray('customResources');\n if (customResources) {\n clusterDetails.customResources = customResources.map(cr => {\n return {\n group: cr.getString('group'),\n apiVersion: cr.getString('apiVersion'),\n plural: cr.getString('plural'),\n };\n });\n }\n\n const dashboardUrl = c.getOptionalString('dashboardUrl');\n if (dashboardUrl) {\n clusterDetails.dashboardUrl = dashboardUrl;\n }\n const dashboardApp = c.getOptionalString('dashboardApp');\n if (dashboardApp) {\n clusterDetails.dashboardApp = dashboardApp;\n }\n if (c.has('dashboardParameters')) {\n clusterDetails.dashboardParameters = c.get('dashboardParameters');\n }\n\n const validationErrors = authStrategy.validateCluster(\n clusterDetails.authMetadata,\n );\n if (validationErrors.length !== 0) {\n throw new Error(\n `Invalid cluster '${clusterDetails.name}': ${validationErrors\n .map(e => e.message)\n .join(', ')}`,\n );\n }\n return clusterDetails;\n }),\n );\n }\n\n private static parseAuthMetadata(\n clusterConfig: Config,\n ): Record<string, string> | undefined {\n const serviceAccountToken = clusterConfig.getOptionalString(\n 'serviceAccountToken',\n );\n const assumeRole = clusterConfig.getOptionalString('assumeRole');\n const externalId = clusterConfig.getOptionalString('externalId');\n const oidcTokenProvider =\n clusterConfig.getOptionalString('oidcTokenProvider');\n\n return serviceAccountToken || assumeRole || externalId || oidcTokenProvider\n ? {\n ...(serviceAccountToken && { serviceAccountToken }),\n ...(assumeRole && {\n [ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: assumeRole,\n }),\n ...(externalId && {\n [ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: externalId,\n }),\n ...(oidcTokenProvider && {\n [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: oidcTokenProvider,\n }),\n }\n : undefined;\n }\n\n async getClusters(): Promise<ClusterDetails[]> {\n return this.clusterDetails;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_AUTH_PROVIDER","ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE","ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID","ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER"],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
4
+ var errors = require('@backstage/errors');
5
+ var container = require('@google-cloud/container');
6
+ var runPeriodically = require('../service/runPeriodically.cjs.js');
7
+ var _package = require('../package.json.cjs.js');
8
+
9
+ function _interopNamespaceCompat(e) {
10
+ if (e && typeof e === 'object' && 'default' in e) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var container__namespace = /*#__PURE__*/_interopNamespaceCompat(container);
28
+
29
+ class GkeClusterLocator {
30
+ constructor(options, client, clusterDetails = void 0, hasClusterDetails = false) {
31
+ this.options = options;
32
+ this.client = client;
33
+ this.clusterDetails = clusterDetails;
34
+ this.hasClusterDetails = hasClusterDetails;
35
+ }
36
+ static fromConfigWithClient(config, client, refreshInterval) {
37
+ const matchingResourceLabels = config.getOptionalConfigArray("matchingResourceLabels")?.map((mrl) => {
38
+ return { key: mrl.getString("key"), value: mrl.getString("value") };
39
+ }) ?? [];
40
+ const storeAuthProviderString = config.getOptionalString("authProvider") === "googleServiceAccount" ? "googleServiceAccount" : "google";
41
+ const options = {
42
+ projectId: config.getString("projectId"),
43
+ authProvider: storeAuthProviderString,
44
+ region: config.getOptionalString("region") ?? "-",
45
+ skipTLSVerify: config.getOptionalBoolean("skipTLSVerify") ?? false,
46
+ skipMetricsLookup: config.getOptionalBoolean("skipMetricsLookup") ?? false,
47
+ exposeDashboard: config.getOptionalBoolean("exposeDashboard") ?? false,
48
+ matchingResourceLabels
49
+ };
50
+ const gkeClusterLocator = new GkeClusterLocator(options, client);
51
+ if (refreshInterval) {
52
+ runPeriodically.runPeriodically(
53
+ () => gkeClusterLocator.refreshClusters(),
54
+ refreshInterval.toMillis()
55
+ );
56
+ }
57
+ return gkeClusterLocator;
58
+ }
59
+ // Added an `x-goog-api-client` header to API requests made by the GKE cluster locator to clearly identify API requests from this plugin.
60
+ static fromConfig(config, refreshInterval = void 0) {
61
+ return GkeClusterLocator.fromConfigWithClient(
62
+ config,
63
+ new container__namespace.v1.ClusterManagerClient({
64
+ libName: `backstage/kubernetes-backend.GkeClusterLocator`,
65
+ libVersion: _package.default.version
66
+ }),
67
+ refreshInterval
68
+ );
69
+ }
70
+ async getClusters() {
71
+ if (!this.hasClusterDetails) {
72
+ await this.refreshClusters();
73
+ }
74
+ return this.clusterDetails ?? [];
75
+ }
76
+ // TODO pass caData into the object
77
+ async refreshClusters() {
78
+ const {
79
+ projectId,
80
+ region,
81
+ authProvider,
82
+ skipTLSVerify,
83
+ skipMetricsLookup,
84
+ exposeDashboard,
85
+ matchingResourceLabels
86
+ } = this.options;
87
+ const request = {
88
+ parent: `projects/${projectId}/locations/${region}`
89
+ };
90
+ try {
91
+ const [response] = await this.client.listClusters(request);
92
+ this.clusterDetails = (response.clusters ?? []).filter((r) => {
93
+ return matchingResourceLabels?.every((mrl) => {
94
+ if (!r.resourceLabels) {
95
+ return false;
96
+ }
97
+ return r.resourceLabels[mrl.key] === mrl.value;
98
+ });
99
+ }).map((r) => ({
100
+ // TODO filter out clusters which don't have name or endpoint
101
+ name: r.name ?? "unknown",
102
+ url: `https://${r.endpoint ?? ""}`,
103
+ authMetadata: { [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider },
104
+ skipTLSVerify,
105
+ skipMetricsLookup,
106
+ ...exposeDashboard ? {
107
+ dashboardApp: "gke",
108
+ dashboardParameters: {
109
+ projectId,
110
+ region,
111
+ clusterName: r.name
112
+ }
113
+ } : {}
114
+ }));
115
+ this.hasClusterDetails = true;
116
+ } catch (e) {
117
+ throw new errors.ForwardedError(
118
+ `There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
119
+ e
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ exports.GkeClusterLocator = GkeClusterLocator;
126
+ //# sourceMappingURL=GkeClusterLocator.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GkeClusterLocator.cjs.js","sources":["../../src/cluster-locator/GkeClusterLocator.ts"],"sourcesContent":["/*\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 { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common';\nimport { Config } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport * as container from '@google-cloud/container';\nimport { Duration } from 'luxon';\nimport { runPeriodically } from '../service/runPeriodically';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\nimport packageinfo from '../../package.json';\n\ninterface MatchResourceLabelEntry {\n key: string;\n value: string;\n}\n\ntype GkeClusterLocatorOptions = {\n projectId: string;\n authProvider: string;\n region?: string;\n skipTLSVerify?: boolean;\n skipMetricsLookup?: boolean;\n exposeDashboard?: boolean;\n matchingResourceLabels?: MatchResourceLabelEntry[];\n};\n\nexport class GkeClusterLocator implements KubernetesClustersSupplier {\n constructor(\n private readonly options: GkeClusterLocatorOptions,\n private readonly client: container.v1.ClusterManagerClient,\n private clusterDetails: ClusterDetails[] | undefined = undefined,\n private hasClusterDetails: boolean = false,\n ) {}\n\n static fromConfigWithClient(\n config: Config,\n client: container.v1.ClusterManagerClient,\n refreshInterval?: Duration,\n ): GkeClusterLocator {\n const matchingResourceLabels: MatchResourceLabelEntry[] =\n config.getOptionalConfigArray('matchingResourceLabels')?.map(mrl => {\n return { key: mrl.getString('key'), value: mrl.getString('value') };\n }) ?? [];\n\n const storeAuthProviderString =\n config.getOptionalString('authProvider') === 'googleServiceAccount'\n ? 'googleServiceAccount'\n : 'google';\n\n const options = {\n projectId: config.getString('projectId'),\n authProvider: storeAuthProviderString,\n region: config.getOptionalString('region') ?? '-',\n skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false,\n skipMetricsLookup:\n config.getOptionalBoolean('skipMetricsLookup') ?? false,\n exposeDashboard: config.getOptionalBoolean('exposeDashboard') ?? false,\n matchingResourceLabels,\n };\n const gkeClusterLocator = new GkeClusterLocator(options, client);\n if (refreshInterval) {\n runPeriodically(\n () => gkeClusterLocator.refreshClusters(),\n refreshInterval.toMillis(),\n );\n }\n return gkeClusterLocator;\n }\n\n // Added an `x-goog-api-client` header to API requests made by the GKE cluster locator to clearly identify API requests from this plugin.\n static fromConfig(\n config: Config,\n refreshInterval: Duration | undefined = undefined,\n ): GkeClusterLocator {\n return GkeClusterLocator.fromConfigWithClient(\n config,\n new container.v1.ClusterManagerClient({\n libName: `backstage/kubernetes-backend.GkeClusterLocator`,\n libVersion: packageinfo.version,\n }),\n refreshInterval,\n );\n }\n\n async getClusters(): Promise<ClusterDetails[]> {\n if (!this.hasClusterDetails) {\n // refresh at least once when first called, when retries are disabled and in tests\n await this.refreshClusters();\n }\n return this.clusterDetails ?? [];\n }\n\n // TODO pass caData into the object\n async refreshClusters(): Promise<void> {\n const {\n projectId,\n region,\n authProvider,\n skipTLSVerify,\n skipMetricsLookup,\n exposeDashboard,\n matchingResourceLabels,\n } = this.options;\n const request = {\n parent: `projects/${projectId}/locations/${region}`,\n };\n\n try {\n const [response] = await this.client.listClusters(request);\n this.clusterDetails = (response.clusters ?? [])\n .filter(r => {\n return matchingResourceLabels?.every(mrl => {\n if (!r.resourceLabels) {\n return false;\n }\n return r.resourceLabels[mrl.key] === mrl.value;\n });\n })\n .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 authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider },\n skipTLSVerify,\n skipMetricsLookup,\n ...(exposeDashboard\n ? {\n dashboardApp: 'gke',\n dashboardParameters: {\n projectId,\n region,\n clusterName: r.name,\n },\n }\n : {}),\n }));\n this.hasClusterDetails = true;\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"],"names":["runPeriodically","container","packageinfo","ANNOTATION_KUBERNETES_AUTH_PROVIDER","ForwardedError"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
4
+ var dns = require('node:dns');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var dns__default = /*#__PURE__*/_interopDefaultCompat(dns);
9
+
10
+ class LocalKubectlProxyClusterLocator {
11
+ clusterDetails;
12
+ // verbatim: when false, IPv4 addresses are placed before IPv6 addresses, ignoring the order from the DNS resolver
13
+ // By default kubectl proxy listens on 127.0.0.1 instead of [::1]
14
+ lookupPromise = dns__default.default.promises.lookup("localhost", { verbatim: false });
15
+ constructor() {
16
+ this.clusterDetails = [
17
+ {
18
+ name: "local",
19
+ url: "http://localhost:8001",
20
+ authMetadata: {
21
+ [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: "localKubectlProxy"
22
+ },
23
+ skipMetricsLookup: true
24
+ }
25
+ ];
26
+ }
27
+ async getClusters() {
28
+ const lookupResolution = await this.lookupPromise;
29
+ this.clusterDetails[0].url = `http://${lookupResolution.address}:8001`;
30
+ return this.clusterDetails;
31
+ }
32
+ }
33
+
34
+ exports.LocalKubectlProxyClusterLocator = LocalKubectlProxyClusterLocator;
35
+ //# sourceMappingURL=LocalKubectlProxyLocator.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalKubectlProxyLocator.cjs.js","sources":["../../src/cluster-locator/LocalKubectlProxyLocator.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 { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport dns from 'node:dns';\n\nexport class LocalKubectlProxyClusterLocator\n implements KubernetesClustersSupplier\n{\n private readonly clusterDetails: ClusterDetails[];\n // verbatim: when false, IPv4 addresses are placed before IPv6 addresses, ignoring the order from the DNS resolver\n // By default kubectl proxy listens on 127.0.0.1 instead of [::1]\n private lookupPromise = dns.promises.lookup('localhost', { verbatim: false });\n\n public constructor() {\n this.clusterDetails = [\n {\n name: 'local',\n url: 'http://localhost:8001',\n authMetadata: {\n [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'localKubectlProxy',\n },\n skipMetricsLookup: true,\n },\n ];\n }\n\n async getClusters(): Promise<ClusterDetails[]> {\n const lookupResolution = await this.lookupPromise;\n this.clusterDetails[0].url = `http://${lookupResolution.address}:8001`;\n return this.clusterDetails;\n }\n}\n"],"names":["dns","ANNOTATION_KUBERNETES_AUTH_PROVIDER"],"mappings":";;;;;;;;;AAuBO,MAAM,+BAEb,CAAA;AAAA,EACmB,cAAA,CAAA;AAAA;AAAA;AAAA,EAGT,aAAA,GAAgBA,qBAAI,QAAS,CAAA,MAAA,CAAO,aAAa,EAAE,QAAA,EAAU,OAAO,CAAA,CAAA;AAAA,EAErE,WAAc,GAAA;AACnB,IAAA,IAAA,CAAK,cAAiB,GAAA;AAAA,MACpB;AAAA,QACE,IAAM,EAAA,OAAA;AAAA,QACN,GAAK,EAAA,uBAAA;AAAA,QACL,YAAc,EAAA;AAAA,UACZ,CAACC,0DAAmC,GAAG,mBAAA;AAAA,SACzC;AAAA,QACA,iBAAmB,EAAA,IAAA;AAAA,OACrB;AAAA,KACF,CAAA;AAAA,GACF;AAAA,EAEA,MAAM,WAAyC,GAAA;AAC7C,IAAM,MAAA,gBAAA,GAAmB,MAAM,IAAK,CAAA,aAAA,CAAA;AACpC,IAAA,IAAA,CAAK,eAAe,CAAC,CAAA,CAAE,GAAM,GAAA,CAAA,OAAA,EAAU,iBAAiB,OAAO,CAAA,KAAA,CAAA,CAAA;AAC/D,IAAA,OAAO,IAAK,CAAA,cAAA,CAAA;AAAA,GACd;AACF;;;;"}
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ var ConfigClusterLocator = require('./ConfigClusterLocator.cjs.js');
4
+ var GkeClusterLocator = require('./GkeClusterLocator.cjs.js');
5
+ var CatalogClusterLocator = require('./CatalogClusterLocator.cjs.js');
6
+ var LocalKubectlProxyLocator = require('./LocalKubectlProxyLocator.cjs.js');
7
+
8
+ class CombinedClustersSupplier {
9
+ constructor(clusterSuppliers, logger) {
10
+ this.clusterSuppliers = clusterSuppliers;
11
+ this.logger = logger;
12
+ }
13
+ async getClusters(options) {
14
+ const clusters = await Promise.all(
15
+ this.clusterSuppliers.map((supplier) => supplier.getClusters(options))
16
+ ).then((res) => {
17
+ return res.flat();
18
+ }).catch((e) => {
19
+ throw e;
20
+ });
21
+ return this.warnDuplicates(clusters);
22
+ }
23
+ warnDuplicates(clusters) {
24
+ const clusterNames = /* @__PURE__ */ new Set();
25
+ const duplicatedNames = /* @__PURE__ */ new Set();
26
+ for (const clusterName of clusters.map((c) => c.name)) {
27
+ if (clusterNames.has(clusterName)) {
28
+ duplicatedNames.add(clusterName);
29
+ } else {
30
+ clusterNames.add(clusterName);
31
+ }
32
+ }
33
+ for (const clusterName of duplicatedNames) {
34
+ this.logger.warn(`Duplicate cluster name '${clusterName}'`);
35
+ }
36
+ return clusters;
37
+ }
38
+ }
39
+ const getCombinedClusterSupplier = (rootConfig, catalogClient, authStrategy, logger, refreshInterval = void 0, auth) => {
40
+ const clusterSuppliers = rootConfig.getConfigArray("kubernetes.clusterLocatorMethods").map((clusterLocatorMethod) => {
41
+ const type = clusterLocatorMethod.getString("type");
42
+ switch (type) {
43
+ case "catalog":
44
+ return CatalogClusterLocator.CatalogClusterLocator.fromConfig(catalogClient, auth);
45
+ case "localKubectlProxy":
46
+ return new LocalKubectlProxyLocator.LocalKubectlProxyClusterLocator();
47
+ case "config":
48
+ return ConfigClusterLocator.ConfigClusterLocator.fromConfig(
49
+ clusterLocatorMethod,
50
+ authStrategy
51
+ );
52
+ case "gke":
53
+ return GkeClusterLocator.GkeClusterLocator.fromConfig(
54
+ clusterLocatorMethod,
55
+ refreshInterval
56
+ );
57
+ default:
58
+ throw new Error(
59
+ `Unsupported kubernetes.clusterLocatorMethods: "${type}"`
60
+ );
61
+ }
62
+ });
63
+ return new CombinedClustersSupplier(clusterSuppliers, logger);
64
+ };
65
+
66
+ exports.getCombinedClusterSupplier = getCombinedClusterSupplier;
67
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../../src/cluster-locator/index.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 { CatalogApi } from '@backstage/catalog-client';\nimport { Config } from '@backstage/config';\nimport { Duration } from 'luxon';\nimport { ClusterDetails, KubernetesClustersSupplier } from '../types/types';\nimport { AuthenticationStrategy } from '../auth/types';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\nimport { CatalogClusterLocator } from './CatalogClusterLocator';\nimport { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\nclass CombinedClustersSupplier implements KubernetesClustersSupplier {\n constructor(\n readonly clusterSuppliers: KubernetesClustersSupplier[],\n readonly logger: LoggerService,\n ) {}\n\n async getClusters(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const clusters = await Promise.all(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n )\n .then(res => {\n return res.flat();\n })\n .catch(e => {\n throw e;\n });\n return this.warnDuplicates(clusters);\n }\n\n private warnDuplicates(clusters: ClusterDetails[]): ClusterDetails[] {\n const clusterNames = new Set<string>();\n const duplicatedNames = new Set<string>();\n for (const clusterName of clusters.map(c => c.name)) {\n if (clusterNames.has(clusterName)) {\n duplicatedNames.add(clusterName);\n } else {\n clusterNames.add(clusterName);\n }\n }\n for (const clusterName of duplicatedNames) {\n this.logger.warn(`Duplicate cluster name '${clusterName}'`);\n }\n return clusters;\n }\n}\n\nexport const getCombinedClusterSupplier = (\n rootConfig: Config,\n catalogClient: CatalogApi,\n authStrategy: AuthenticationStrategy,\n logger: LoggerService,\n refreshInterval: Duration | undefined = undefined,\n auth: AuthService,\n): KubernetesClustersSupplier => {\n const clusterSuppliers = rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'catalog':\n return CatalogClusterLocator.fromConfig(catalogClient, auth);\n case 'localKubectlProxy':\n return new LocalKubectlProxyClusterLocator();\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n authStrategy,\n );\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n refreshInterval,\n );\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n });\n\n return new CombinedClustersSupplier(clusterSuppliers, logger);\n};\n"],"names":["CatalogClusterLocator","LocalKubectlProxyClusterLocator","ConfigClusterLocator","GkeClusterLocator"],"mappings":";;;;;;;AA+BA,MAAM,wBAA+D,CAAA;AAAA,EACnE,WAAA,CACW,kBACA,MACT,EAAA;AAFS,IAAA,IAAA,CAAA,gBAAA,GAAA,gBAAA,CAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA,CAAA;AAAA,GACR;AAAA,EAEH,MAAM,YAAY,OAEY,EAAA;AAC5B,IAAM,MAAA,QAAA,GAAW,MAAM,OAAQ,CAAA,GAAA;AAAA,MAC7B,KAAK,gBAAiB,CAAA,GAAA,CAAI,cAAY,QAAS,CAAA,WAAA,CAAY,OAAO,CAAC,CAAA;AAAA,KACrE,CACG,KAAK,CAAO,GAAA,KAAA;AACX,MAAA,OAAO,IAAI,IAAK,EAAA,CAAA;AAAA,KACjB,CACA,CAAA,KAAA,CAAM,CAAK,CAAA,KAAA;AACV,MAAM,MAAA,CAAA,CAAA;AAAA,KACP,CAAA,CAAA;AACH,IAAO,OAAA,IAAA,CAAK,eAAe,QAAQ,CAAA,CAAA;AAAA,GACrC;AAAA,EAEQ,eAAe,QAA8C,EAAA;AACnE,IAAM,MAAA,YAAA,uBAAmB,GAAY,EAAA,CAAA;AACrC,IAAM,MAAA,eAAA,uBAAsB,GAAY,EAAA,CAAA;AACxC,IAAA,KAAA,MAAW,eAAe,QAAS,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA,CAAA,CAAE,IAAI,CAAG,EAAA;AACnD,MAAI,IAAA,YAAA,CAAa,GAAI,CAAA,WAAW,CAAG,EAAA;AACjC,QAAA,eAAA,CAAgB,IAAI,WAAW,CAAA,CAAA;AAAA,OAC1B,MAAA;AACL,QAAA,YAAA,CAAa,IAAI,WAAW,CAAA,CAAA;AAAA,OAC9B;AAAA,KACF;AACA,IAAA,KAAA,MAAW,eAAe,eAAiB,EAAA;AACzC,MAAA,IAAA,CAAK,MAAO,CAAA,IAAA,CAAK,CAA2B,wBAAA,EAAA,WAAW,CAAG,CAAA,CAAA,CAAA,CAAA;AAAA,KAC5D;AACA,IAAO,OAAA,QAAA,CAAA;AAAA,GACT;AACF,CAAA;AAEa,MAAA,0BAAA,GAA6B,CACxC,UACA,EAAA,aAAA,EACA,cACA,MACA,EAAA,eAAA,GAAwC,QACxC,IAC+B,KAAA;AAC/B,EAAA,MAAM,mBAAmB,UACtB,CAAA,cAAA,CAAe,kCAAkC,CAAA,CACjD,IAAI,CAAwB,oBAAA,KAAA;AAC3B,IAAM,MAAA,IAAA,GAAO,oBAAqB,CAAA,SAAA,CAAU,MAAM,CAAA,CAAA;AAClD,IAAA,QAAQ,IAAM;AAAA,MACZ,KAAK,SAAA;AACH,QAAO,OAAAA,2CAAA,CAAsB,UAAW,CAAA,aAAA,EAAe,IAAI,CAAA,CAAA;AAAA,MAC7D,KAAK,mBAAA;AACH,QAAA,OAAO,IAAIC,wDAAgC,EAAA,CAAA;AAAA,MAC7C,KAAK,QAAA;AACH,QAAA,OAAOC,yCAAqB,CAAA,UAAA;AAAA,UAC1B,oBAAA;AAAA,UACA,YAAA;AAAA,SACF,CAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,OAAOC,mCAAkB,CAAA,UAAA;AAAA,UACvB,oBAAA;AAAA,UACA,eAAA;AAAA,SACF,CAAA;AAAA,MACF;AACE,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,kDAAkD,IAAI,CAAA,CAAA,CAAA;AAAA,SACxD,CAAA;AAAA,KACJ;AAAA,GACD,CAAA,CAAA;AAEH,EAAO,OAAA,IAAI,wBAAyB,CAAA,gBAAA,EAAkB,MAAM,CAAA,CAAA;AAC9D;;;;"}