@backstage/plugin-kubernetes-backend 0.21.11-next.2 → 0.21.12-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/actions/createGetKubernetesClustersAction.cjs.js +88 -0
- package/dist/actions/createGetKubernetesClustersAction.cjs.js.map +1 -0
- package/dist/actions/createGetKubernetesResourcesForEntityAction.cjs.js +137 -0
- package/dist/actions/createGetKubernetesResourcesForEntityAction.cjs.js.map +1 -0
- package/dist/actions/index.cjs.js +12 -0
- package/dist/actions/index.cjs.js.map +1 -0
- package/dist/auth/AksStrategy.cjs.js.map +1 -1
- package/dist/auth/AnonymousStrategy.cjs.js.map +1 -1
- package/dist/auth/AwsIamStrategy.cjs.js.map +1 -1
- package/dist/auth/AzureIdentityStrategy.cjs.js.map +1 -1
- package/dist/auth/DispatchStrategy.cjs.js.map +1 -1
- package/dist/auth/GoogleServiceAccountStrategy.cjs.js.map +1 -1
- package/dist/auth/GoogleStrategy.cjs.js.map +1 -1
- package/dist/auth/OidcStrategy.cjs.js.map +1 -1
- package/dist/auth/ServiceAccountStrategy.cjs.js.map +1 -1
- package/dist/auth/buildDefaultAuthStrategyMap.cjs.js.map +1 -1
- package/dist/auth/requirePermission.cjs.js.map +1 -1
- package/dist/cluster-locator/CatalogClusterLocator.cjs.js.map +1 -1
- package/dist/cluster-locator/CatalogClusterLocatorOptions.cjs.js.map +1 -1
- package/dist/cluster-locator/ConfigClusterLocator.cjs.js.map +1 -1
- package/dist/cluster-locator/GkeClusterLocator.cjs.js.map +1 -1
- package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js.map +1 -1
- package/dist/cluster-locator/catalogClusterAuthMetadata.cjs.js.map +1 -1
- package/dist/cluster-locator/index.cjs.js.map +1 -1
- package/dist/cluster-locator/validateClusterApiServerUrl.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/dist/package.json.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +16 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/routes/resourcesRoutes.cjs.js.map +1 -1
- package/dist/service/KubernetesConnection.cjs.js.map +1 -1
- package/dist/service/KubernetesFanOutHandler.cjs.js.map +1 -1
- package/dist/service/KubernetesFetcher.cjs.js.map +1 -1
- package/dist/service/KubernetesInitializer.cjs.js.map +1 -1
- package/dist/service/KubernetesProxy.cjs.js +1 -1
- package/dist/service/KubernetesProxy.cjs.js.map +1 -1
- package/dist/service/KubernetesRouter.cjs.js +0 -6
- package/dist/service/KubernetesRouter.cjs.js.map +1 -1
- package/dist/service/ProxyAuditSession.cjs.js +3 -0
- package/dist/service/ProxyAuditSession.cjs.js.map +1 -1
- package/dist/service/ProxyMiddlewareCache.cjs.js.map +1 -1
- package/dist/service/runPeriodically.cjs.js.map +1 -1
- package/dist/service-locator/CatalogRelationServiceLocator.cjs.js.map +1 -1
- package/dist/service-locator/MultiTenantServiceLocator.cjs.js.map +1 -1
- package/dist/service-locator/SingleTenantServiceLocator.cjs.js.map +1 -1
- package/dist/service-locator/buildDefaultServiceLocator.cjs.js.map +1 -1
- package/package.json +17 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KubernetesFetcher.cjs.js","sources":["../../src/service/KubernetesFetcher.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 type { CoreV1Api, Metrics } from '@kubernetes/client-node';\nimport {\n FetchResponseWrapper,\n KubernetesFetcher,\n ObjectFetchParams,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport {\n FetchResponse,\n KubernetesFetchError,\n PodStatusFetchResponse,\n} from '@backstage/plugin-kubernetes-common';\nimport { Response } from 'node-fetch';\nimport { JsonObject } from '@backstage/types';\nimport {\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { KubernetesConnection } from './KubernetesConnection';\n\nexport interface KubernetesClientBasedFetcherOptions {\n logger: LoggerService;\n connection: KubernetesConnection;\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 errors: KubernetesFetchError[] = [];\n const responses: FetchResponse[] = [];\n for (const result of results) {\n if (isError(result)) {\n errors.push(result);\n } else {\n responses.push(result);\n }\n }\n return { errors, responses };\n}\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly connection: KubernetesConnection;\n\n constructor({ connection }: KubernetesClientBasedFetcherOptions) {\n this.connection = connection;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(({ objectType, group, apiVersion, plural }) =>\n this.fetchResource(\n params.clusterDetails,\n params.credential,\n { group, apiVersion, plural },\n params.namespace,\n params.labelSelector,\n ).then(\n (r: Response): Promise<FetchResult> =>\n r.ok\n ? r.json().then(\n ({ kind, items }): FetchResponse => ({\n type: objectType,\n resources: this.transformResources(\n { objectType, group, apiVersion, plural },\n kind,\n items,\n ),\n }),\n )\n : this.connection.handleUnsuccessfulResponse(\n params.clusterDetails.name,\n r,\n ),\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n async fetchPodMetricsByNamespaces(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespaces: Set<string>,\n labelSelector?: string,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(namespaces).map(ns =>\n this.fetchPodMetricsForNamespace(\n clusterDetails,\n credential,\n ns,\n labelSelector,\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n private async fetchPodMetricsForNamespace(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespace: string,\n labelSelector?: string,\n ): Promise<FetchResult> {\n const [podMetrics, podList] = await Promise.all([\n this.fetchResource(\n clusterDetails,\n credential,\n { group: 'metrics.k8s.io', apiVersion: 'v1beta1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n this.fetchResource(\n clusterDetails,\n credential,\n { group: '', apiVersion: 'v1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n ]);\n if (podMetrics.ok && podList.ok) {\n const { topPods } = await import('@kubernetes/client-node');\n return topPods(\n {\n listPodForAllNamespaces: () => podList.json(),\n } as unknown as CoreV1Api,\n {\n getPodMetrics: () => podMetrics.json(),\n } as unknown as Metrics,\n ).then(\n (resources): PodStatusFetchResponse => ({\n type: 'podstatus',\n resources,\n }),\n );\n } else if (podMetrics.ok) {\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podList,\n );\n }\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podMetrics,\n );\n }\n\n private async fetchResource(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n resource: Pick<ObjectToFetch, 'group' | 'apiVersion' | 'plural'>,\n namespace?: string,\n labelSelector?: string,\n ): Promise<Response> {\n const resourcePath = this.connection.buildResourcePath(\n resource.group,\n resource.apiVersion,\n resource.plural,\n namespace,\n );\n\n return this.connection.fetchWithConnection(\n clusterDetails,\n credential,\n resourcePath,\n labelSelector,\n );\n }\n\n private redactSecretData(items: JsonObject[]): JsonObject[] {\n return items.map((item: JsonObject) => {\n const redacted: JsonObject = { ...item };\n if (item.data && typeof item.data === 'object') {\n redacted.data = Object.fromEntries(\n Object.keys(item.data).map(key => [key, '***']),\n );\n }\n if (item.stringData && typeof item.stringData === 'object') {\n redacted.stringData = Object.fromEntries(\n Object.keys(item.stringData).map(key => [key, '***']),\n );\n }\n return redacted;\n });\n }\n\n private transformResources(\n resource: ObjectToFetch,\n kind: string | undefined,\n items: JsonObject[],\n ): JsonObject[] {\n const itemKind = kind?.replace(/(List)$/, '');\n\n // Whether a response holds Secrets is decided from the resource that was\n // requested and from the kind reported by the API server, rather than from\n // the object type, which callers can influence. The request is checked on\n // its own so that masking does not depend on the response at all, and the\n // kind is checked as well to cover requests that reach Secrets by some\n // other shape.\n const containsSecrets =\n resource.objectType === 'secrets' ||\n itemKind === 'Secret' ||\n (resource.group === '' &&\n resource.apiVersion === 'v1' &&\n resource.plural === 'secrets');\n\n const resources = containsSecrets ? this.redactSecretData(items) : items;\n\n if (resource.objectType === 'customresources') {\n if (itemKind === undefined) {\n throw new Error(\n `Missing kind in response when fetching '${resource.plural}'`,\n );\n }\n return resources.map((item: JsonObject) => ({\n ...item,\n kind: itemKind,\n }));\n }\n\n return resources;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KubernetesFetcher.cjs.js","sources":["../../src/service/KubernetesFetcher.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 type { CoreV1Api, Metrics } from '@kubernetes/client-node';\nimport {\n FetchResponseWrapper,\n KubernetesFetcher,\n ObjectFetchParams,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport {\n FetchResponse,\n KubernetesFetchError,\n PodStatusFetchResponse,\n} from '@backstage/plugin-kubernetes-common';\nimport { Response } from 'node-fetch';\nimport { JsonObject } from '@backstage/types';\nimport {\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { KubernetesConnection } from './KubernetesConnection';\n\nexport interface KubernetesClientBasedFetcherOptions {\n logger: LoggerService;\n connection: KubernetesConnection;\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 errors: KubernetesFetchError[] = [];\n const responses: FetchResponse[] = [];\n for (const result of results) {\n if (isError(result)) {\n errors.push(result);\n } else {\n responses.push(result);\n }\n }\n return { errors, responses };\n}\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly connection: KubernetesConnection;\n\n constructor({ connection }: KubernetesClientBasedFetcherOptions) {\n this.connection = connection;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(({ objectType, group, apiVersion, plural }) =>\n this.fetchResource(\n params.clusterDetails,\n params.credential,\n { group, apiVersion, plural },\n params.namespace,\n params.labelSelector,\n ).then(\n (r: Response): Promise<FetchResult> =>\n r.ok\n ? r.json().then(\n ({ kind, items }): FetchResponse => ({\n type: objectType,\n resources: this.transformResources(\n { objectType, group, apiVersion, plural },\n kind,\n items,\n ),\n }),\n )\n : this.connection.handleUnsuccessfulResponse(\n params.clusterDetails.name,\n r,\n ),\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n async fetchPodMetricsByNamespaces(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespaces: Set<string>,\n labelSelector?: string,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(namespaces).map(ns =>\n this.fetchPodMetricsForNamespace(\n clusterDetails,\n credential,\n ns,\n labelSelector,\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n private async fetchPodMetricsForNamespace(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespace: string,\n labelSelector?: string,\n ): Promise<FetchResult> {\n const [podMetrics, podList] = await Promise.all([\n this.fetchResource(\n clusterDetails,\n credential,\n { group: 'metrics.k8s.io', apiVersion: 'v1beta1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n this.fetchResource(\n clusterDetails,\n credential,\n { group: '', apiVersion: 'v1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n ]);\n if (podMetrics.ok && podList.ok) {\n const { topPods } = await import('@kubernetes/client-node');\n return topPods(\n {\n listPodForAllNamespaces: () => podList.json(),\n } as unknown as CoreV1Api,\n {\n getPodMetrics: () => podMetrics.json(),\n } as unknown as Metrics,\n ).then(\n (resources): PodStatusFetchResponse => ({\n type: 'podstatus',\n resources,\n }),\n );\n } else if (podMetrics.ok) {\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podList,\n );\n }\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podMetrics,\n );\n }\n\n private async fetchResource(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n resource: Pick<ObjectToFetch, 'group' | 'apiVersion' | 'plural'>,\n namespace?: string,\n labelSelector?: string,\n ): Promise<Response> {\n const resourcePath = this.connection.buildResourcePath(\n resource.group,\n resource.apiVersion,\n resource.plural,\n namespace,\n );\n\n return this.connection.fetchWithConnection(\n clusterDetails,\n credential,\n resourcePath,\n labelSelector,\n );\n }\n\n private redactSecretData(items: JsonObject[]): JsonObject[] {\n return items.map((item: JsonObject) => {\n const redacted: JsonObject = { ...item };\n if (item.data && typeof item.data === 'object') {\n redacted.data = Object.fromEntries(\n Object.keys(item.data).map(key => [key, '***']),\n );\n }\n if (item.stringData && typeof item.stringData === 'object') {\n redacted.stringData = Object.fromEntries(\n Object.keys(item.stringData).map(key => [key, '***']),\n );\n }\n return redacted;\n });\n }\n\n private transformResources(\n resource: ObjectToFetch,\n kind: string | undefined,\n items: JsonObject[],\n ): JsonObject[] {\n const itemKind = kind?.replace(/(List)$/, '');\n\n // Whether a response holds Secrets is decided from the resource that was\n // requested and from the kind reported by the API server, rather than from\n // the object type, which callers can influence. The request is checked on\n // its own so that masking does not depend on the response at all, and the\n // kind is checked as well to cover requests that reach Secrets by some\n // other shape.\n const containsSecrets =\n resource.objectType === 'secrets' ||\n itemKind === 'Secret' ||\n (resource.group === '' &&\n resource.apiVersion === 'v1' &&\n resource.plural === 'secrets');\n\n const resources = containsSecrets ? this.redactSecretData(items) : items;\n\n if (resource.objectType === 'customresources') {\n if (itemKind === undefined) {\n throw new Error(\n `Missing kind in response when fetching '${resource.plural}'`,\n );\n }\n return resources.map((item: JsonObject) => ({\n ...item,\n kind: itemKind,\n }));\n }\n\n return resources;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KubernetesInitializer.cjs.js","sources":["../../src/service/KubernetesInitializer.ts"],"sourcesContent":["/*\n * Copyright 2025 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 AuthenticationStrategy,\n CustomResource,\n KubernetesClustersSupplier,\n KubernetesClusterSupplierFactory,\n KubernetesFetcher,\n KubernetesFetcherFactory,\n KubernetesObjectsProviderFactory,\n KubernetesObjectTypes,\n KubernetesServiceLocator,\n KubernetesServiceLocatorFactory,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\nimport { KubernetesConnection } from './KubernetesConnection';\nimport {\n AuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { buildDefaultAuthStrategyMap } from '../auth/buildDefaultAuthStrategyMap';\nimport { Duration } from 'luxon';\nimport { getCombinedClusterSupplier } from '../cluster-locator';\nimport { DispatchStrategy } from '../auth/DispatchStrategy';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { buildDefaultServiceLocator } from '../service-locator/buildDefaultServiceLocator';\nimport {\n ALL_OBJECTS,\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\n\ntype Opts = {\n fetcher?: KubernetesFetcherFactory;\n clusterSupplier?: KubernetesClusterSupplierFactory;\n serviceLocator?: KubernetesServiceLocatorFactory;\n objectsProvider?: KubernetesObjectsProviderFactory;\n authStrategyMap?: Map<string, AuthenticationStrategy>;\n logger: LoggerService;\n config: RootConfigService;\n catalog: CatalogService;\n auth: AuthService;\n};\n\nexport class KubernetesInitializer {\n private readonly opts: Opts;\n\n constructor(opts: Opts) {\n this.opts = opts;\n }\n\n static create(opts: Opts) {\n return new KubernetesInitializer(opts);\n }\n\n private defaultConnection() {\n return new KubernetesConnection({ logger: this.opts.logger });\n }\n\n private async defaultFetcher(connection: KubernetesConnection) {\n return new KubernetesClientBasedFetcher({\n logger: this.opts.logger,\n connection,\n });\n }\n\n private async defaultAuthStrategy() {\n return buildDefaultAuthStrategyMap({\n logger: this.opts.logger,\n config: this.opts.config,\n });\n }\n\n private async defaultClusterSupplier(opts: {\n authStrategyMap: Map<string, AuthenticationStrategy>;\n }) {\n const refreshInterval = Duration.fromObject({\n minutes: 60,\n });\n\n return getCombinedClusterSupplier(\n this.opts.config,\n this.opts.catalog,\n new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n this.opts.logger,\n refreshInterval,\n this.opts.auth,\n );\n }\n\n private async defaultServiceLocator(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n }) {\n return buildDefaultServiceLocator({\n config: this.opts.config,\n clusterSupplier: opts.clusterSupplier,\n });\n }\n\n private async defaultObjectsProvider(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n authStrategyMap: Map<string, AuthenticationStrategy>;\n fetcher: KubernetesFetcher;\n serviceLocator: KubernetesServiceLocator;\n customResources: CustomResource[];\n objectTypesToFetch: ObjectToFetch[];\n }) {\n return new KubernetesFanOutHandler({\n logger: this.opts.logger,\n config: this.opts.config,\n fetcher: opts.fetcher,\n serviceLocator: opts.serviceLocator,\n customResources: opts.customResources,\n objectTypesToFetch: opts.objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n });\n }\n\n private async defaultObjectsProviderOptions() {\n const customResources: CustomResource[] = (\n this.opts.config.getOptionalConfigArray('kubernetes.customResources') ??\n []\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 const objectTypesToFetchStrings = this.opts.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const apiVersionOverrides = this.opts.config.getOptionalConfig(\n 'kubernetes.apiVersionOverrides',\n );\n\n let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = ALL_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n\n if (apiVersionOverrides) {\n objectTypesToFetch ??= DEFAULT_OBJECTS;\n\n for (const obj of objectTypesToFetch) {\n if (apiVersionOverrides.has(obj.objectType)) {\n obj.apiVersion = apiVersionOverrides.getString(obj.objectType);\n }\n }\n }\n\n return {\n customResources,\n objectTypesToFetch: objectTypesToFetch ?? DEFAULT_OBJECTS,\n };\n }\n\n async init() {\n const connection = this.defaultConnection();\n\n const fetcher =\n (await this.opts.fetcher?.({\n getDefault: () => this.defaultFetcher(connection),\n })) ?? (await this.defaultFetcher(connection));\n\n const authStrategyMap =\n this.opts.authStrategyMap ?? (await this.defaultAuthStrategy());\n\n const clusterSupplier =\n (await this.opts.clusterSupplier?.({\n getDefault: () => this.defaultClusterSupplier({ authStrategyMap }),\n })) ?? (await this.defaultClusterSupplier({ authStrategyMap }));\n\n const serviceLocator =\n (await this.opts.serviceLocator?.({\n getDefault: () => this.defaultServiceLocator({ clusterSupplier }),\n clusterSupplier,\n })) ?? (await this.defaultServiceLocator({ clusterSupplier }));\n\n const { customResources, objectTypesToFetch } =\n await this.defaultObjectsProviderOptions();\n\n const objectsProvider =\n (await this.opts.objectsProvider?.({\n getDefault: () =>\n this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }),\n fetcher,\n clusterSupplier,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(authStrategyMap.entries()),\n }),\n })) ??\n (await this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }));\n\n return {\n fetcher,\n authStrategyMap,\n clusterSupplier,\n serviceLocator,\n objectsProvider,\n };\n }\n}\n"],"names":["KubernetesConnection","KubernetesClientBasedFetcher","buildDefaultAuthStrategyMap","Duration","getCombinedClusterSupplier","DispatchStrategy","buildDefaultServiceLocator","KubernetesFanOutHandler","ALL_OBJECTS","DEFAULT_OBJECTS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KubernetesInitializer.cjs.js","sources":["../../src/service/KubernetesInitializer.ts"],"sourcesContent":["/*\n * Copyright 2025 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 AuthenticationStrategy,\n CustomResource,\n KubernetesClustersSupplier,\n KubernetesClusterSupplierFactory,\n KubernetesFetcher,\n KubernetesFetcherFactory,\n KubernetesObjectsProviderFactory,\n KubernetesObjectTypes,\n KubernetesServiceLocator,\n KubernetesServiceLocatorFactory,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\nimport { KubernetesConnection } from './KubernetesConnection';\nimport {\n AuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { buildDefaultAuthStrategyMap } from '../auth/buildDefaultAuthStrategyMap';\nimport { Duration } from 'luxon';\nimport { getCombinedClusterSupplier } from '../cluster-locator';\nimport { DispatchStrategy } from '../auth/DispatchStrategy';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { buildDefaultServiceLocator } from '../service-locator/buildDefaultServiceLocator';\nimport {\n ALL_OBJECTS,\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\n\ntype Opts = {\n fetcher?: KubernetesFetcherFactory;\n clusterSupplier?: KubernetesClusterSupplierFactory;\n serviceLocator?: KubernetesServiceLocatorFactory;\n objectsProvider?: KubernetesObjectsProviderFactory;\n authStrategyMap?: Map<string, AuthenticationStrategy>;\n logger: LoggerService;\n config: RootConfigService;\n catalog: CatalogService;\n auth: AuthService;\n};\n\nexport class KubernetesInitializer {\n private readonly opts: Opts;\n\n constructor(opts: Opts) {\n this.opts = opts;\n }\n\n static create(opts: Opts) {\n return new KubernetesInitializer(opts);\n }\n\n private defaultConnection() {\n return new KubernetesConnection({ logger: this.opts.logger });\n }\n\n private async defaultFetcher(connection: KubernetesConnection) {\n return new KubernetesClientBasedFetcher({\n logger: this.opts.logger,\n connection,\n });\n }\n\n private async defaultAuthStrategy() {\n return buildDefaultAuthStrategyMap({\n logger: this.opts.logger,\n config: this.opts.config,\n });\n }\n\n private async defaultClusterSupplier(opts: {\n authStrategyMap: Map<string, AuthenticationStrategy>;\n }) {\n const refreshInterval = Duration.fromObject({\n minutes: 60,\n });\n\n return getCombinedClusterSupplier(\n this.opts.config,\n this.opts.catalog,\n new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n this.opts.logger,\n refreshInterval,\n this.opts.auth,\n );\n }\n\n private async defaultServiceLocator(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n }) {\n return buildDefaultServiceLocator({\n config: this.opts.config,\n clusterSupplier: opts.clusterSupplier,\n });\n }\n\n private async defaultObjectsProvider(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n authStrategyMap: Map<string, AuthenticationStrategy>;\n fetcher: KubernetesFetcher;\n serviceLocator: KubernetesServiceLocator;\n customResources: CustomResource[];\n objectTypesToFetch: ObjectToFetch[];\n }) {\n return new KubernetesFanOutHandler({\n logger: this.opts.logger,\n config: this.opts.config,\n fetcher: opts.fetcher,\n serviceLocator: opts.serviceLocator,\n customResources: opts.customResources,\n objectTypesToFetch: opts.objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n });\n }\n\n private async defaultObjectsProviderOptions() {\n const customResources: CustomResource[] = (\n this.opts.config.getOptionalConfigArray('kubernetes.customResources') ??\n []\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 const objectTypesToFetchStrings = this.opts.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const apiVersionOverrides = this.opts.config.getOptionalConfig(\n 'kubernetes.apiVersionOverrides',\n );\n\n let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = ALL_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n\n if (apiVersionOverrides) {\n objectTypesToFetch ??= DEFAULT_OBJECTS;\n\n for (const obj of objectTypesToFetch) {\n if (apiVersionOverrides.has(obj.objectType)) {\n obj.apiVersion = apiVersionOverrides.getString(obj.objectType);\n }\n }\n }\n\n return {\n customResources,\n objectTypesToFetch: objectTypesToFetch ?? DEFAULT_OBJECTS,\n };\n }\n\n async init() {\n const connection = this.defaultConnection();\n\n const fetcher =\n (await this.opts.fetcher?.({\n getDefault: () => this.defaultFetcher(connection),\n })) ?? (await this.defaultFetcher(connection));\n\n const authStrategyMap =\n this.opts.authStrategyMap ?? (await this.defaultAuthStrategy());\n\n const clusterSupplier =\n (await this.opts.clusterSupplier?.({\n getDefault: () => this.defaultClusterSupplier({ authStrategyMap }),\n })) ?? (await this.defaultClusterSupplier({ authStrategyMap }));\n\n const serviceLocator =\n (await this.opts.serviceLocator?.({\n getDefault: () => this.defaultServiceLocator({ clusterSupplier }),\n clusterSupplier,\n })) ?? (await this.defaultServiceLocator({ clusterSupplier }));\n\n const { customResources, objectTypesToFetch } =\n await this.defaultObjectsProviderOptions();\n\n const objectsProvider =\n (await this.opts.objectsProvider?.({\n getDefault: () =>\n this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }),\n fetcher,\n clusterSupplier,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(authStrategyMap.entries()),\n }),\n })) ??\n (await this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }));\n\n return {\n fetcher,\n authStrategyMap,\n clusterSupplier,\n serviceLocator,\n objectsProvider,\n };\n }\n}\n"],"names":["KubernetesConnection","KubernetesClientBasedFetcher","buildDefaultAuthStrategyMap","Duration","getCombinedClusterSupplier","DispatchStrategy","buildDefaultServiceLocator","KubernetesFanOutHandler","ALL_OBJECTS","DEFAULT_OBJECTS"],"mappings":"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"}
|
|
@@ -131,7 +131,7 @@ class KubernetesProxy {
|
|
|
131
131
|
port: url.port,
|
|
132
132
|
ca: bufferFromFileOrString(cluster.caFile, cluster.caData)?.toString()
|
|
133
133
|
};
|
|
134
|
-
const authHeader = req.headers[HEADER_KUBERNETES_AUTH.
|
|
134
|
+
const authHeader = req.headers[HEADER_KUBERNETES_AUTH.toLowerCase()];
|
|
135
135
|
if (typeof authHeader === "string") {
|
|
136
136
|
req.headers.authorization = authHeader;
|
|
137
137
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KubernetesProxy.cjs.js","sources":["../../src/service/KubernetesProxy.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 */\nimport {\n ErrorResponseBody,\n ForwardedError,\n NotAllowedError,\n NotFoundError,\n serializeError,\n} from '@backstage/errors';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n SERVICEACCOUNT_CA_PATH,\n kubernetesProxyPermission,\n KubernetesRequestAuth,\n} from '@backstage/plugin-kubernetes-common';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport type { Cluster } from '@kubernetes/client-node';\nimport { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware';\nimport fs from 'fs-extra';\n\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\n\nimport {\n ProxyMiddlewareCache,\n resolveProxyMiddlewareCacheOptions,\n} from './ProxyMiddlewareCache';\n\nimport type { NextFunction, Request, Response } from 'express';\nimport { IncomingHttpHeaders } from 'node:http';\nimport {\n AuditorService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport { ProxyAuditSession } from './ProxyAuditSession';\n\nconst PROXY_PREPARED_TARGET = Symbol('kubernetesProxyPreparedTarget');\n\ntype PreparedProxyTarget = {\n proxyTarget: any;\n rewrittenPath: string;\n cluster: ClusterDetails;\n};\n\n/**\n * The header that is used to specify the cluster name.\n *\n * @public\n */\nexport const HEADER_KUBERNETES_CLUSTER: string = 'Backstage-Kubernetes-Cluster';\n\n/**\n * The header that is used to specify the Authentication Authorities token.\n * e.x if using the google auth provider as your authentication authority then this field would be the google provided bearer token.\n * @public\n */\nexport const HEADER_KUBERNETES_AUTH: string =\n 'Backstage-Kubernetes-Authorization';\n\n/**\n * The options object expected to be passed as a parameter to KubernetesProxy.createRequestHandler().\n *\n * @public\n */\nexport type KubernetesProxyCreateRequestHandlerOptions = {\n permissionApi: PermissionsService;\n};\n\n/**\n * Options accepted as a parameter by the KubernetesProxy\n *\n * @public\n */\nexport type KubernetesProxyOptions = {\n logger: LoggerService;\n clusterSupplier: KubernetesClustersSupplier;\n authStrategy: AuthenticationStrategy;\n discovery: DiscoveryService;\n httpAuth: HttpAuthService;\n auditor?: AuditorService;\n middlewareCache?: {\n ttlMs?: number;\n maxSize?: number;\n };\n};\n\n/**\n * A proxy that routes requests to the Kubernetes API.\n *\n * @public\n */\nexport class KubernetesProxy {\n private readonly middlewareCache: ProxyMiddlewareCache;\n private readonly logger: LoggerService;\n private readonly clusterSupplier: KubernetesClustersSupplier;\n private readonly authStrategy: AuthenticationStrategy;\n private readonly httpAuth: HttpAuthService;\n private readonly auditor?: AuditorService;\n\n constructor(options: KubernetesProxyOptions) {\n this.logger = options.logger;\n this.clusterSupplier = options.clusterSupplier;\n this.authStrategy = options.authStrategy;\n this.httpAuth = options.httpAuth;\n this.auditor = options.auditor;\n this.middlewareCache = new ProxyMiddlewareCache(\n resolveProxyMiddlewareCacheOptions(options.middlewareCache),\n );\n }\n\n public createRequestHandler(\n options: KubernetesProxyCreateRequestHandlerOptions,\n ): RequestHandler {\n const { permissionApi } = options;\n return async (req, res, next) => {\n const clusterNameHeader =\n req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];\n const clusterName =\n typeof clusterNameHeader === 'string' && clusterNameHeader.length > 0\n ? clusterNameHeader\n : 'unknown';\n const path = req.path || req.url.split('?')[0] || '';\n\n let clientDisconnected = false;\n const onSocketClose = () => {\n clientDisconnected = true;\n };\n req.socket.once('close', onSocketClose);\n\n let auditSession: ProxyAuditSession | undefined;\n let finishListenerAttached = false;\n try {\n auditSession = await ProxyAuditSession.start(this.auditor, {\n req,\n clusterName,\n method: req.method,\n path,\n logger: this.logger,\n });\n\n finishListenerAttached = await this.authorizeAndDispatch(\n req,\n res,\n next,\n permissionApi,\n auditSession,\n onSocketClose,\n () => clientDisconnected,\n );\n } catch (error) {\n req.socket.removeListener('close', onSocketClose);\n const err = error instanceof Error ? error : new Error(String(error));\n if (auditSession && !finishListenerAttached) {\n auditSession.finalize(err);\n }\n throw error;\n }\n };\n }\n\n /**\n * Authorizes the request, resolves the target cluster, and dispatches\n * to either the HTTP or WebSocket proxy path. Returns `true` when\n * HTTP finish listeners have been attached.\n */\n private async authorizeAndDispatch(\n req: Request,\n res: Response,\n next: NextFunction,\n permissionApi: PermissionsService,\n auditSession: ProxyAuditSession,\n onSocketClose: () => void,\n isClientDisconnected: () => boolean,\n ): Promise<boolean> {\n const authorizeResponse = await permissionApi.authorize(\n [{ permission: kubernetesProxyPermission }],\n { credentials: await this.httpAuth.credentials(req) },\n );\n\n if (authorizeResponse[0].result === AuthorizeResult.DENY) {\n req.socket.removeListener('close', onSocketClose);\n auditSession.finalize(new NotAllowedError('Unauthorized'));\n res.status(403).json({\n error: serializeError(new NotAllowedError('Unauthorized')),\n });\n return false;\n }\n\n if (isClientDisconnected()) {\n auditSession.finalize(\n new Error('Client disconnected before proxy dispatch'),\n );\n return false;\n }\n\n const prepared = await this.prepareProxyTarget(req);\n auditSession.setResolvedClusterName(prepared.cluster.name);\n (req as any)[PROXY_PREPARED_TARGET] = prepared;\n const middleware = this.getOrCreateMiddleware(prepared.cluster);\n\n if (isClientDisconnected()) {\n auditSession.finalize(\n new Error('Client disconnected before proxy dispatch'),\n );\n return false;\n }\n\n return KubernetesProxy.dispatchToProxy(\n req,\n res,\n next,\n middleware,\n auditSession,\n onSocketClose,\n );\n }\n\n /**\n * Resolves the cluster, credentials, and target URL for a proxy request\n * before dispatching to the middleware. This ensures all async preparation\n * completes under the caller's try/catch, avoiding unhandled rejections\n * from http-proxy-middleware's fire-and-forget upgrade() path.\n */\n private async prepareProxyTarget(req: Request): Promise<PreparedProxyTarget> {\n const cluster = await this.getClusterForRequest(req);\n const url = new URL(cluster.url);\n\n const { bufferFromFileOrString } = await import('@kubernetes/client-node');\n\n const target: any = {\n protocol: url.protocol,\n host: url.hostname,\n port: url.port,\n ca: bufferFromFileOrString(cluster.caFile, cluster.caData)?.toString(),\n };\n\n const authHeader =\n req.headers[HEADER_KUBERNETES_AUTH.toLocaleLowerCase('en-US')];\n if (typeof authHeader === 'string') {\n req.headers.authorization = authHeader;\n } else {\n const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(\n req.headers,\n );\n\n const credential = await this.authStrategy.getCredential(\n cluster,\n authObj,\n );\n\n if (credential.type === 'bearer token') {\n req.headers.authorization = `Bearer ${credential.token}`;\n } else if (credential.type === 'x509 client certificate') {\n target.key = credential.key;\n target.cert = credential.cert;\n }\n }\n\n const requestPath = req.originalUrl || req.url || '';\n const rewrittenPath = requestPath.replace(\n new RegExp(\n `^${(req.baseUrl || '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`,\n ),\n url.pathname || '',\n );\n\n return { proxyTarget: target, rewrittenPath, cluster };\n }\n\n // We create one middleware per remote cluster and hold on to them, because\n // the secure property isn't possible to decide on a per-request basis with a\n // single middleware instance. Target resolution and path rewriting are handled\n // per-request via the PROXY_PREPARED_TARGET symbol stashed on each req by\n // prepareProxyTarget. Entries are refreshed after a TTL or when cluster details\n // change.\n private getOrCreateMiddleware(cluster: ClusterDetails): RequestHandler {\n let middleware = this.middlewareCache.get(cluster);\n if (!middleware) {\n const logger = this.logger.child({ cluster: cluster.name });\n middleware = createProxyMiddleware({\n logProvider: () => ({\n log: logger.info.bind(logger),\n debug: logger.debug.bind(logger),\n info: logger.info.bind(logger),\n warn: logger.warn.bind(logger),\n error: logger.error.bind(logger),\n }),\n // ws must be false to prevent http-proxy-middleware from auto-subscribing\n // to the server's 'upgrade' event, which would bypass Express routing and\n // skip audit event creation for WebSocket requests.\n ws: false,\n secure: !cluster.skipTLSVerify,\n changeOrigin: true,\n router: req => {\n return (req as any)[PROXY_PREPARED_TARGET]?.proxyTarget;\n },\n pathRewrite: (_path, req) => {\n return (req as any)[PROXY_PREPARED_TARGET]?.rewrittenPath ?? _path;\n },\n onError: (error, req, res) => {\n const wrappedError = new ForwardedError(\n `Cluster '${cluster.name}' request error`,\n error,\n );\n\n logger.error('Kubernetes proxy error', wrappedError);\n\n if (typeof (res as { status?: unknown }).status !== 'function') {\n return;\n }\n\n const body: ErrorResponseBody = {\n error: serializeError(wrappedError, {\n includeStack: process.env.NODE_ENV === 'development',\n }),\n request: { method: req.method, url: req.originalUrl },\n response: { statusCode: 500 },\n };\n res.status(500).json(body);\n },\n onProxyReqWs: ProxyAuditSession.handleWebSocketProxyReq,\n });\n this.middlewareCache.set(cluster, middleware);\n }\n return middleware;\n }\n\n /**\n * Branches to WebSocket upgrade or HTTP middleware dispatch and wires\n * up the appropriate audit listeners. Returns `true` when HTTP finish\n * listeners have been attached (used by the caller's catch block to\n * avoid double-finalizing).\n */\n private static dispatchToProxy(\n req: Request,\n res: Response,\n next: NextFunction,\n middleware: RequestHandler,\n auditSession: ProxyAuditSession,\n onSocketClose: () => void,\n ): boolean {\n const isWebSocketUpgrade =\n (req.header('connection') ?? '')\n .toLowerCase()\n .split(',')\n .some(t => t.trim() === 'upgrade') &&\n req.header('upgrade')?.toLowerCase() === 'websocket';\n\n if (isWebSocketUpgrade) {\n auditSession.prepareForWebSocketUpgrade(req, onSocketClose);\n\n try {\n middleware.upgrade!(req, req.socket, undefined);\n } catch (error) {\n req.socket.removeListener('close', onSocketClose);\n auditSession.finalize(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n return false;\n }\n\n // HTTP path: remove the early close listener; HTTP audit listeners\n // on the response take over from here.\n req.socket.removeListener('close', onSocketClose);\n auditSession.attachToHttpResponse(res);\n middleware(req, res, next);\n return true;\n }\n\n private async getClusterForRequest(req: Request): Promise<ClusterDetails> {\n const { KubeConfig } = await import('@kubernetes/client-node');\n\n const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];\n const clusters = await this.clusterSupplier.getClusters({\n credentials: await this.httpAuth.credentials(req),\n });\n\n if (!clusters || clusters.length <= 0) {\n throw new NotFoundError(`No Clusters configured`);\n }\n\n const hasClusterNameHeader =\n typeof clusterName === 'string' && clusterName.length > 0;\n\n let cluster: ClusterDetails | undefined;\n\n if (hasClusterNameHeader) {\n cluster = clusters.find(c => c.name === clusterName);\n } else if (clusters.length === 1) {\n cluster = clusters.at(0);\n }\n\n if (!cluster) {\n throw new NotFoundError(`Cluster '${clusterName}' not found`);\n }\n\n const authProvider =\n cluster.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n\n if (\n authProvider === 'serviceAccount' &&\n fs.pathExistsSync(SERVICEACCOUNT_CA_PATH) &&\n !cluster.authMetadata.serviceAccountToken\n ) {\n const kc = new KubeConfig();\n kc.loadFromCluster();\n const clusterFromKubeConfig = kc.getCurrentCluster() as Cluster;\n\n const url = new URL(clusterFromKubeConfig.server);\n cluster.url = clusterFromKubeConfig.server;\n if (url.protocol === 'https:') {\n cluster.caFile = clusterFromKubeConfig.caFile;\n }\n }\n\n return cluster;\n }\n\n private static authHeadersToKubernetesRequestAuth(\n originalHeaders: IncomingHttpHeaders,\n ): KubernetesRequestAuth {\n return Object.keys(originalHeaders)\n .filter(header => header.startsWith('backstage-kubernetes-authorization'))\n .map(header =>\n KubernetesProxy.headerToDictionary(header, originalHeaders),\n )\n .filter(headerAsDic => Object.keys(headerAsDic).length !== 0)\n .reduce(KubernetesProxy.combineHeaders, {});\n }\n\n private static headerToDictionary(\n header: string,\n originalHeaders: IncomingHttpHeaders,\n ): KubernetesRequestAuth {\n const obj: KubernetesRequestAuth = {};\n const headerSplit = header.split('-');\n if (headerSplit.length >= 4) {\n const framework = headerSplit[3].toLowerCase();\n if (headerSplit.length >= 5) {\n const provider = headerSplit.slice(4).join('-').toLowerCase();\n obj[framework] = { [provider]: originalHeaders[header] };\n } else {\n obj[framework] = originalHeaders[header];\n }\n }\n return obj;\n }\n\n private static combineHeaders(\n authObj: any,\n header: any,\n ): KubernetesRequestAuth {\n const framework = Object.keys(header)[0];\n\n if (authObj[framework]) {\n authObj[framework] = {\n ...authObj[framework],\n ...header[framework],\n };\n } else {\n authObj[framework] = header[framework];\n }\n\n return authObj;\n }\n}\n"],"names":["ProxyMiddlewareCache","resolveProxyMiddlewareCacheOptions","ProxyAuditSession","kubernetesProxyPermission","AuthorizeResult","NotAllowedError","serializeError","createProxyMiddleware","ForwardedError","NotFoundError","ANNOTATION_KUBERNETES_AUTH_PROVIDER","fs","SERVICEACCOUNT_CA_PATH"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KubernetesProxy.cjs.js","sources":["../../src/service/KubernetesProxy.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 */\nimport {\n ErrorResponseBody,\n ForwardedError,\n NotAllowedError,\n NotFoundError,\n serializeError,\n} from '@backstage/errors';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n SERVICEACCOUNT_CA_PATH,\n kubernetesProxyPermission,\n KubernetesRequestAuth,\n} from '@backstage/plugin-kubernetes-common';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport type { Cluster } from '@kubernetes/client-node';\nimport { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware';\nimport fs from 'fs-extra';\n\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\n\nimport {\n ProxyMiddlewareCache,\n resolveProxyMiddlewareCacheOptions,\n} from './ProxyMiddlewareCache';\n\nimport type { NextFunction, Request, Response } from 'express';\nimport { IncomingHttpHeaders } from 'node:http';\nimport {\n AuditorService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n PermissionsService,\n} from '@backstage/backend-plugin-api';\nimport { ProxyAuditSession } from './ProxyAuditSession';\n\nconst PROXY_PREPARED_TARGET = Symbol('kubernetesProxyPreparedTarget');\n\ntype PreparedProxyTarget = {\n proxyTarget: any;\n rewrittenPath: string;\n cluster: ClusterDetails;\n};\n\n/**\n * The header that is used to specify the cluster name.\n *\n * @public\n */\nexport const HEADER_KUBERNETES_CLUSTER: string = 'Backstage-Kubernetes-Cluster';\n\n/**\n * The header that is used to specify the Authentication Authorities token.\n * e.x if using the google auth provider as your authentication authority then this field would be the google provided bearer token.\n * @public\n */\nexport const HEADER_KUBERNETES_AUTH: string =\n 'Backstage-Kubernetes-Authorization';\n\n/**\n * The options object expected to be passed as a parameter to KubernetesProxy.createRequestHandler().\n *\n * @public\n */\nexport type KubernetesProxyCreateRequestHandlerOptions = {\n permissionApi: PermissionsService;\n};\n\n/**\n * Options accepted as a parameter by the KubernetesProxy\n *\n * @public\n */\nexport type KubernetesProxyOptions = {\n logger: LoggerService;\n clusterSupplier: KubernetesClustersSupplier;\n authStrategy: AuthenticationStrategy;\n discovery: DiscoveryService;\n httpAuth: HttpAuthService;\n auditor?: AuditorService;\n middlewareCache?: {\n ttlMs?: number;\n maxSize?: number;\n };\n};\n\n/**\n * A proxy that routes requests to the Kubernetes API.\n *\n * @public\n */\nexport class KubernetesProxy {\n private readonly middlewareCache: ProxyMiddlewareCache;\n private readonly logger: LoggerService;\n private readonly clusterSupplier: KubernetesClustersSupplier;\n private readonly authStrategy: AuthenticationStrategy;\n private readonly httpAuth: HttpAuthService;\n private readonly auditor?: AuditorService;\n\n constructor(options: KubernetesProxyOptions) {\n this.logger = options.logger;\n this.clusterSupplier = options.clusterSupplier;\n this.authStrategy = options.authStrategy;\n this.httpAuth = options.httpAuth;\n this.auditor = options.auditor;\n this.middlewareCache = new ProxyMiddlewareCache(\n resolveProxyMiddlewareCacheOptions(options.middlewareCache),\n );\n }\n\n public createRequestHandler(\n options: KubernetesProxyCreateRequestHandlerOptions,\n ): RequestHandler {\n const { permissionApi } = options;\n return async (req, res, next) => {\n const clusterNameHeader =\n req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];\n const clusterName =\n typeof clusterNameHeader === 'string' && clusterNameHeader.length > 0\n ? clusterNameHeader\n : 'unknown';\n const path = req.path || req.url.split('?')[0] || '';\n\n let clientDisconnected = false;\n const onSocketClose = () => {\n clientDisconnected = true;\n };\n req.socket.once('close', onSocketClose);\n\n let auditSession: ProxyAuditSession | undefined;\n let finishListenerAttached = false;\n try {\n auditSession = await ProxyAuditSession.start(this.auditor, {\n req,\n clusterName,\n method: req.method,\n path,\n logger: this.logger,\n });\n\n finishListenerAttached = await this.authorizeAndDispatch(\n req,\n res,\n next,\n permissionApi,\n auditSession,\n onSocketClose,\n () => clientDisconnected,\n );\n } catch (error) {\n req.socket.removeListener('close', onSocketClose);\n const err = error instanceof Error ? error : new Error(String(error));\n if (auditSession && !finishListenerAttached) {\n auditSession.finalize(err);\n }\n throw error;\n }\n };\n }\n\n /**\n * Authorizes the request, resolves the target cluster, and dispatches\n * to either the HTTP or WebSocket proxy path. Returns `true` when\n * HTTP finish listeners have been attached.\n */\n private async authorizeAndDispatch(\n req: Request,\n res: Response,\n next: NextFunction,\n permissionApi: PermissionsService,\n auditSession: ProxyAuditSession,\n onSocketClose: () => void,\n isClientDisconnected: () => boolean,\n ): Promise<boolean> {\n const authorizeResponse = await permissionApi.authorize(\n [{ permission: kubernetesProxyPermission }],\n { credentials: await this.httpAuth.credentials(req) },\n );\n\n if (authorizeResponse[0].result === AuthorizeResult.DENY) {\n req.socket.removeListener('close', onSocketClose);\n auditSession.finalize(new NotAllowedError('Unauthorized'));\n res.status(403).json({\n error: serializeError(new NotAllowedError('Unauthorized')),\n });\n return false;\n }\n\n if (isClientDisconnected()) {\n auditSession.finalize(\n new Error('Client disconnected before proxy dispatch'),\n );\n return false;\n }\n\n const prepared = await this.prepareProxyTarget(req);\n auditSession.setResolvedClusterName(prepared.cluster.name);\n (req as any)[PROXY_PREPARED_TARGET] = prepared;\n const middleware = this.getOrCreateMiddleware(prepared.cluster);\n\n if (isClientDisconnected()) {\n auditSession.finalize(\n new Error('Client disconnected before proxy dispatch'),\n );\n return false;\n }\n\n return KubernetesProxy.dispatchToProxy(\n req,\n res,\n next,\n middleware,\n auditSession,\n onSocketClose,\n );\n }\n\n /**\n * Resolves the cluster, credentials, and target URL for a proxy request\n * before dispatching to the middleware. This ensures all async preparation\n * completes under the caller's try/catch, avoiding unhandled rejections\n * from http-proxy-middleware's fire-and-forget upgrade() path.\n */\n private async prepareProxyTarget(req: Request): Promise<PreparedProxyTarget> {\n const cluster = await this.getClusterForRequest(req);\n const url = new URL(cluster.url);\n\n const { bufferFromFileOrString } = await import('@kubernetes/client-node');\n\n const target: any = {\n protocol: url.protocol,\n host: url.hostname,\n port: url.port,\n ca: bufferFromFileOrString(cluster.caFile, cluster.caData)?.toString(),\n };\n\n const authHeader = req.headers[HEADER_KUBERNETES_AUTH.toLowerCase()];\n if (typeof authHeader === 'string') {\n req.headers.authorization = authHeader;\n } else {\n const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(\n req.headers,\n );\n\n const credential = await this.authStrategy.getCredential(\n cluster,\n authObj,\n );\n\n if (credential.type === 'bearer token') {\n req.headers.authorization = `Bearer ${credential.token}`;\n } else if (credential.type === 'x509 client certificate') {\n target.key = credential.key;\n target.cert = credential.cert;\n }\n }\n\n const requestPath = req.originalUrl || req.url || '';\n const rewrittenPath = requestPath.replace(\n new RegExp(\n `^${(req.baseUrl || '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}`,\n ),\n url.pathname || '',\n );\n\n return { proxyTarget: target, rewrittenPath, cluster };\n }\n\n // We create one middleware per remote cluster and hold on to them, because\n // the secure property isn't possible to decide on a per-request basis with a\n // single middleware instance. Target resolution and path rewriting are handled\n // per-request via the PROXY_PREPARED_TARGET symbol stashed on each req by\n // prepareProxyTarget. Entries are refreshed after a TTL or when cluster details\n // change.\n private getOrCreateMiddleware(cluster: ClusterDetails): RequestHandler {\n let middleware = this.middlewareCache.get(cluster);\n if (!middleware) {\n const logger = this.logger.child({ cluster: cluster.name });\n middleware = createProxyMiddleware({\n logProvider: () => ({\n log: logger.info.bind(logger),\n debug: logger.debug.bind(logger),\n info: logger.info.bind(logger),\n warn: logger.warn.bind(logger),\n error: logger.error.bind(logger),\n }),\n // ws must be false to prevent http-proxy-middleware from auto-subscribing\n // to the server's 'upgrade' event, which would bypass Express routing and\n // skip audit event creation for WebSocket requests.\n ws: false,\n secure: !cluster.skipTLSVerify,\n changeOrigin: true,\n router: req => {\n return (req as any)[PROXY_PREPARED_TARGET]?.proxyTarget;\n },\n pathRewrite: (_path, req) => {\n return (req as any)[PROXY_PREPARED_TARGET]?.rewrittenPath ?? _path;\n },\n onError: (error, req, res) => {\n const wrappedError = new ForwardedError(\n `Cluster '${cluster.name}' request error`,\n error,\n );\n\n logger.error('Kubernetes proxy error', wrappedError);\n\n if (typeof (res as { status?: unknown }).status !== 'function') {\n return;\n }\n\n const body: ErrorResponseBody = {\n error: serializeError(wrappedError, {\n includeStack: process.env.NODE_ENV === 'development',\n }),\n request: { method: req.method, url: req.originalUrl },\n response: { statusCode: 500 },\n };\n res.status(500).json(body);\n },\n onProxyReqWs: ProxyAuditSession.handleWebSocketProxyReq,\n });\n this.middlewareCache.set(cluster, middleware);\n }\n return middleware;\n }\n\n /**\n * Branches to WebSocket upgrade or HTTP middleware dispatch and wires\n * up the appropriate audit listeners. Returns `true` when HTTP finish\n * listeners have been attached (used by the caller's catch block to\n * avoid double-finalizing).\n */\n private static dispatchToProxy(\n req: Request,\n res: Response,\n next: NextFunction,\n middleware: RequestHandler,\n auditSession: ProxyAuditSession,\n onSocketClose: () => void,\n ): boolean {\n const isWebSocketUpgrade =\n (req.header('connection') ?? '')\n .toLowerCase()\n .split(',')\n .some(t => t.trim() === 'upgrade') &&\n req.header('upgrade')?.toLowerCase() === 'websocket';\n\n if (isWebSocketUpgrade) {\n auditSession.prepareForWebSocketUpgrade(req, onSocketClose);\n\n try {\n middleware.upgrade!(req, req.socket, undefined);\n } catch (error) {\n req.socket.removeListener('close', onSocketClose);\n auditSession.finalize(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n return false;\n }\n\n // HTTP path: remove the early close listener; HTTP audit listeners\n // on the response take over from here.\n req.socket.removeListener('close', onSocketClose);\n auditSession.attachToHttpResponse(res);\n middleware(req, res, next);\n return true;\n }\n\n private async getClusterForRequest(req: Request): Promise<ClusterDetails> {\n const { KubeConfig } = await import('@kubernetes/client-node');\n\n const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];\n const clusters = await this.clusterSupplier.getClusters({\n credentials: await this.httpAuth.credentials(req),\n });\n\n if (!clusters || clusters.length <= 0) {\n throw new NotFoundError(`No Clusters configured`);\n }\n\n const hasClusterNameHeader =\n typeof clusterName === 'string' && clusterName.length > 0;\n\n let cluster: ClusterDetails | undefined;\n\n if (hasClusterNameHeader) {\n cluster = clusters.find(c => c.name === clusterName);\n } else if (clusters.length === 1) {\n cluster = clusters.at(0);\n }\n\n if (!cluster) {\n throw new NotFoundError(`Cluster '${clusterName}' not found`);\n }\n\n const authProvider =\n cluster.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n\n if (\n authProvider === 'serviceAccount' &&\n fs.pathExistsSync(SERVICEACCOUNT_CA_PATH) &&\n !cluster.authMetadata.serviceAccountToken\n ) {\n const kc = new KubeConfig();\n kc.loadFromCluster();\n const clusterFromKubeConfig = kc.getCurrentCluster() as Cluster;\n\n const url = new URL(clusterFromKubeConfig.server);\n cluster.url = clusterFromKubeConfig.server;\n if (url.protocol === 'https:') {\n cluster.caFile = clusterFromKubeConfig.caFile;\n }\n }\n\n return cluster;\n }\n\n private static authHeadersToKubernetesRequestAuth(\n originalHeaders: IncomingHttpHeaders,\n ): KubernetesRequestAuth {\n return Object.keys(originalHeaders)\n .filter(header => header.startsWith('backstage-kubernetes-authorization'))\n .map(header =>\n KubernetesProxy.headerToDictionary(header, originalHeaders),\n )\n .filter(headerAsDic => Object.keys(headerAsDic).length !== 0)\n .reduce(KubernetesProxy.combineHeaders, {});\n }\n\n private static headerToDictionary(\n header: string,\n originalHeaders: IncomingHttpHeaders,\n ): KubernetesRequestAuth {\n const obj: KubernetesRequestAuth = {};\n const headerSplit = header.split('-');\n if (headerSplit.length >= 4) {\n const framework = headerSplit[3].toLowerCase();\n if (headerSplit.length >= 5) {\n const provider = headerSplit.slice(4).join('-').toLowerCase();\n obj[framework] = { [provider]: originalHeaders[header] };\n } else {\n obj[framework] = originalHeaders[header];\n }\n }\n return obj;\n }\n\n private static combineHeaders(\n authObj: any,\n header: any,\n ): KubernetesRequestAuth {\n const framework = Object.keys(header)[0];\n\n if (authObj[framework]) {\n authObj[framework] = {\n ...authObj[framework],\n ...header[framework],\n };\n } else {\n authObj[framework] = header[framework];\n }\n\n return authObj;\n }\n}\n"],"names":["ProxyMiddlewareCache","resolveProxyMiddlewareCacheOptions","ProxyAuditSession","kubernetesProxyPermission","AuthorizeResult","NotAllowedError","serializeError","createProxyMiddleware","ForwardedError","NotFoundError","ANNOTATION_KUBERNETES_AUTH_PROVIDER","fs","SERVICEACCOUNT_CA_PATH"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
|
|
4
|
-
var pluginPermissionNode = require('@backstage/plugin-permission-node');
|
|
5
4
|
var express = require('express');
|
|
6
5
|
var Router = require('express-promise-router');
|
|
7
6
|
require('@aws-sdk/credential-providers');
|
|
@@ -113,11 +112,6 @@ class KubernetesRouter {
|
|
|
113
112
|
const router = Router__default.default();
|
|
114
113
|
router.use("/proxy", proxy.createRequestHandler({ permissionApi }));
|
|
115
114
|
router.use(express__default.default.json());
|
|
116
|
-
router.use(
|
|
117
|
-
pluginPermissionNode.createPermissionIntegrationRouter({
|
|
118
|
-
permissions: pluginKubernetesCommon.kubernetesPermissions
|
|
119
|
-
})
|
|
120
|
-
);
|
|
121
115
|
router.post("/services/:serviceId", async (req, res) => {
|
|
122
116
|
const serviceId = req.params.serviceId;
|
|
123
117
|
const requestBody = req.body;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KubernetesRouter.cjs.js","sources":["../../src/service/KubernetesRouter.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 */\nimport { Config } from '@backstage/config';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,\n kubernetesClustersReadPermission,\n kubernetesPermissions,\n kubernetesResourcesReadPermission,\n} from '@backstage/plugin-kubernetes-common';\nimport { PermissionEvaluator } from '@backstage/plugin-permission-common';\nimport { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';\nimport express from 'express';\nimport Router from 'express-promise-router';\n\nimport { DispatchStrategy } from '../auth';\nimport { NotAllowedError, toError } from '@backstage/errors';\n\nimport {\n AuthService,\n AuditorService,\n BackstageCredentials,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n AuthMetadata,\n KubernetesClustersSupplier,\n KubernetesFetcher,\n KubernetesObjectsProvider,\n KubernetesRouterFactory,\n KubernetesServiceLocator,\n} from '@backstage/plugin-kubernetes-node';\nimport { addResourceRoutesToRouter } from '../routes/resourcesRoutes';\nimport { ObjectsByEntityRequest } from '../types/types';\nimport { KubernetesProxy } from './KubernetesProxy';\nimport { requirePermission } from '../auth/requirePermission';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';\nimport { resolveProxyMiddlewareCacheOptions } from './ProxyMiddlewareCache';\n\nexport interface KubernetesEnvironment {\n logger: LoggerService;\n config: Config;\n catalog: CatalogService;\n discovery: DiscoveryService;\n permissions: PermissionEvaluator;\n auth: AuthService;\n httpAuth: HttpAuthService;\n auditor: AuditorService;\n authStrategyMap: { [key: string]: AuthenticationStrategy };\n fetcher: KubernetesFetcher;\n clusterSupplier: KubernetesClustersSupplier;\n serviceLocator: KubernetesServiceLocator;\n objectsProvider: KubernetesObjectsProvider;\n customRouter?: KubernetesRouterFactory;\n}\n\nexport class KubernetesRouter {\n static create(env: KubernetesEnvironment) {\n return new KubernetesRouter(env);\n }\n\n protected readonly env: KubernetesEnvironment;\n\n constructor(env: KubernetesEnvironment) {\n this.env = env;\n }\n\n public async getRouter() {\n const {\n logger,\n config,\n permissions,\n authStrategyMap,\n clusterSupplier,\n objectsProvider,\n catalog,\n discovery,\n httpAuth,\n customRouter,\n } = this.env;\n\n logger.info('Initializing Kubernetes backend');\n\n if (!config.has('kubernetes')) {\n if (process.env.NODE_ENV !== 'development') {\n throw new Error('Kubernetes configuration is missing');\n }\n logger.warn(\n 'Failed to initialize kubernetes backend: kubernetes config is missing',\n );\n return Router();\n }\n\n await this.warnForClustersWithSkipTLSVerify(logger, clusterSupplier);\n\n const proxy = this.buildProxy(\n logger,\n clusterSupplier,\n discovery,\n httpAuth,\n authStrategyMap,\n );\n\n return (\n customRouter?.({\n getDefault: () =>\n this.buildDefaultRouter(\n objectsProvider,\n clusterSupplier,\n catalog,\n proxy,\n permissions,\n httpAuth,\n authStrategyMap,\n ),\n objectsProvider,\n clusterSupplier,\n authStrategyMap,\n }) ??\n this.buildDefaultRouter(\n objectsProvider,\n clusterSupplier,\n catalog,\n proxy,\n permissions,\n httpAuth,\n authStrategyMap,\n )\n );\n }\n\n private buildProxy(\n logger: LoggerService,\n clusterSupplier: KubernetesClustersSupplier,\n discovery: DiscoveryService,\n httpAuth: HttpAuthService,\n authStrategyMap: { [key: string]: AuthenticationStrategy },\n ): KubernetesProxy {\n const authStrategy = new DispatchStrategy({\n authStrategyMap,\n });\n const middlewareCacheConfig = this.env.config.getOptionalConfig(\n 'kubernetes.proxy.middlewareCache',\n );\n const { ttlMs, maxSize } = resolveProxyMiddlewareCacheOptions({\n ttlMs: middlewareCacheConfig?.getOptionalNumber('ttl.milliseconds'),\n maxSize: middlewareCacheConfig?.getOptionalNumber('maxSize'),\n });\n\n return new KubernetesProxy({\n logger,\n clusterSupplier,\n authStrategy,\n discovery,\n httpAuth,\n auditor: this.env.auditor,\n middlewareCache: { ttlMs, maxSize },\n });\n }\n\n private buildDefaultRouter(\n objectsProvider: KubernetesObjectsProvider,\n clusterSupplier: KubernetesClustersSupplier,\n catalog: CatalogService,\n proxy: KubernetesProxy,\n permissionApi: PermissionEvaluator,\n httpAuth: HttpAuthService,\n authStrategyMap: { [key: string]: AuthenticationStrategy },\n ): express.Router {\n const logger = this.env.logger;\n const auditor = this.env.auditor;\n const router = Router();\n router.use('/proxy', proxy.createRequestHandler({ permissionApi }));\n router.use(express.json());\n router.use(\n createPermissionIntegrationRouter({\n permissions: kubernetesPermissions,\n }),\n );\n\n // @deprecated\n router.post('/services/:serviceId', async (req, res) => {\n const serviceId = req.params.serviceId;\n const requestBody: ObjectsByEntityRequest = req.body;\n let entityRef: string | undefined;\n if (requestBody?.entity) {\n try {\n entityRef = stringifyEntityRef(requestBody.entity);\n } catch {\n entityRef = undefined;\n }\n }\n\n const auditorEvent = await auditor.createEvent({\n eventId: 'resource-fetch',\n request: req,\n meta: { queryType: 'services', entityRef, serviceId },\n });\n\n try {\n await requirePermission(\n permissionApi,\n kubernetesResourcesReadPermission,\n httpAuth,\n req,\n );\n\n const credentials = await httpAuth.credentials(req);\n\n let resolvedEntity = requestBody?.entity;\n if (requestBody?.entity) {\n if (!entityRef) {\n throw new NotAllowedError('Invalid entity reference');\n }\n const parsedRef = parseEntityRef(entityRef);\n const catalogEntity = await catalog.getEntityByRef(parsedRef, {\n credentials,\n });\n if (!catalogEntity) {\n throw new NotAllowedError(`Entity not found, ${entityRef}`);\n }\n resolvedEntity = catalogEntity;\n }\n\n const response = await objectsProvider.getKubernetesObjectsByEntity(\n {\n entity: resolvedEntity,\n auth: requestBody?.auth || {},\n },\n { credentials },\n );\n res.json(response);\n auditorEvent\n .success()\n .catch(error =>\n logger.error(\n 'Failed to emit audit event resource-fetch (services)',\n error,\n ),\n );\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n await auditorEvent.fail({ error: err });\n if (e instanceof NotAllowedError) {\n throw e;\n }\n logger.error(\n `action=retrieveObjectsByServiceId service=${serviceId}, error: ${err}`,\n );\n if (!res.headersSent) {\n res.status(500).json({ error: err.message });\n }\n }\n });\n\n router.get('/clusters', async (req, res) => {\n const auditorEvent = await auditor.createEvent({\n eventId: 'cluster-fetch',\n request: req,\n meta: { queryType: 'list' },\n });\n\n try {\n await requirePermission(\n permissionApi,\n kubernetesClustersReadPermission,\n httpAuth,\n req,\n );\n const credentials = await httpAuth.credentials(req);\n const clusterDetails = await this.fetchClusterDetails(clusterSupplier, {\n credentials,\n });\n res.json({\n items: clusterDetails.map(cd => {\n const oidcTokenProvider =\n cd.authMetadata[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];\n const authProvider =\n cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n const strategy = authStrategyMap[authProvider];\n let auth: AuthMetadata = {};\n if (strategy) {\n auth = strategy.presentAuthMetadata(cd.authMetadata);\n }\n\n return {\n name: cd.name,\n title: cd.title,\n dashboardUrl: cd.dashboardUrl,\n authProvider,\n ...(oidcTokenProvider && { oidcTokenProvider }),\n ...(auth && Object.keys(auth).length !== 0 && { auth }),\n };\n }),\n });\n auditorEvent\n .success()\n .catch(error =>\n logger.error(\n 'Failed to emit audit event cluster-fetch (list)',\n error,\n ),\n );\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n await auditorEvent.fail({ error: err });\n throw e;\n }\n });\n\n addResourceRoutesToRouter(\n router,\n catalog,\n objectsProvider,\n httpAuth,\n permissionApi,\n auditor,\n logger,\n );\n\n return router;\n }\n\n private async fetchClusterDetails(\n clusterSupplier: KubernetesClustersSupplier,\n options: { credentials: BackstageCredentials },\n ) {\n const clusterDetails = await clusterSupplier.getClusters(options);\n\n this.env.logger.debug(\n `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,\n );\n\n return clusterDetails;\n }\n\n private async warnForClustersWithSkipTLSVerify(\n logger: LoggerService,\n clusterSupplier: KubernetesClustersSupplier,\n ): Promise<void> {\n try {\n const credentials = await this.env.auth.getOwnServiceCredentials();\n const clusters = await clusterSupplier.getClusters({ credentials });\n\n for (const cluster of clusters) {\n if (cluster.skipTLSVerify) {\n logger.warn(\n `Cluster '${cluster.name}' is configured with skipTLSVerify: true; TLS certificate verification is disabled for Kubernetes API traffic to this cluster`,\n );\n }\n }\n } catch (error) {\n logger.warn(\n `Failed to log skipTLSVerify warnings at startup: ${\n toError(error).message\n }`,\n );\n }\n }\n}\n"],"names":["Router","DispatchStrategy","resolveProxyMiddlewareCacheOptions","KubernetesProxy","express","createPermissionIntegrationRouter","kubernetesPermissions","stringifyEntityRef","requirePermission","kubernetesResourcesReadPermission","NotAllowedError","parseEntityRef","kubernetesClustersReadPermission","ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER","ANNOTATION_KUBERNETES_AUTH_PROVIDER","addResourceRoutesToRouter","toError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KubernetesRouter.cjs.js","sources":["../../src/service/KubernetesRouter.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 */\nimport { Config } from '@backstage/config';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,\n kubernetesClustersReadPermission,\n kubernetesResourcesReadPermission,\n} from '@backstage/plugin-kubernetes-common';\nimport { PermissionEvaluator } from '@backstage/plugin-permission-common';\nimport express from 'express';\nimport Router from 'express-promise-router';\n\nimport { DispatchStrategy } from '../auth';\nimport { NotAllowedError, toError } from '@backstage/errors';\n\nimport {\n AuthService,\n AuditorService,\n BackstageCredentials,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n AuthMetadata,\n KubernetesClustersSupplier,\n KubernetesFetcher,\n KubernetesObjectsProvider,\n KubernetesRouterFactory,\n KubernetesServiceLocator,\n} from '@backstage/plugin-kubernetes-node';\nimport { addResourceRoutesToRouter } from '../routes/resourcesRoutes';\nimport { ObjectsByEntityRequest } from '../types/types';\nimport { KubernetesProxy } from './KubernetesProxy';\nimport { requirePermission } from '../auth/requirePermission';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';\nimport { resolveProxyMiddlewareCacheOptions } from './ProxyMiddlewareCache';\n\nexport interface KubernetesEnvironment {\n logger: LoggerService;\n config: Config;\n catalog: CatalogService;\n discovery: DiscoveryService;\n permissions: PermissionEvaluator;\n auth: AuthService;\n httpAuth: HttpAuthService;\n auditor: AuditorService;\n authStrategyMap: { [key: string]: AuthenticationStrategy };\n fetcher: KubernetesFetcher;\n clusterSupplier: KubernetesClustersSupplier;\n serviceLocator: KubernetesServiceLocator;\n objectsProvider: KubernetesObjectsProvider;\n customRouter?: KubernetesRouterFactory;\n}\n\nexport class KubernetesRouter {\n static create(env: KubernetesEnvironment) {\n return new KubernetesRouter(env);\n }\n\n protected readonly env: KubernetesEnvironment;\n\n constructor(env: KubernetesEnvironment) {\n this.env = env;\n }\n\n public async getRouter() {\n const {\n logger,\n config,\n permissions,\n authStrategyMap,\n clusterSupplier,\n objectsProvider,\n catalog,\n discovery,\n httpAuth,\n customRouter,\n } = this.env;\n\n logger.info('Initializing Kubernetes backend');\n\n if (!config.has('kubernetes')) {\n if (process.env.NODE_ENV !== 'development') {\n throw new Error('Kubernetes configuration is missing');\n }\n logger.warn(\n 'Failed to initialize kubernetes backend: kubernetes config is missing',\n );\n return Router();\n }\n\n await this.warnForClustersWithSkipTLSVerify(logger, clusterSupplier);\n\n const proxy = this.buildProxy(\n logger,\n clusterSupplier,\n discovery,\n httpAuth,\n authStrategyMap,\n );\n\n return (\n customRouter?.({\n getDefault: () =>\n this.buildDefaultRouter(\n objectsProvider,\n clusterSupplier,\n catalog,\n proxy,\n permissions,\n httpAuth,\n authStrategyMap,\n ),\n objectsProvider,\n clusterSupplier,\n authStrategyMap,\n }) ??\n this.buildDefaultRouter(\n objectsProvider,\n clusterSupplier,\n catalog,\n proxy,\n permissions,\n httpAuth,\n authStrategyMap,\n )\n );\n }\n\n private buildProxy(\n logger: LoggerService,\n clusterSupplier: KubernetesClustersSupplier,\n discovery: DiscoveryService,\n httpAuth: HttpAuthService,\n authStrategyMap: { [key: string]: AuthenticationStrategy },\n ): KubernetesProxy {\n const authStrategy = new DispatchStrategy({\n authStrategyMap,\n });\n const middlewareCacheConfig = this.env.config.getOptionalConfig(\n 'kubernetes.proxy.middlewareCache',\n );\n const { ttlMs, maxSize } = resolveProxyMiddlewareCacheOptions({\n ttlMs: middlewareCacheConfig?.getOptionalNumber('ttl.milliseconds'),\n maxSize: middlewareCacheConfig?.getOptionalNumber('maxSize'),\n });\n\n return new KubernetesProxy({\n logger,\n clusterSupplier,\n authStrategy,\n discovery,\n httpAuth,\n auditor: this.env.auditor,\n middlewareCache: { ttlMs, maxSize },\n });\n }\n\n private buildDefaultRouter(\n objectsProvider: KubernetesObjectsProvider,\n clusterSupplier: KubernetesClustersSupplier,\n catalog: CatalogService,\n proxy: KubernetesProxy,\n permissionApi: PermissionEvaluator,\n httpAuth: HttpAuthService,\n authStrategyMap: { [key: string]: AuthenticationStrategy },\n ): express.Router {\n const logger = this.env.logger;\n const auditor = this.env.auditor;\n const router = Router();\n router.use('/proxy', proxy.createRequestHandler({ permissionApi }));\n router.use(express.json());\n\n // @deprecated\n router.post('/services/:serviceId', async (req, res) => {\n const serviceId = req.params.serviceId;\n const requestBody: ObjectsByEntityRequest = req.body;\n let entityRef: string | undefined;\n if (requestBody?.entity) {\n try {\n entityRef = stringifyEntityRef(requestBody.entity);\n } catch {\n entityRef = undefined;\n }\n }\n\n const auditorEvent = await auditor.createEvent({\n eventId: 'resource-fetch',\n request: req,\n meta: { queryType: 'services', entityRef, serviceId },\n });\n\n try {\n await requirePermission(\n permissionApi,\n kubernetesResourcesReadPermission,\n httpAuth,\n req,\n );\n\n const credentials = await httpAuth.credentials(req);\n\n let resolvedEntity = requestBody?.entity;\n if (requestBody?.entity) {\n if (!entityRef) {\n throw new NotAllowedError('Invalid entity reference');\n }\n const parsedRef = parseEntityRef(entityRef);\n const catalogEntity = await catalog.getEntityByRef(parsedRef, {\n credentials,\n });\n if (!catalogEntity) {\n throw new NotAllowedError(`Entity not found, ${entityRef}`);\n }\n resolvedEntity = catalogEntity;\n }\n\n const response = await objectsProvider.getKubernetesObjectsByEntity(\n {\n entity: resolvedEntity,\n auth: requestBody?.auth || {},\n },\n { credentials },\n );\n res.json(response);\n auditorEvent\n .success()\n .catch(error =>\n logger.error(\n 'Failed to emit audit event resource-fetch (services)',\n error,\n ),\n );\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n await auditorEvent.fail({ error: err });\n if (e instanceof NotAllowedError) {\n throw e;\n }\n logger.error(\n `action=retrieveObjectsByServiceId service=${serviceId}, error: ${err}`,\n );\n if (!res.headersSent) {\n res.status(500).json({ error: err.message });\n }\n }\n });\n\n router.get('/clusters', async (req, res) => {\n const auditorEvent = await auditor.createEvent({\n eventId: 'cluster-fetch',\n request: req,\n meta: { queryType: 'list' },\n });\n\n try {\n await requirePermission(\n permissionApi,\n kubernetesClustersReadPermission,\n httpAuth,\n req,\n );\n const credentials = await httpAuth.credentials(req);\n const clusterDetails = await this.fetchClusterDetails(clusterSupplier, {\n credentials,\n });\n res.json({\n items: clusterDetails.map(cd => {\n const oidcTokenProvider =\n cd.authMetadata[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];\n const authProvider =\n cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n const strategy = authStrategyMap[authProvider];\n let auth: AuthMetadata = {};\n if (strategy) {\n auth = strategy.presentAuthMetadata(cd.authMetadata);\n }\n\n return {\n name: cd.name,\n title: cd.title,\n dashboardUrl: cd.dashboardUrl,\n authProvider,\n ...(oidcTokenProvider && { oidcTokenProvider }),\n ...(auth && Object.keys(auth).length !== 0 && { auth }),\n };\n }),\n });\n auditorEvent\n .success()\n .catch(error =>\n logger.error(\n 'Failed to emit audit event cluster-fetch (list)',\n error,\n ),\n );\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n await auditorEvent.fail({ error: err });\n throw e;\n }\n });\n\n addResourceRoutesToRouter(\n router,\n catalog,\n objectsProvider,\n httpAuth,\n permissionApi,\n auditor,\n logger,\n );\n\n return router;\n }\n\n private async fetchClusterDetails(\n clusterSupplier: KubernetesClustersSupplier,\n options: { credentials: BackstageCredentials },\n ) {\n const clusterDetails = await clusterSupplier.getClusters(options);\n\n this.env.logger.debug(\n `action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,\n );\n\n return clusterDetails;\n }\n\n private async warnForClustersWithSkipTLSVerify(\n logger: LoggerService,\n clusterSupplier: KubernetesClustersSupplier,\n ): Promise<void> {\n try {\n const credentials = await this.env.auth.getOwnServiceCredentials();\n const clusters = await clusterSupplier.getClusters({ credentials });\n\n for (const cluster of clusters) {\n if (cluster.skipTLSVerify) {\n logger.warn(\n `Cluster '${cluster.name}' is configured with skipTLSVerify: true; TLS certificate verification is disabled for Kubernetes API traffic to this cluster`,\n );\n }\n }\n } catch (error) {\n logger.warn(\n `Failed to log skipTLSVerify warnings at startup: ${\n toError(error).message\n }`,\n );\n }\n }\n}\n"],"names":["Router","DispatchStrategy","resolveProxyMiddlewareCacheOptions","KubernetesProxy","express","stringifyEntityRef","requirePermission","kubernetesResourcesReadPermission","NotAllowedError","parseEntityRef","kubernetesClustersReadPermission","ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER","ANNOTATION_KUBERNETES_AUTH_PROVIDER","addResourceRoutesToRouter","toError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProxyAuditSession.cjs.js","sources":["../../src/service/ProxyAuditSession.ts"],"sourcesContent":["/*\n * Copyright 2026 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 type {\n AuditorService,\n AuditorServiceEvent,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Request, Response } from 'express';\n\nconst PROXY_WEBSOCKET_AUDIT = Symbol('kubernetesProxyWebSocketAudit');\n\ntype WebSocketAuditContext = {\n session: ProxyAuditSession;\n onSocketClose: () => void;\n};\n\n/**\n * Tracks a single proxy request through audit event creation and finalization.\n * When no auditor is configured every method is a no-op.\n */\nexport class ProxyAuditSession {\n private resolvedClusterName?: string;\n private finalized = false;\n\n private constructor(\n private readonly event: AuditorServiceEvent | undefined,\n private readonly initialClusterName: string,\n private readonly logger: LoggerService,\n ) {}\n\n static async start(\n auditor: AuditorService | undefined,\n options: {\n req: Request;\n clusterName: string;\n method: string | undefined;\n path: string;\n logger: LoggerService;\n },\n ): Promise<ProxyAuditSession> {\n if (!auditor) {\n return new ProxyAuditSession(\n undefined,\n options.clusterName,\n options.logger,\n );\n }\n\n const sanitizedUrl = (options.req.originalUrl ?? options.req.url).split(\n '?',\n )[0];\n const sanitizedRequest = Object.create(options.req, {\n originalUrl: { value: sanitizedUrl, enumerable: true },\n url: { value: sanitizedUrl, enumerable: true },\n });\n\n const event = await auditor.createEvent({\n eventId: 'cluster-fetch',\n severityLevel: 'medium',\n request: sanitizedRequest,\n meta: {\n queryType: 'proxy',\n clusterName: options.clusterName,\n method: options.method,\n path: options.path,\n },\n });\n\n return new ProxyAuditSession(event, options.clusterName, options.logger);\n }\n\n setResolvedClusterName(name: string): void {\n this.resolvedClusterName = name;\n }\n\n finalize(error?: Error): void {\n if (this.finalized) {\n return;\n }\n this.finalized = true;\n\n if (!this.event) {\n return;\n }\n\n const terminalMeta =\n this.resolvedClusterName &&\n this.resolvedClusterName !== this.initialClusterName\n ? { clusterName: this.resolvedClusterName }\n : undefined;\n\n const audit = error\n ? this.event.fail({\n error,\n ...(terminalMeta && { meta: terminalMeta }),\n })\n : this.event.success(terminalMeta ? { meta: terminalMeta } : undefined);\n\n audit.catch(err =>\n this.logger.error('Failed to emit proxy audit event', err),\n );\n }\n\n attachToHttpResponse(res: Response): void {\n const finalizeFromResponse = () => {\n if (!res.writableFinished) {\n this.finalize(\n new Error('Client disconnected before response completed'),\n );\n } else if (res.statusCode >= 400) {\n this.finalize(\n new Error(`Proxy responded with status ${res.statusCode}`),\n );\n } else {\n this.finalize();\n }\n };\n\n const finalizeOnce = () => {\n res.removeListener('finish', finalizeOnce);\n res.removeListener('close', finalizeOnce);\n finalizeFromResponse();\n };\n\n res.once('finish', finalizeOnce);\n res.once('close', finalizeOnce);\n }\n\n prepareForWebSocketUpgrade(req: Request, onSocketClose: () => void): void {\n (req as Request & { [PROXY_WEBSOCKET_AUDIT]?: WebSocketAuditContext })[\n PROXY_WEBSOCKET_AUDIT\n ] = { session: this, onSocketClose };\n }\n\n static handleWebSocketProxyReq(\n proxyReq: import('http').ClientRequest,\n req: import('http').IncomingMessage,\n socket: import('net').Socket,\n ): void {\n const typedReq = req as Request & {\n [PROXY_WEBSOCKET_AUDIT]?: WebSocketAuditContext;\n };\n const context = typedReq[PROXY_WEBSOCKET_AUDIT];\n if (!context) {\n return;\n }\n delete typedReq[PROXY_WEBSOCKET_AUDIT];\n\n const { session, onSocketClose } = context;\n const cleanups: (() => void)[] = [];\n const finalize = (error?: Error) => {\n session.finalize(error);\n cleanups.forEach(fn => fn());\n req.socket.removeListener('close', onSocketClose);\n };\n\n const onUpgrade = () => finalize();\n const onResponse = (proxyRes: import('http').IncomingMessage) => {\n finalize(\n new Error(\n `Upstream rejected WebSocket upgrade with status ${proxyRes.statusCode}`,\n ),\n );\n };\n const onProxyError = (err: Error) => finalize(err);\n const onSocketError = (err: Error | unknown) =>\n finalize(err instanceof Error ? err : new Error(String(err)));\n const onSocketClosed = () =>\n finalize(\n new Error('Client closed connection during WebSocket handshake'),\n );\n\n proxyReq.once('upgrade', onUpgrade);\n proxyReq.once('response', onResponse);\n proxyReq.once('error', onProxyError);\n socket.once('error', onSocketError);\n socket.once('close', onSocketClosed);\n\n cleanups.push(\n () => proxyReq.removeListener('upgrade', onUpgrade),\n () => proxyReq.removeListener('response', onResponse),\n () => proxyReq.removeListener('error', onProxyError),\n () => socket.removeListener('error', onSocketError),\n () => socket.removeListener('close', onSocketClosed),\n );\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProxyAuditSession.cjs.js","sources":["../../src/service/ProxyAuditSession.ts"],"sourcesContent":["/*\n * Copyright 2026 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 type {\n AuditorService,\n AuditorServiceEvent,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { Request, Response } from 'express';\n\nconst PROXY_WEBSOCKET_AUDIT = Symbol('kubernetesProxyWebSocketAudit');\n\ntype WebSocketAuditContext = {\n session: ProxyAuditSession;\n onSocketClose: () => void;\n};\n\n/**\n * Tracks a single proxy request through audit event creation and finalization.\n * When no auditor is configured every method is a no-op.\n */\nexport class ProxyAuditSession {\n private resolvedClusterName?: string;\n private finalized = false;\n\n private constructor(\n private readonly event: AuditorServiceEvent | undefined,\n private readonly initialClusterName: string,\n private readonly logger: LoggerService,\n ) {}\n\n static async start(\n auditor: AuditorService | undefined,\n options: {\n req: Request;\n clusterName: string;\n method: string | undefined;\n path: string;\n logger: LoggerService;\n },\n ): Promise<ProxyAuditSession> {\n if (!auditor) {\n return new ProxyAuditSession(\n undefined,\n options.clusterName,\n options.logger,\n );\n }\n\n const sanitizedUrl = (options.req.originalUrl ?? options.req.url).split(\n '?',\n )[0];\n const sanitizedRequest = Object.create(options.req, {\n originalUrl: { value: sanitizedUrl, enumerable: true },\n url: { value: sanitizedUrl, enumerable: true },\n });\n\n const event = await auditor.createEvent({\n eventId: 'cluster-fetch',\n severityLevel: 'medium',\n request: sanitizedRequest,\n meta: {\n queryType: 'proxy',\n clusterName: options.clusterName,\n method: options.method,\n path: options.path,\n },\n });\n\n return new ProxyAuditSession(event, options.clusterName, options.logger);\n }\n\n setResolvedClusterName(name: string): void {\n this.resolvedClusterName = name;\n }\n\n finalize(error?: Error): void {\n if (this.finalized) {\n return;\n }\n this.finalized = true;\n\n if (!this.event) {\n return;\n }\n\n const terminalMeta =\n this.resolvedClusterName &&\n this.resolvedClusterName !== this.initialClusterName\n ? { clusterName: this.resolvedClusterName }\n : undefined;\n\n const audit = error\n ? this.event.fail({\n error,\n ...(terminalMeta && { meta: terminalMeta }),\n })\n : this.event.success(terminalMeta ? { meta: terminalMeta } : undefined);\n\n audit.catch(err =>\n this.logger.error('Failed to emit proxy audit event', err),\n );\n }\n\n attachToHttpResponse(res: Response): void {\n const finalizeFromResponse = () => {\n if (!res.writableFinished) {\n this.finalize(\n new Error('Client disconnected before response completed'),\n );\n } else if (res.statusCode >= 400) {\n this.finalize(\n new Error(`Proxy responded with status ${res.statusCode}`),\n );\n } else {\n this.finalize();\n }\n };\n\n const finalizeOnce = () => {\n res.removeListener('finish', finalizeOnce);\n res.removeListener('close', finalizeOnce);\n finalizeFromResponse();\n };\n\n res.once('finish', finalizeOnce);\n res.once('close', finalizeOnce);\n }\n\n prepareForWebSocketUpgrade(req: Request, onSocketClose: () => void): void {\n (req as Request & { [PROXY_WEBSOCKET_AUDIT]?: WebSocketAuditContext })[\n PROXY_WEBSOCKET_AUDIT\n ] = { session: this, onSocketClose };\n }\n\n static handleWebSocketProxyReq(\n proxyReq: import('http').ClientRequest,\n req: import('http').IncomingMessage,\n socket: import('net').Socket,\n ): void {\n const typedReq = req as Request & {\n [PROXY_WEBSOCKET_AUDIT]?: WebSocketAuditContext;\n };\n const context = typedReq[PROXY_WEBSOCKET_AUDIT];\n if (!context) {\n return;\n }\n delete typedReq[PROXY_WEBSOCKET_AUDIT];\n\n const { session, onSocketClose } = context;\n const cleanups: (() => void)[] = [];\n const finalize = (error?: Error) => {\n session.finalize(error);\n cleanups.forEach(fn => fn());\n req.socket.removeListener('close', onSocketClose);\n };\n\n const onUpgrade = () => finalize();\n const onResponse = (proxyRes: import('http').IncomingMessage) => {\n finalize(\n new Error(\n `Upstream rejected WebSocket upgrade with status ${proxyRes.statusCode}`,\n ),\n );\n };\n const onProxyError = (err: Error) => finalize(err);\n const onSocketError = (err: Error | unknown) =>\n finalize(err instanceof Error ? err : new Error(String(err)));\n const onSocketClosed = () =>\n finalize(\n new Error('Client closed connection during WebSocket handshake'),\n );\n\n proxyReq.once('upgrade', onUpgrade);\n proxyReq.once('response', onResponse);\n proxyReq.once('error', onProxyError);\n socket.once('error', onSocketError);\n socket.once('close', onSocketClosed);\n\n cleanups.push(\n () => proxyReq.removeListener('upgrade', onUpgrade),\n () => proxyReq.removeListener('response', onResponse),\n () => proxyReq.removeListener('error', onProxyError),\n () => socket.removeListener('error', onSocketError),\n () => socket.removeListener('close', onSocketClosed),\n );\n }\n}\n"],"names":[],"mappings":";;AAuBA,MAAM,qBAAA,0BAA+B,+BAA+B,CAAA;AAW7D,MAAM,iBAAA,CAAkB;AAAA,EAIrB,WAAA,CACW,KAAA,EACA,kBAAA,EACA,MAAA,EACjB;AAHiB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,kBAAA,GAAA,kBAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAChB;AAAA,EAHgB,KAAA;AAAA,EACA,kBAAA;AAAA,EACA,MAAA;AAAA,EANX,mBAAA;AAAA,EACA,SAAA,GAAY,KAAA;AAAA,EAQpB,aAAa,KAAA,CACX,OAAA,EACA,OAAA,EAO4B;AAC5B,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,OAAO,IAAI,iBAAA;AAAA,QACT,MAAA;AAAA,QACA,OAAA,CAAQ,WAAA;AAAA,QACR,OAAA,CAAQ;AAAA,OACV;AAAA,IACF;AAEA,IAAA,MAAM,gBAAgB,OAAA,CAAQ,GAAA,CAAI,WAAA,IAAe,OAAA,CAAQ,IAAI,GAAA,EAAK,KAAA;AAAA,MAChE;AAAA,MACA,CAAC,CAAA;AACH,IAAA,MAAM,gBAAA,GAAmB,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,GAAA,EAAK;AAAA,MAClD,WAAA,EAAa,EAAE,KAAA,EAAO,YAAA,EAAc,YAAY,IAAA,EAAK;AAAA,MACrD,GAAA,EAAK,EAAE,KAAA,EAAO,YAAA,EAAc,YAAY,IAAA;AAAK,KAC9C,CAAA;AAED,IAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,WAAA,CAAY;AAAA,MACtC,OAAA,EAAS,eAAA;AAAA,MACT,aAAA,EAAe,QAAA;AAAA,MACf,OAAA,EAAS,gBAAA;AAAA,MACT,IAAA,EAAM;AAAA,QACJ,SAAA,EAAW,OAAA;AAAA,QACX,aAAa,OAAA,CAAQ,WAAA;AAAA,QACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,QAChB,MAAM,OAAA,CAAQ;AAAA;AAChB,KACD,CAAA;AAED,IAAA,OAAO,IAAI,iBAAA,CAAkB,KAAA,EAAO,OAAA,CAAQ,WAAA,EAAa,QAAQ,MAAM,CAAA;AAAA,EACzE;AAAA,EAEA,uBAAuB,IAAA,EAAoB;AACzC,IAAA,IAAA,CAAK,mBAAA,GAAsB,IAAA;AAAA,EAC7B;AAAA,EAEA,SAAS,KAAA,EAAqB;AAC5B,IAAA,IAAI,KAAK,SAAA,EAAW;AAClB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AAEjB,IAAA,IAAI,CAAC,KAAK,KAAA,EAAO;AACf,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GACJ,IAAA,CAAK,mBAAA,IACL,IAAA,CAAK,mBAAA,KAAwB,IAAA,CAAK,kBAAA,GAC9B,EAAE,WAAA,EAAa,IAAA,CAAK,mBAAA,EAAoB,GACxC,MAAA;AAEN,IAAA,MAAM,KAAA,GAAQ,KAAA,GACV,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK;AAAA,MACd,KAAA;AAAA,MACA,GAAI,YAAA,IAAgB,EAAE,IAAA,EAAM,YAAA;AAAa,KAC1C,CAAA,GACD,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,eAAe,EAAE,IAAA,EAAM,YAAA,EAAa,GAAI,MAAS,CAAA;AAExE,IAAA,KAAA,CAAM,KAAA;AAAA,MAAM,CAAA,GAAA,KACV,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,oCAAoC,GAAG;AAAA,KAC3D;AAAA,EACF;AAAA,EAEA,qBAAqB,GAAA,EAAqB;AACxC,IAAA,MAAM,uBAAuB,MAAM;AACjC,MAAA,IAAI,CAAC,IAAI,gBAAA,EAAkB;AACzB,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,IAAI,MAAM,+CAA+C;AAAA,SAC3D;AAAA,MACF,CAAA,MAAA,IAAW,GAAA,CAAI,UAAA,IAAc,GAAA,EAAK;AAChC,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,GAAA,CAAI,UAAU,CAAA,CAAE;AAAA,SAC3D;AAAA,MACF,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,QAAA,EAAS;AAAA,MAChB;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,eAAe,MAAM;AACzB,MAAA,GAAA,CAAI,cAAA,CAAe,UAAU,YAAY,CAAA;AACzC,MAAA,GAAA,CAAI,cAAA,CAAe,SAAS,YAAY,CAAA;AACxC,MAAA,oBAAA,EAAqB;AAAA,IACvB,CAAA;AAEA,IAAA,GAAA,CAAI,IAAA,CAAK,UAAU,YAAY,CAAA;AAC/B,IAAA,GAAA,CAAI,IAAA,CAAK,SAAS,YAAY,CAAA;AAAA,EAChC;AAAA,EAEA,0BAAA,CAA2B,KAAc,aAAA,EAAiC;AACxE,IAAC,IACC,qBACF,CAAA,GAAI,EAAE,OAAA,EAAS,MAAM,aAAA,EAAc;AAAA,EACrC;AAAA,EAEA,OAAO,uBAAA,CACL,QAAA,EACA,GAAA,EACA,MAAA,EACM;AACN,IAAA,MAAM,QAAA,GAAW,GAAA;AAGjB,IAAA,MAAM,OAAA,GAAU,SAAS,qBAAqB,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA;AAAA,IACF;AACA,IAAA,OAAO,SAAS,qBAAqB,CAAA;AAErC,IAAA,MAAM,EAAE,OAAA,EAAS,aAAA,EAAc,GAAI,OAAA;AACnC,IAAA,MAAM,WAA2B,EAAC;AAClC,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAkB;AAClC,MAAA,OAAA,CAAQ,SAAS,KAAK,CAAA;AACtB,MAAA,QAAA,CAAS,OAAA,CAAQ,CAAA,EAAA,KAAM,EAAA,EAAI,CAAA;AAC3B,MAAA,GAAA,CAAI,MAAA,CAAO,cAAA,CAAe,OAAA,EAAS,aAAa,CAAA;AAAA,IAClD,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,EAAS;AACjC,IAAA,MAAM,UAAA,GAAa,CAAC,QAAA,KAA6C;AAC/D,MAAA,QAAA;AAAA,QACE,IAAI,KAAA;AAAA,UACF,CAAA,gDAAA,EAAmD,SAAS,UAAU,CAAA;AAAA;AACxE,OACF;AAAA,IACF,CAAA;AACA,IAAA,MAAM,YAAA,GAAe,CAAC,GAAA,KAAe,QAAA,CAAS,GAAG,CAAA;AACjD,IAAA,MAAM,aAAA,GAAgB,CAAC,GAAA,KACrB,QAAA,CAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC9D,IAAA,MAAM,iBAAiB,MACrB,QAAA;AAAA,MACE,IAAI,MAAM,qDAAqD;AAAA,KACjE;AAEF,IAAA,QAAA,CAAS,IAAA,CAAK,WAAW,SAAS,CAAA;AAClC,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,UAAU,CAAA;AACpC,IAAA,QAAA,CAAS,IAAA,CAAK,SAAS,YAAY,CAAA;AACnC,IAAA,MAAA,CAAO,IAAA,CAAK,SAAS,aAAa,CAAA;AAClC,IAAA,MAAA,CAAO,IAAA,CAAK,SAAS,cAAc,CAAA;AAEnC,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,MAAM,QAAA,CAAS,cAAA,CAAe,SAAA,EAAW,SAAS,CAAA;AAAA,MAClD,MAAM,QAAA,CAAS,cAAA,CAAe,UAAA,EAAY,UAAU,CAAA;AAAA,MACpD,MAAM,QAAA,CAAS,cAAA,CAAe,OAAA,EAAS,YAAY,CAAA;AAAA,MACnD,MAAM,MAAA,CAAO,cAAA,CAAe,OAAA,EAAS,aAAa,CAAA;AAAA,MAClD,MAAM,MAAA,CAAO,cAAA,CAAe,OAAA,EAAS,cAAc;AAAA,KACrD;AAAA,EACF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProxyMiddlewareCache.cjs.js","sources":["../../src/service/ProxyMiddlewareCache.ts"],"sourcesContent":["/*\n * Copyright 2026 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 type { RequestHandler } from 'http-proxy-middleware';\nimport type { ClusterDetails } from '@backstage/plugin-kubernetes-node';\n\nexport const DEFAULT_PROXY_MIDDLEWARE_CACHE_TTL_MS = 60_000;\nexport const DEFAULT_PROXY_MIDDLEWARE_CACHE_MAX_SIZE = 100;\n\n/**\n * @internal\n */\nexport function resolveProxyMiddlewareCacheOptions(options?: {\n ttlMs?: number;\n maxSize?: number;\n}): { ttlMs: number; maxSize: number } {\n const ttlMs =\n options?.ttlMs !== undefined && options.ttlMs > 0\n ? options.ttlMs\n : DEFAULT_PROXY_MIDDLEWARE_CACHE_TTL_MS;\n const maxSize =\n options?.maxSize !== undefined && options.maxSize >= 1\n ? options.maxSize\n : DEFAULT_PROXY_MIDDLEWARE_CACHE_MAX_SIZE;\n\n return { ttlMs, maxSize };\n}\n\ntype CacheEntry = {\n middleware: RequestHandler;\n fingerprint: string;\n createdAtMs: number;\n};\n\n/**\n * Builds a cache fingerprint from cluster details that affect cached proxy middleware.\n *\n * @internal\n */\nexport function buildProxyMiddlewareFingerprint(\n cluster: ClusterDetails,\n): string {\n return `${cluster.url}|${cluster.skipTLSVerify ?? false}|${\n cluster.caData ?? ''\n }|${cluster.caFile ?? ''}`;\n}\n\n/**\n * Bounded TTL cache for per-cluster http-proxy-middleware instances.\n *\n * @internal\n */\nexport class ProxyMiddlewareCache {\n readonly #ttlMs: number;\n readonly #maxSize: number;\n readonly #entries = new Map<string, CacheEntry>();\n readonly #now: () => number;\n\n constructor(options: {\n ttlMs?: number;\n maxSize?: number;\n now?: () => number;\n }) {\n const resolved = resolveProxyMiddlewareCacheOptions(options);\n this.#ttlMs = resolved.ttlMs;\n this.#maxSize = resolved.maxSize;\n this.#now = options.now ?? (() => Date.now());\n }\n\n get(cluster: ClusterDetails): RequestHandler | undefined {\n const entry = this.#entries.get(cluster.name);\n if (!entry) {\n return undefined;\n }\n\n const fingerprint = buildProxyMiddlewareFingerprint(cluster);\n if (\n entry.fingerprint !== fingerprint ||\n this.#now() - entry.createdAtMs > this.#ttlMs\n ) {\n this.#entries.delete(cluster.name);\n return undefined;\n }\n\n return entry.middleware;\n }\n\n set(cluster: ClusterDetails, middleware: RequestHandler): void {\n this.#entries.delete(cluster.name);\n\n while (this.#entries.size >= this.#maxSize) {\n const oldestKey = this.#findOldestKey();\n if (!oldestKey) {\n break;\n }\n this.#entries.delete(oldestKey);\n }\n\n this.#entries.set(cluster.name, {\n middleware,\n fingerprint: buildProxyMiddlewareFingerprint(cluster),\n createdAtMs: this.#now(),\n });\n }\n\n #findOldestKey(): string | undefined {\n let oldestKey: string | undefined;\n let oldestTime = Number.POSITIVE_INFINITY;\n\n for (const [key, entry] of this.#entries) {\n if (entry.createdAtMs < oldestTime) {\n oldestTime = entry.createdAtMs;\n oldestKey = key;\n }\n }\n\n return oldestKey;\n }\n}\n"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ProxyMiddlewareCache.cjs.js","sources":["../../src/service/ProxyMiddlewareCache.ts"],"sourcesContent":["/*\n * Copyright 2026 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 type { RequestHandler } from 'http-proxy-middleware';\nimport type { ClusterDetails } from '@backstage/plugin-kubernetes-node';\n\nexport const DEFAULT_PROXY_MIDDLEWARE_CACHE_TTL_MS = 60_000;\nexport const DEFAULT_PROXY_MIDDLEWARE_CACHE_MAX_SIZE = 100;\n\n/**\n * @internal\n */\nexport function resolveProxyMiddlewareCacheOptions(options?: {\n ttlMs?: number;\n maxSize?: number;\n}): { ttlMs: number; maxSize: number } {\n const ttlMs =\n options?.ttlMs !== undefined && options.ttlMs > 0\n ? options.ttlMs\n : DEFAULT_PROXY_MIDDLEWARE_CACHE_TTL_MS;\n const maxSize =\n options?.maxSize !== undefined && options.maxSize >= 1\n ? options.maxSize\n : DEFAULT_PROXY_MIDDLEWARE_CACHE_MAX_SIZE;\n\n return { ttlMs, maxSize };\n}\n\ntype CacheEntry = {\n middleware: RequestHandler;\n fingerprint: string;\n createdAtMs: number;\n};\n\n/**\n * Builds a cache fingerprint from cluster details that affect cached proxy middleware.\n *\n * @internal\n */\nexport function buildProxyMiddlewareFingerprint(\n cluster: ClusterDetails,\n): string {\n return `${cluster.url}|${cluster.skipTLSVerify ?? false}|${\n cluster.caData ?? ''\n }|${cluster.caFile ?? ''}`;\n}\n\n/**\n * Bounded TTL cache for per-cluster http-proxy-middleware instances.\n *\n * @internal\n */\nexport class ProxyMiddlewareCache {\n readonly #ttlMs: number;\n readonly #maxSize: number;\n readonly #entries = new Map<string, CacheEntry>();\n readonly #now: () => number;\n\n constructor(options: {\n ttlMs?: number;\n maxSize?: number;\n now?: () => number;\n }) {\n const resolved = resolveProxyMiddlewareCacheOptions(options);\n this.#ttlMs = resolved.ttlMs;\n this.#maxSize = resolved.maxSize;\n this.#now = options.now ?? (() => Date.now());\n }\n\n get(cluster: ClusterDetails): RequestHandler | undefined {\n const entry = this.#entries.get(cluster.name);\n if (!entry) {\n return undefined;\n }\n\n const fingerprint = buildProxyMiddlewareFingerprint(cluster);\n if (\n entry.fingerprint !== fingerprint ||\n this.#now() - entry.createdAtMs > this.#ttlMs\n ) {\n this.#entries.delete(cluster.name);\n return undefined;\n }\n\n return entry.middleware;\n }\n\n set(cluster: ClusterDetails, middleware: RequestHandler): void {\n this.#entries.delete(cluster.name);\n\n while (this.#entries.size >= this.#maxSize) {\n const oldestKey = this.#findOldestKey();\n if (!oldestKey) {\n break;\n }\n this.#entries.delete(oldestKey);\n }\n\n this.#entries.set(cluster.name, {\n middleware,\n fingerprint: buildProxyMiddlewareFingerprint(cluster),\n createdAtMs: this.#now(),\n });\n }\n\n #findOldestKey(): string | undefined {\n let oldestKey: string | undefined;\n let oldestTime = Number.POSITIVE_INFINITY;\n\n for (const [key, entry] of this.#entries) {\n if (entry.createdAtMs < oldestTime) {\n oldestTime = entry.createdAtMs;\n oldestKey = key;\n }\n }\n\n return oldestKey;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runPeriodically.cjs.js","sources":["../../src/service/runPeriodically.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\n/**\n * Runs a function repeatedly, with a fixed wait between invocations.\n *\n * Supports async functions, and silently ignores exceptions and rejections.\n *\n * @param fn - The function to run. May return a Promise.\n * @param delayMs - The delay between a completed function invocation and the\n * next.\n * @returns A function that, when called, stops the invocation loop.\n */\nexport function runPeriodically(fn: () => any, delayMs: number): () => void {\n let cancel: () => void;\n let cancelled = false;\n const cancellationPromise = new Promise<void>(resolve => {\n cancel = () => {\n resolve();\n cancelled = true;\n };\n });\n\n const startRefresh = async () => {\n while (!cancelled) {\n try {\n await fn();\n } catch {\n // ignore intentionally\n }\n\n await Promise.race([\n new Promise(resolve => setTimeout(resolve, delayMs)),\n cancellationPromise,\n ]);\n }\n };\n startRefresh();\n\n return cancel!;\n}\n"],"names":[],"mappings":";;AA0BO,SAAS,eAAA,CAAgB,IAAe,OAAA,EAA6B;AAC1E,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,SAAA,GAAY,KAAA;AAChB,EAAA,MAAM,mBAAA,GAAsB,IAAI,OAAA,CAAc,CAAA,OAAA,KAAW;AACvD,IAAA,MAAA,GAAS,MAAM;AACb,MAAA,OAAA,EAAQ;AACR,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,eAAe,YAAY;AAC/B,IAAA,OAAO,CAAC,SAAA,EAAW;AACjB,MAAA,IAAI;AACF,QAAA,MAAM,EAAA,EAAG;AAAA,MACX,CAAA,CAAA,MAAQ;AAAA,MAER;AAEA,MAAA,MAAM,QAAQ,IAAA,CAAK;AAAA,QACjB,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,QACnD;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACA,EAAA,YAAA,EAAa;AAEb,EAAA,OAAO,MAAA;AACT
|
|
1
|
+
{"version":3,"file":"runPeriodically.cjs.js","sources":["../../src/service/runPeriodically.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\n/**\n * Runs a function repeatedly, with a fixed wait between invocations.\n *\n * Supports async functions, and silently ignores exceptions and rejections.\n *\n * @param fn - The function to run. May return a Promise.\n * @param delayMs - The delay between a completed function invocation and the\n * next.\n * @returns A function that, when called, stops the invocation loop.\n */\nexport function runPeriodically(fn: () => any, delayMs: number): () => void {\n let cancel: () => void;\n let cancelled = false;\n const cancellationPromise = new Promise<void>(resolve => {\n cancel = () => {\n resolve();\n cancelled = true;\n };\n });\n\n const startRefresh = async () => {\n while (!cancelled) {\n try {\n await fn();\n } catch {\n // ignore intentionally\n }\n\n await Promise.race([\n new Promise(resolve => setTimeout(resolve, delayMs)),\n cancellationPromise,\n ]);\n }\n };\n startRefresh();\n\n return cancel!;\n}\n"],"names":[],"mappings":";;AA0BO,SAAS,eAAA,CAAgB,IAAe,OAAA,EAA6B;AAC1E,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,SAAA,GAAY,KAAA;AAChB,EAAA,MAAM,mBAAA,GAAsB,IAAI,OAAA,CAAc,CAAA,OAAA,KAAW;AACvD,IAAA,MAAA,GAAS,MAAM;AACb,MAAA,OAAA,EAAQ;AACR,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,eAAe,YAAY;AAC/B,IAAA,OAAO,CAAC,SAAA,EAAW;AACjB,MAAA,IAAI;AACF,QAAA,MAAM,EAAA,EAAG;AAAA,MACX,CAAA,CAAA,MAAQ;AAAA,MAER;AAEA,MAAA,MAAM,QAAQ,IAAA,CAAK;AAAA,QACjB,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,QACnD;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF,CAAA;AACA,EAAA,YAAA,EAAa;AAEb,EAAA,OAAO,MAAA;AACT;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CatalogRelationServiceLocator.cjs.js","sources":["../../src/service-locator/CatalogRelationServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\n\n// This locator assumes that service is located on the clusters it depends on\n// Therefore it will return the clusters based on the relations it has or none otherwise\nexport class CatalogRelationServiceLocator implements KubernetesServiceLocator {\n private readonly clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n if (\n entity.relations &&\n entity.relations.some(\n r => r.type === 'dependsOn' && r.targetRef.includes('resource:'),\n )\n ) {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => {\n return {\n clusters: clusters.filter(c =>\n this.doesEntityDependOnCluster(entity, c),\n ),\n };\n });\n }\n return Promise.resolve({ clusters: [] });\n }\n\n protected doesEntityDependOnCluster(\n entity: Entity,\n cluster: ClusterDetails,\n ): boolean {\n return entity.relations!.some(\n rel =>\n rel.type === 'dependsOn' &&\n rel.targetRef ===\n `resource:${entity.metadata.namespace ?? 'default'}/${cluster.name}`,\n );\n }\n}\n"],"names":[],"mappings":";;AAyBO,MAAM,6BAAA,CAAkE;AAAA,EAC5D,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,QACA,cAAA,EACyC;AACzC,IAAA,IACE,MAAA,CAAO,SAAA,IACP,MAAA,CAAO,SAAA,CAAU,IAAA;AAAA,MACf,OAAK,CAAA,CAAE,IAAA,KAAS,eAAe,CAAA,CAAE,SAAA,CAAU,SAAS,WAAW;AAAA,KACjE,EACA;AACA,MAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,eAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,KAAY;AAChB,QAAA,OAAO;AAAA,UACL,UAAU,QAAA,CAAS,MAAA;AAAA,YAAO,CAAA,CAAA,KACxB,IAAA,CAAK,yBAAA,CAA0B,MAAA,EAAQ,CAAC;AAAA;AAC1C,SACF;AAAA,MACF,CAAC,CAAA;AAAA,IACL;AACA,IAAA,OAAO,QAAQ,OAAA,CAAQ,EAAE,QAAA,EAAU,IAAI,CAAA;AAAA,EACzC;AAAA,EAEU,yBAAA,CACR,QACA,OAAA,EACS;AACT,IAAA,OAAO,OAAO,SAAA,CAAW,IAAA;AAAA,MACvB,CAAA,GAAA,KACE,GAAA,CAAI,IAAA,KAAS,WAAA,IACb,GAAA,CAAI,SAAA,KACF,CAAA,SAAA,EAAY,MAAA,CAAO,QAAA,CAAS,SAAA,IAAa,SAAS,CAAA,CAAA,EAAI,QAAQ,IAAI,CAAA;AAAA,KACxE;AAAA,EACF;AACF
|
|
1
|
+
{"version":3,"file":"CatalogRelationServiceLocator.cjs.js","sources":["../../src/service-locator/CatalogRelationServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\n\n// This locator assumes that service is located on the clusters it depends on\n// Therefore it will return the clusters based on the relations it has or none otherwise\nexport class CatalogRelationServiceLocator implements KubernetesServiceLocator {\n private readonly clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n if (\n entity.relations &&\n entity.relations.some(\n r => r.type === 'dependsOn' && r.targetRef.includes('resource:'),\n )\n ) {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => {\n return {\n clusters: clusters.filter(c =>\n this.doesEntityDependOnCluster(entity, c),\n ),\n };\n });\n }\n return Promise.resolve({ clusters: [] });\n }\n\n protected doesEntityDependOnCluster(\n entity: Entity,\n cluster: ClusterDetails,\n ): boolean {\n return entity.relations!.some(\n rel =>\n rel.type === 'dependsOn' &&\n rel.targetRef ===\n `resource:${entity.metadata.namespace ?? 'default'}/${cluster.name}`,\n );\n }\n}\n"],"names":[],"mappings":";;AAyBO,MAAM,6BAAA,CAAkE;AAAA,EAC5D,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,QACA,cAAA,EACyC;AACzC,IAAA,IACE,MAAA,CAAO,SAAA,IACP,MAAA,CAAO,SAAA,CAAU,IAAA;AAAA,MACf,OAAK,CAAA,CAAE,IAAA,KAAS,eAAe,CAAA,CAAE,SAAA,CAAU,SAAS,WAAW;AAAA,KACjE,EACA;AACA,MAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,eAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,KAAY;AAChB,QAAA,OAAO;AAAA,UACL,UAAU,QAAA,CAAS,MAAA;AAAA,YAAO,CAAA,CAAA,KACxB,IAAA,CAAK,yBAAA,CAA0B,MAAA,EAAQ,CAAC;AAAA;AAC1C,SACF;AAAA,MACF,CAAC,CAAA;AAAA,IACL;AACA,IAAA,OAAO,QAAQ,OAAA,CAAQ,EAAE,QAAA,EAAU,IAAI,CAAA;AAAA,EACzC;AAAA,EAEU,yBAAA,CACR,QACA,OAAA,EACS;AACT,IAAA,OAAO,OAAO,SAAA,CAAW,IAAA;AAAA,MACvB,CAAA,GAAA,KACE,GAAA,CAAI,IAAA,KAAS,WAAA,IACb,GAAA,CAAI,SAAA,KACF,CAAA,SAAA,EAAY,MAAA,CAAO,QAAA,CAAS,SAAA,IAAa,SAAS,CAAA,CAAA,EAAI,QAAQ,IAAI,CAAA;AAAA,KACxE;AAAA,EACF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiTenantServiceLocator.cjs.js","sources":["../../src/service-locator/MultiTenantServiceLocator.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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\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 clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n _entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => ({ clusters }));\n }\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,yBAAA,CAA8D;AAAA,EACxD,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,SACA,cAAA,EACyC;AACzC,IAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,cAAA,CAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,MAAa,EAAE,UAAS,CAAE,CAAA;AAAA,EACpC;AACF
|
|
1
|
+
{"version":3,"file":"MultiTenantServiceLocator.cjs.js","sources":["../../src/service-locator/MultiTenantServiceLocator.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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\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 clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n _entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => ({ clusters }));\n }\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,yBAAA,CAA8D;AAAA,EACxD,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,SACA,cAAA,EACyC;AACzC,IAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,cAAA,CAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,MAAa,EAAE,UAAS,CAAE,CAAA;AAAA,EACpC;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SingleTenantServiceLocator.cjs.js","sources":["../../src/service-locator/SingleTenantServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\n\n// This locator assumes that service is located on one cluster\n// Therefore it will always return specified cluster provided in backstage.io/kubernetes-cluster annotation\n// If backstage.io/kubernetes-cluster annotation not provided will always return all cluster provided\nexport class SingleTenantServiceLocator implements KubernetesServiceLocator {\n private readonly clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n _entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => {\n if (\n _entity.metadata?.annotations?.['backstage.io/kubernetes-cluster']\n ) {\n return {\n clusters: clusters.filter(\n c =>\n c.name ===\n _entity.metadata?.annotations?.[\n 'backstage.io/kubernetes-cluster'\n ],\n ),\n };\n }\n return { clusters };\n });\n }\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,0BAAA,CAA+D;AAAA,EACzD,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,SACA,cAAA,EACyC;AACzC,IAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,eAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,KAAY;AAChB,MAAA,IACE,OAAA,CAAQ,QAAA,EAAU,WAAA,GAAc,iCAAiC,CAAA,EACjE;AACA,QAAA,OAAO;AAAA,UACL,UAAU,QAAA,CAAS,MAAA;AAAA,YACjB,OACE,CAAA,CAAE,IAAA,KACF,OAAA,CAAQ,QAAA,EAAU,cAChB,iCACF;AAAA;AACJ,SACF;AAAA,MACF;AACA,MAAA,OAAO,EAAE,QAAA,EAAS;AAAA,IACpB,CAAC,CAAA;AAAA,EACL;AACF
|
|
1
|
+
{"version":3,"file":"SingleTenantServiceLocator.cjs.js","sources":["../../src/service-locator/SingleTenantServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { Entity } from '@backstage/catalog-model';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n KubernetesServiceLocator,\n ServiceLocatorRequestContext,\n} from '@backstage/plugin-kubernetes-node';\n\n// This locator assumes that service is located on one cluster\n// Therefore it will always return specified cluster provided in backstage.io/kubernetes-cluster annotation\n// If backstage.io/kubernetes-cluster annotation not provided will always return all cluster provided\nexport class SingleTenantServiceLocator implements KubernetesServiceLocator {\n private readonly clusterSupplier: KubernetesClustersSupplier;\n\n constructor(clusterSupplier: KubernetesClustersSupplier) {\n this.clusterSupplier = clusterSupplier;\n }\n\n // As this implementation always returns all clusters serviceId is ignored here\n getClustersByEntity(\n _entity: Entity,\n requestContext: ServiceLocatorRequestContext,\n ): Promise<{ clusters: ClusterDetails[] }> {\n return this.clusterSupplier\n .getClusters({ credentials: requestContext.credentials })\n .then(clusters => {\n if (\n _entity.metadata?.annotations?.['backstage.io/kubernetes-cluster']\n ) {\n return {\n clusters: clusters.filter(\n c =>\n c.name ===\n _entity.metadata?.annotations?.[\n 'backstage.io/kubernetes-cluster'\n ],\n ),\n };\n }\n return { clusters };\n });\n }\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,0BAAA,CAA+D;AAAA,EACzD,eAAA;AAAA,EAEjB,YAAY,eAAA,EAA6C;AACvD,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA;AAAA,EAGA,mBAAA,CACE,SACA,cAAA,EACyC;AACzC,IAAA,OAAO,IAAA,CAAK,eAAA,CACT,WAAA,CAAY,EAAE,WAAA,EAAa,eAAe,WAAA,EAAa,CAAA,CACvD,IAAA,CAAK,CAAA,QAAA,KAAY;AAChB,MAAA,IACE,OAAA,CAAQ,QAAA,EAAU,WAAA,GAAc,iCAAiC,CAAA,EACjE;AACA,QAAA,OAAO;AAAA,UACL,UAAU,QAAA,CAAS,MAAA;AAAA,YACjB,OACE,CAAA,CAAE,IAAA,KACF,OAAA,CAAQ,QAAA,EAAU,cAChB,iCACF;AAAA;AACJ,SACF;AAAA,MACF;AACA,MAAA,OAAO,EAAE,QAAA,EAAS;AAAA,IACpB,CAAC,CAAA;AAAA,EACL;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildDefaultServiceLocator.cjs.js","sources":["../../src/service-locator/buildDefaultServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { ServiceLocatorMethod } from '../types';\nimport { KubernetesClustersSupplier } from '@backstage/plugin-kubernetes-node';\nimport { MultiTenantServiceLocator } from './MultiTenantServiceLocator';\nimport { SingleTenantServiceLocator } from './SingleTenantServiceLocator';\nimport { CatalogRelationServiceLocator } from './CatalogRelationServiceLocator';\n\nexport const buildDefaultServiceLocator = ({\n config,\n clusterSupplier,\n}: {\n config: RootConfigService;\n clusterSupplier: KubernetesClustersSupplier;\n}) => {\n const method = config.getString(\n 'kubernetes.serviceLocatorMethod.type',\n ) as ServiceLocatorMethod;\n\n switch (method) {\n case 'multiTenant':\n return new MultiTenantServiceLocator(clusterSupplier);\n case 'singleTenant':\n return new SingleTenantServiceLocator(clusterSupplier);\n case 'catalogRelation':\n return new CatalogRelationServiceLocator(clusterSupplier);\n case 'http':\n throw new Error('not implemented');\n default:\n throw new Error(\n `Unsupported kubernetes.serviceLocatorMethod \"${method}\"`,\n );\n }\n};\n"],"names":["MultiTenantServiceLocator","SingleTenantServiceLocator","CatalogRelationServiceLocator"],"mappings":";;;;;;AAsBO,MAAM,6BAA6B,CAAC;AAAA,EACzC,MAAA;AAAA,EACA;AACF,CAAA,KAGM;AACJ,EAAA,MAAM,SAAS,MAAA,CAAO,SAAA;AAAA,IACpB;AAAA,GACF;AAEA,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,aAAA;AACH,MAAA,OAAO,IAAIA,oDAA0B,eAAe,CAAA;AAAA,IACtD,KAAK,cAAA;AACH,MAAA,OAAO,IAAIC,sDAA2B,eAAe,CAAA;AAAA,IACvD,KAAK,iBAAA;AACH,MAAA,OAAO,IAAIC,4DAA8B,eAAe,CAAA;AAAA,IAC1D,KAAK,MAAA;AACH,MAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,IACnC;AACE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,MAAM,CAAA,CAAA;AAAA,OACxD;AAAA;AAEN
|
|
1
|
+
{"version":3,"file":"buildDefaultServiceLocator.cjs.js","sources":["../../src/service-locator/buildDefaultServiceLocator.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { RootConfigService } from '@backstage/backend-plugin-api';\nimport { ServiceLocatorMethod } from '../types';\nimport { KubernetesClustersSupplier } from '@backstage/plugin-kubernetes-node';\nimport { MultiTenantServiceLocator } from './MultiTenantServiceLocator';\nimport { SingleTenantServiceLocator } from './SingleTenantServiceLocator';\nimport { CatalogRelationServiceLocator } from './CatalogRelationServiceLocator';\n\nexport const buildDefaultServiceLocator = ({\n config,\n clusterSupplier,\n}: {\n config: RootConfigService;\n clusterSupplier: KubernetesClustersSupplier;\n}) => {\n const method = config.getString(\n 'kubernetes.serviceLocatorMethod.type',\n ) as ServiceLocatorMethod;\n\n switch (method) {\n case 'multiTenant':\n return new MultiTenantServiceLocator(clusterSupplier);\n case 'singleTenant':\n return new SingleTenantServiceLocator(clusterSupplier);\n case 'catalogRelation':\n return new CatalogRelationServiceLocator(clusterSupplier);\n case 'http':\n throw new Error('not implemented');\n default:\n throw new Error(\n `Unsupported kubernetes.serviceLocatorMethod \"${method}\"`,\n );\n }\n};\n"],"names":["MultiTenantServiceLocator","SingleTenantServiceLocator","CatalogRelationServiceLocator"],"mappings":";;;;;;AAsBO,MAAM,6BAA6B,CAAC;AAAA,EACzC,MAAA;AAAA,EACA;AACF,CAAA,KAGM;AACJ,EAAA,MAAM,SAAS,MAAA,CAAO,SAAA;AAAA,IACpB;AAAA,GACF;AAEA,EAAA,QAAQ,MAAA;AAAQ,IACd,KAAK,aAAA;AACH,MAAA,OAAO,IAAIA,oDAA0B,eAAe,CAAA;AAAA,IACtD,KAAK,cAAA;AACH,MAAA,OAAO,IAAIC,sDAA2B,eAAe,CAAA;AAAA,IACvD,KAAK,iBAAA;AACH,MAAA,OAAO,IAAIC,4DAA8B,eAAe,CAAA;AAAA,IAC1D,KAAK,MAAA;AACH,MAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,IACnC;AACE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,MAAM,CAAA,CAAA;AAAA,OACxD;AAAA;AAEN;;"}
|