@backstage/plugin-kubernetes-react 0.5.19-next.0 → 0.5.19-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @backstage/plugin-kubernetes-react
2
2
 
3
+ ## 0.5.19-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e68cb8a: Added optional clustersCacheTtlMs option to KubernetesBackendClient that caches getClusters() responses for the specified duration, avoiding repeated /clusters requests when multiple proxy calls resolve cluster auth in quick succession.
8
+
3
9
  ## 0.5.19-next.0
4
10
 
5
11
  ### Patch Changes
@@ -5,10 +5,20 @@ class KubernetesBackendClient {
5
5
  discoveryApi;
6
6
  fetchApi;
7
7
  kubernetesAuthProvidersApi;
8
+ clustersCacheTtlMs;
9
+ clustersCache;
10
+ clustersCacheTimestamp = 0;
11
+ clustersFetchPromise;
8
12
  constructor(options) {
9
13
  this.discoveryApi = options.discoveryApi;
10
14
  this.fetchApi = options.fetchApi;
11
15
  this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi;
16
+ if (options.clustersCacheTtlMs !== void 0 && !(Number.isFinite(options.clustersCacheTtlMs) && options.clustersCacheTtlMs >= 0)) {
17
+ throw new Error(
18
+ `clustersCacheTtlMs must be a finite number >= 0, got ${options.clustersCacheTtlMs}`
19
+ );
20
+ }
21
+ this.clustersCacheTtlMs = options.clustersCacheTtlMs;
12
22
  }
13
23
  async handleResponse(response) {
14
24
  if (!response.ok) {
@@ -70,9 +80,32 @@ class KubernetesBackendClient {
70
80
  });
71
81
  }
72
82
  async getClusters() {
73
- const url = `${await this.discoveryApi.getBaseUrl("kubernetes")}/clusters`;
74
- const response = await this.fetchApi.fetch(url);
75
- return (await this.handleResponse(response)).items;
83
+ if (this.clustersCacheTtlMs !== void 0 && this.clustersCache && Date.now() - this.clustersCacheTimestamp < this.clustersCacheTtlMs) {
84
+ return this.clustersCache;
85
+ }
86
+ if (this.clustersFetchPromise) {
87
+ return this.clustersFetchPromise;
88
+ }
89
+ const fetchPromise = (async () => {
90
+ const url = `${await this.discoveryApi.getBaseUrl(
91
+ "kubernetes"
92
+ )}/clusters`;
93
+ const response = await this.fetchApi.fetch(url);
94
+ return (await this.handleResponse(response)).items;
95
+ })();
96
+ if (this.clustersCacheTtlMs !== void 0) {
97
+ this.clustersFetchPromise = fetchPromise;
98
+ }
99
+ try {
100
+ const clusters = await fetchPromise;
101
+ if (this.clustersCacheTtlMs !== void 0) {
102
+ this.clustersCache = clusters;
103
+ this.clustersCacheTimestamp = Date.now();
104
+ }
105
+ return clusters;
106
+ } finally {
107
+ this.clustersFetchPromise = void 0;
108
+ }
76
109
  }
77
110
  async proxy(options) {
78
111
  const { authProvider, oidcTokenProvider } = await this.getCluster(
@@ -1 +1 @@
1
- {"version":3,"file":"KubernetesBackendClient.esm.js","sources":["../../src/api/KubernetesBackendClient.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 { KubernetesApi } from './types';\nimport {\n KubernetesRequestBody,\n ObjectsByEntityResponse,\n WorkloadsByEntityRequest,\n CustomObjectsByEntityRequest,\n} from '@backstage/plugin-kubernetes-common';\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider';\nimport { NotFoundError } from '@backstage/errors';\n\n/** @public */\nexport class KubernetesBackendClient implements KubernetesApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly kubernetesAuthProvidersApi: KubernetesAuthProvidersApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n kubernetesAuthProvidersApi: KubernetesAuthProvidersApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi;\n }\n\n private async handleResponse(response: Response): Promise<any> {\n if (!response.ok) {\n const payload = await response.text();\n let message;\n switch (response.status) {\n case 404:\n message =\n 'Could not find the Kubernetes Backend (HTTP 404). Make sure the plugin has been fully installed.';\n break;\n default:\n message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;\n }\n throw new Error(message);\n }\n\n return await response.json();\n }\n\n private async postRequired(path: string, requestBody: any): Promise<any> {\n const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n return this.handleResponse(response);\n }\n\n public async getCluster(clusterName: string): Promise<{\n name: string;\n authProvider: string;\n oidcTokenProvider?: string;\n }> {\n const cluster = await this.getClusters().then(clusters =>\n clusters.find(c => c.name === clusterName),\n );\n if (!cluster) {\n throw new NotFoundError(`Cluster ${clusterName} not found`);\n }\n\n return cluster;\n }\n\n private async getCredentials(\n authProvider: string,\n oidcTokenProvider?: string,\n ): Promise<{ token?: string }> {\n return await this.kubernetesAuthProvidersApi.getCredentials(\n authProvider === 'oidc'\n ? `${authProvider}.${oidcTokenProvider}`\n : authProvider,\n );\n }\n\n async getObjectsByEntity(\n requestBody: KubernetesRequestBody,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired(\n `/services/${requestBody.entity.metadata.name}`,\n requestBody,\n );\n }\n\n async getWorkloadsByEntity(\n request: WorkloadsByEntityRequest,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired('/resources/workloads/query', {\n auth: request.auth,\n entityRef: stringifyEntityRef(request.entity),\n });\n }\n\n async getCustomObjectsByEntity(\n request: CustomObjectsByEntityRequest,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired(`/resources/custom/query`, {\n entityRef: stringifyEntityRef(request.entity),\n auth: request.auth,\n customResources: request.customResources,\n });\n }\n\n async getClusters(): Promise<{ name: string; authProvider: string }[]> {\n const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`;\n const response = await this.fetchApi.fetch(url);\n\n return (await this.handleResponse(response)).items;\n }\n\n async proxy(options: {\n clusterName: string;\n path: string;\n init?: RequestInit;\n }): Promise<Response> {\n const { authProvider, oidcTokenProvider } = await this.getCluster(\n options.clusterName,\n );\n const kubernetesCredentials = await this.getCredentials(\n authProvider,\n oidcTokenProvider,\n );\n const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/proxy${\n options.path\n }`;\n const headers = KubernetesBackendClient.getKubernetesHeaders(\n options,\n kubernetesCredentials?.token,\n authProvider,\n oidcTokenProvider,\n );\n return await this.fetchApi.fetch(url, { ...options.init, headers });\n }\n\n private static getKubernetesHeaders(\n options: {\n clusterName: string;\n path: string;\n init?: RequestInit;\n },\n k8sToken: string | undefined,\n authProvider: string,\n oidcTokenProvider: string | undefined,\n ) {\n const kubernetesAuthHeader =\n KubernetesBackendClient.getKubernetesAuthHeaderByAuthProvider(\n authProvider,\n oidcTokenProvider,\n );\n return {\n ...options.init?.headers,\n [`Backstage-Kubernetes-Cluster`]: options.clusterName,\n ...(k8sToken && {\n [kubernetesAuthHeader]: k8sToken,\n }),\n };\n }\n\n private static getKubernetesAuthHeaderByAuthProvider(\n authProvider: string,\n oidcTokenProvider: string | undefined,\n ): string {\n let header: string = 'Backstage-Kubernetes-Authorization';\n\n header = header.concat('-', authProvider);\n\n if (oidcTokenProvider) header = header.concat('-', oidcTokenProvider);\n\n return header;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"KubernetesBackendClient.esm.js","sources":["../../src/api/KubernetesBackendClient.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 { KubernetesApi } from './types';\nimport {\n KubernetesRequestBody,\n ObjectsByEntityResponse,\n WorkloadsByEntityRequest,\n CustomObjectsByEntityRequest,\n} from '@backstage/plugin-kubernetes-common';\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider';\nimport { NotFoundError } from '@backstage/errors';\n\n/** @public */\nexport class KubernetesBackendClient implements KubernetesApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly kubernetesAuthProvidersApi: KubernetesAuthProvidersApi;\n private readonly clustersCacheTtlMs: number | undefined;\n private clustersCache: { name: string; authProvider: string }[] | undefined;\n private clustersCacheTimestamp = 0;\n private clustersFetchPromise:\n | Promise<{ name: string; authProvider: string }[]>\n | undefined;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n kubernetesAuthProvidersApi: KubernetesAuthProvidersApi;\n /**\n * When set, `getClusters()` results are cached for this many milliseconds.\n * Useful when many proxy calls resolve cluster auth in quick succession.\n */\n clustersCacheTtlMs?: number;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi;\n if (\n options.clustersCacheTtlMs !== undefined &&\n !(\n Number.isFinite(options.clustersCacheTtlMs) &&\n options.clustersCacheTtlMs >= 0\n )\n ) {\n throw new Error(\n `clustersCacheTtlMs must be a finite number >= 0, got ${options.clustersCacheTtlMs}`,\n );\n }\n this.clustersCacheTtlMs = options.clustersCacheTtlMs;\n }\n\n private async handleResponse(response: Response): Promise<any> {\n if (!response.ok) {\n const payload = await response.text();\n let message;\n switch (response.status) {\n case 404:\n message =\n 'Could not find the Kubernetes Backend (HTTP 404). Make sure the plugin has been fully installed.';\n break;\n default:\n message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;\n }\n throw new Error(message);\n }\n\n return await response.json();\n }\n\n private async postRequired(path: string, requestBody: any): Promise<any> {\n const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`;\n const response = await this.fetchApi.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n return this.handleResponse(response);\n }\n\n public async getCluster(clusterName: string): Promise<{\n name: string;\n authProvider: string;\n oidcTokenProvider?: string;\n }> {\n const cluster = await this.getClusters().then(clusters =>\n clusters.find(c => c.name === clusterName),\n );\n if (!cluster) {\n throw new NotFoundError(`Cluster ${clusterName} not found`);\n }\n\n return cluster;\n }\n\n private async getCredentials(\n authProvider: string,\n oidcTokenProvider?: string,\n ): Promise<{ token?: string }> {\n return await this.kubernetesAuthProvidersApi.getCredentials(\n authProvider === 'oidc'\n ? `${authProvider}.${oidcTokenProvider}`\n : authProvider,\n );\n }\n\n async getObjectsByEntity(\n requestBody: KubernetesRequestBody,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired(\n `/services/${requestBody.entity.metadata.name}`,\n requestBody,\n );\n }\n\n async getWorkloadsByEntity(\n request: WorkloadsByEntityRequest,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired('/resources/workloads/query', {\n auth: request.auth,\n entityRef: stringifyEntityRef(request.entity),\n });\n }\n\n async getCustomObjectsByEntity(\n request: CustomObjectsByEntityRequest,\n ): Promise<ObjectsByEntityResponse> {\n return await this.postRequired(`/resources/custom/query`, {\n entityRef: stringifyEntityRef(request.entity),\n auth: request.auth,\n customResources: request.customResources,\n });\n }\n\n async getClusters(): Promise<{ name: string; authProvider: string }[]> {\n if (\n this.clustersCacheTtlMs !== undefined &&\n this.clustersCache &&\n Date.now() - this.clustersCacheTimestamp < this.clustersCacheTtlMs\n ) {\n return this.clustersCache;\n }\n\n if (this.clustersFetchPromise) {\n return this.clustersFetchPromise;\n }\n\n const fetchPromise = (async () => {\n const url = `${await this.discoveryApi.getBaseUrl(\n 'kubernetes',\n )}/clusters`;\n const response = await this.fetchApi.fetch(url);\n return (await this.handleResponse(response)).items;\n })();\n\n if (this.clustersCacheTtlMs !== undefined) {\n this.clustersFetchPromise = fetchPromise;\n }\n\n try {\n const clusters = await fetchPromise;\n if (this.clustersCacheTtlMs !== undefined) {\n this.clustersCache = clusters;\n this.clustersCacheTimestamp = Date.now();\n }\n return clusters;\n } finally {\n this.clustersFetchPromise = undefined;\n }\n }\n\n async proxy(options: {\n clusterName: string;\n path: string;\n init?: RequestInit;\n }): Promise<Response> {\n const { authProvider, oidcTokenProvider } = await this.getCluster(\n options.clusterName,\n );\n const kubernetesCredentials = await this.getCredentials(\n authProvider,\n oidcTokenProvider,\n );\n const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/proxy${\n options.path\n }`;\n const headers = KubernetesBackendClient.getKubernetesHeaders(\n options,\n kubernetesCredentials?.token,\n authProvider,\n oidcTokenProvider,\n );\n return await this.fetchApi.fetch(url, { ...options.init, headers });\n }\n\n private static getKubernetesHeaders(\n options: {\n clusterName: string;\n path: string;\n init?: RequestInit;\n },\n k8sToken: string | undefined,\n authProvider: string,\n oidcTokenProvider: string | undefined,\n ) {\n const kubernetesAuthHeader =\n KubernetesBackendClient.getKubernetesAuthHeaderByAuthProvider(\n authProvider,\n oidcTokenProvider,\n );\n return {\n ...options.init?.headers,\n [`Backstage-Kubernetes-Cluster`]: options.clusterName,\n ...(k8sToken && {\n [kubernetesAuthHeader]: k8sToken,\n }),\n };\n }\n\n private static getKubernetesAuthHeaderByAuthProvider(\n authProvider: string,\n oidcTokenProvider: string | undefined,\n ): string {\n let header: string = 'Backstage-Kubernetes-Authorization';\n\n header = header.concat('-', authProvider);\n\n if (oidcTokenProvider) header = header.concat('-', oidcTokenProvider);\n\n return header;\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -243,10 +243,19 @@ declare class KubernetesBackendClient implements KubernetesApi {
243
243
  private readonly discoveryApi;
244
244
  private readonly fetchApi;
245
245
  private readonly kubernetesAuthProvidersApi;
246
+ private readonly clustersCacheTtlMs;
247
+ private clustersCache;
248
+ private clustersCacheTimestamp;
249
+ private clustersFetchPromise;
246
250
  constructor(options: {
247
251
  discoveryApi: DiscoveryApi;
248
252
  fetchApi: FetchApi;
249
253
  kubernetesAuthProvidersApi: KubernetesAuthProvidersApi;
254
+ /**
255
+ * When set, `getClusters()` results are cached for this many milliseconds.
256
+ * Useful when many proxy calls resolve cluster auth in quick succession.
257
+ */
258
+ clustersCacheTtlMs?: number;
250
259
  });
251
260
  private handleResponse;
252
261
  private postRequired;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-kubernetes-react",
3
- "version": "0.5.19-next.0",
3
+ "version": "0.5.19-next.1",
4
4
  "description": "Web library for the kubernetes-react plugin",
5
5
  "backstage": {
6
6
  "role": "web-library",
@@ -62,11 +62,11 @@
62
62
  "test": "backstage-cli package test"
63
63
  },
64
64
  "dependencies": {
65
- "@backstage/catalog-model": "1.8.1-next.0",
66
- "@backstage/core-components": "0.18.10-next.0",
67
- "@backstage/core-plugin-api": "1.12.6-next.0",
65
+ "@backstage/catalog-model": "1.8.1-next.1",
66
+ "@backstage/core-components": "0.18.10-next.1",
67
+ "@backstage/core-plugin-api": "1.12.6-next.1",
68
68
  "@backstage/errors": "1.3.1-next.0",
69
- "@backstage/plugin-kubernetes-common": "0.9.12-next.0",
69
+ "@backstage/plugin-kubernetes-common": "0.9.12-next.1",
70
70
  "@backstage/types": "1.2.2",
71
71
  "@kubernetes-models/apimachinery": "^2.0.0",
72
72
  "@kubernetes-models/base": "^5.0.0",
@@ -85,7 +85,7 @@
85
85
  "react-use": "^17.4.0"
86
86
  },
87
87
  "devDependencies": {
88
- "@backstage/cli": "0.36.2-next.0",
88
+ "@backstage/cli": "0.36.2-next.1",
89
89
  "@backstage/core-app-api": "1.20.1-next.0",
90
90
  "@backstage/test-utils": "1.7.18-next.0",
91
91
  "@testing-library/jest-dom": "^6.0.0",