Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2025-04-22 14:48:46 +02:00
parent 216c6b2763
commit 835764ac43
37 changed files with 99 additions and 81 deletions
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createMockDirectory } from '@backstage/backend-test-utils';
import { ServiceAccountStrategy } from './ServiceAccountStrategy';
@@ -22,7 +23,7 @@ const mockDir = createMockDirectory({
},
});
jest.mock('@kubernetes/client-node', () => ({
const mockClient = {
KubeConfig: class {
#loaded = false;
loadFromCluster() {
@@ -43,12 +44,13 @@ jest.mock('@kubernetes/client-node', () => ({
};
}
},
}));
};
describe('ServiceAccountStrategy', () => {
describe('#getCredential', () => {
it('reads bearer token from config', async () => {
const strategy = new ServiceAccountStrategy();
(strategy as any).injectedKubernetesClient = mockClient;
const credential = await strategy.getCredential({
name: '',
@@ -65,6 +67,7 @@ describe('ServiceAccountStrategy', () => {
describe('when serviceAccountToken is absent from config', () => {
it('reads in-cluster token', async () => {
const strategy = new ServiceAccountStrategy();
(strategy as any).injectedKubernetesClient = mockClient;
const credential = await strategy.getCredential({
name: '',
@@ -20,7 +20,7 @@ import {
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
import { KubeConfig, User } from '@kubernetes/client-node';
import type { User } from '@kubernetes/client-node';
import fs from 'fs-extra';
/**
@@ -28,9 +28,16 @@ import fs from 'fs-extra';
* @public
*/
export class ServiceAccountStrategy implements AuthenticationStrategy {
// Only used in tests
private injectedKubernetesClient?: typeof import('@kubernetes/client-node');
public async getCredential(
clusterDetails: ClusterDetails,
): Promise<KubernetesCredential> {
const { KubeConfig } =
this.injectedKubernetesClient ??
(await import('@kubernetes/client-node'));
const token = clusterDetails.authMetadata.serviceAccountToken;
if (token) {
return { type: 'bearer token', token };
@@ -33,7 +33,7 @@ import {
PodFetchResponse,
PodStatusFetchResponse,
} from '@backstage/plugin-kubernetes-common';
import {
import type {
ContainerStatus,
CurrentResourceUsage,
PodStatus,
@@ -14,14 +14,7 @@
* limitations under the License.
*/
import {
bufferFromFileOrString,
Cluster,
CoreV1Api,
KubeConfig,
Metrics,
topPods,
} from '@kubernetes/client-node';
import type { Cluster, CoreV1Api, Metrics } from '@kubernetes/client-node';
import lodash, { Dictionary } from 'lodash';
import {
FetchResponseWrapper,
@@ -126,12 +119,14 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
}
fetchPodMetricsByNamespaces(
async fetchPodMetricsByNamespaces(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
namespaces: Set<string>,
labelSelector?: string,
): Promise<FetchResponseWrapper> {
const { topPods } = await import('@kubernetes/client-node');
const fetchResults = Array.from(namespaces).map(async ns => {
const [podMetrics, podList] = await Promise.all([
this.fetchResource(
@@ -193,7 +188,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
};
}
private fetchResource(
private async fetchResource(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
group: string,
@@ -217,9 +212,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];
if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
[url, requestInit] = this.fetchArgsInCluster(credential);
[url, requestInit] = await this.fetchArgsInCluster(credential);
} else if (!this.isCredentialMissing(authProvider, credential)) {
[url, requestInit] = this.fetchArgs(clusterDetails, credential);
[url, requestInit] = await this.fetchArgs(clusterDetails, credential);
} else {
return Promise.reject(
new Error(
@@ -261,10 +256,12 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
);
}
private fetchArgs(
private async fetchArgs(
clusterDetails: ClusterDetails,
credential: KubernetesCredential,
): [URL, RequestInit] {
): Promise<[URL, fetch.RequestInit]> {
const { bufferFromFileOrString } = await import('@kubernetes/client-node');
const requestInit: RequestInit = {
method: 'GET',
headers: {
@@ -293,9 +290,12 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
}
return [url, requestInit];
}
private fetchArgsInCluster(
private async fetchArgsInCluster(
credential: KubernetesCredential,
): [URL, RequestInit] {
): Promise<[URL, fetch.RequestInit]> {
const { KubeConfig } = await import('@kubernetes/client-node');
const requestInit: RequestInit = {
method: 'GET',
headers: {
@@ -27,11 +27,7 @@ import {
KubernetesRequestAuth,
} from '@backstage/plugin-kubernetes-common';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import {
bufferFromFileOrString,
Cluster,
KubeConfig,
} from '@kubernetes/client-node';
import type { Cluster } from '@kubernetes/client-node';
import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware';
import fs from 'fs-extra';
@@ -176,6 +172,10 @@ export class KubernetesProxy {
const cluster = await this.getClusterForRequest(req);
const url = new URL(cluster.url);
const { bufferFromFileOrString } = await import(
'@kubernetes/client-node'
);
const target: any = {
protocol: url.protocol,
host: url.hostname,
@@ -234,6 +234,8 @@ export class KubernetesProxy {
}
private async getClusterForRequest(req: Request): Promise<ClusterDetails> {
const { KubeConfig } = await import('@kubernetes/client-node');
const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];
const clusters = await this.clusterSupplier.getClusters({
credentials: await this.httpAuth.credentials(req),
@@ -17,7 +17,7 @@ import { useApiResources } from './useApiResources';
import { useCallback, useEffect } from 'react';
import { useEntity } from '@backstage/plugin-catalog-react';
import { Table, TableColumn } from '@backstage/core-components';
import { IAPIGroup } from '@kubernetes-models/apimachinery/apis/meta/v1';
import type { IAPIGroup } from '@kubernetes-models/apimachinery/apis/meta/v1';
import { useKubernetesClusterError } from '../KubernetesClusterErrorContext/KubernetesClusterErrorContext';
import { makeStyles } from '@material-ui/core/styles';
@@ -17,7 +17,7 @@ import useAsync from 'react-use/esm/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { kubernetesApiRef } from '@backstage/plugin-kubernetes-react';
import { IAPIGroupList } from '@kubernetes-models/apimachinery/apis/meta/v1';
import type { IAPIGroupList } from '@kubernetes-models/apimachinery/apis/meta/v1';
/**
* Arguments for useApiResources
+15 -15
View File
@@ -9,21 +9,21 @@ import { FetchResponse as FetchResponse_2 } from '@backstage/plugin-kubernetes-c
import type { JsonObject } from '@backstage/types';
import type { JsonValue } from '@backstage/types';
import { ObjectsByEntityResponse as ObjectsByEntityResponse_2 } from '@backstage/plugin-kubernetes-common';
import { PodStatus } from '@kubernetes/client-node';
import { V1ConfigMap } from '@kubernetes/client-node';
import { V1CronJob } from '@kubernetes/client-node';
import { V1DaemonSet } from '@kubernetes/client-node';
import { V1Deployment } from '@kubernetes/client-node';
import { V1Ingress } from '@kubernetes/client-node';
import { V1Job } from '@kubernetes/client-node';
import { V1LimitRange } from '@kubernetes/client-node';
import { V1Pod } from '@kubernetes/client-node';
import { V1ReplicaSet } from '@kubernetes/client-node';
import { V1ResourceQuota } from '@kubernetes/client-node';
import { V1Secret } from '@kubernetes/client-node';
import { V1Service } from '@kubernetes/client-node';
import { V1StatefulSet } from '@kubernetes/client-node';
import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import type { PodStatus } from '@kubernetes/client-node';
import type { V1ConfigMap } from '@kubernetes/client-node';
import type { V1CronJob } from '@kubernetes/client-node';
import type { V1DaemonSet } from '@kubernetes/client-node';
import type { V1Deployment } from '@kubernetes/client-node';
import type { V1Ingress } from '@kubernetes/client-node';
import type { V1Job } from '@kubernetes/client-node';
import type { V1LimitRange } from '@kubernetes/client-node';
import type { V1Pod } from '@kubernetes/client-node';
import type { V1ReplicaSet } from '@kubernetes/client-node';
import type { V1ResourceQuota } from '@kubernetes/client-node';
import type { V1Secret } from '@kubernetes/client-node';
import type { V1Service } from '@kubernetes/client-node';
import type { V1StatefulSet } from '@kubernetes/client-node';
import type { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
// @public
export const ANNOTATION_KUBERNETES_API_SERVER = 'kubernetes.io/api-server';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {
import type {
V1Pod,
V1Deployment,
V2HorizontalPodAutoscaler,
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import type { JsonObject, JsonValue } from '@backstage/types';
import {
import type {
PodStatus,
V1ConfigMap,
V1CronJob,
@@ -38,6 +38,8 @@ import { setupServer } from 'msw/node';
import { loggerToWinstonLogger } from '@backstage/backend-common';
import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter';
jest.setTimeout(60_000);
describe('Pinniped - tokenCredentialRequest', () => {
let app: ExtendedHttpServer;
const logger = loggerToWinstonLogger(mockServices.logger.mock());
@@ -16,7 +16,6 @@
import { ClusterDetails } from '@backstage/plugin-kubernetes-node';
import * as https from 'https';
import { bufferFromFileOrString } from '@kubernetes/client-node';
import fetch, { RequestInit } from 'node-fetch';
import { Logger } from 'winston';
@@ -75,7 +74,7 @@ export class PinnipedHelper {
url.pathname = `/apis/${apiGroup}/tokencredentialrequests`;
const requestInit: RequestInit = this.buildRequestForPinniped(
const requestInit = await this.buildRequestForPinniped(
url,
clusterDetails,
pinnipedParams,
@@ -107,11 +106,13 @@ export class PinnipedHelper {
return Promise.reject(data.status.message);
}
private buildRequestForPinniped(
private async buildRequestForPinniped(
url: URL,
clusterDetails: ClusterDetails,
pinnipedParams: PinnipedParameters,
): RequestInit {
): Promise<fetch.RequestInit> {
const { bufferFromFileOrString } = await import('@kubernetes/client-node');
const body = {
apiVersion:
pinnipedParams.tokenCredentialRequest?.apiGroup ??
+7 -7
View File
@@ -22,8 +22,8 @@ import { FetchApi } from '@backstage/core-plugin-api';
import { GroupedResponses } from '@backstage/plugin-kubernetes-common';
import { IContainer } from 'kubernetes-models/v1';
import { IContainerStatus } from 'kubernetes-models/v1';
import { IIoK8sApimachineryPkgApisMetaV1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import type { IIoK8sApimachineryPkgApisMetaV1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import type { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import { JsonObject } from '@backstage/types';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
@@ -34,11 +34,11 @@ import { Pod } from 'kubernetes-models/v1';
import { Pod as Pod_2 } from 'kubernetes-models/v1/Pod';
import { ProfileInfoApi } from '@backstage/core-plugin-api';
import { ReactNode } from 'react';
import { TypeMeta } from '@kubernetes-models/base';
import { V1Job } from '@kubernetes/client-node';
import { V1ObjectMeta } from '@kubernetes/client-node';
import { V1Pod } from '@kubernetes/client-node';
import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import type { TypeMeta } from '@kubernetes-models/base';
import type { V1Job } from '@kubernetes/client-node';
import type { V1ObjectMeta } from '@kubernetes/client-node';
import type { V1Pod } from '@kubernetes/client-node';
import type { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import { WorkloadsByEntityRequest } from '@backstage/plugin-kubernetes-common';
// @public (undocumented)
@@ -20,7 +20,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1CronJob, V1Job } from '@kubernetes/client-node';
import type { V1CronJob, V1Job } from '@kubernetes/client-node';
import { JobsAccordions } from '../JobsAccordions';
import { CronJobDrawer } from './CronJobsDrawer';
import { getOwnedResources } from '../../utils/owner';
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { V1CronJob } from '@kubernetes/client-node';
import type { V1CronJob } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
@@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Pod, V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import type { V1Pod, V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import { PodsTable } from '../../Pods';
import { HorizontalPodAutoscalerDrawer } from '../../HorizontalPodAutoscalers';
import { RolloutDrawer } from './RolloutDrawer';
@@ -20,7 +20,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Pod, V1DaemonSet } from '@kubernetes/client-node';
import type { V1Pod, V1DaemonSet } from '@kubernetes/client-node';
import { PodsTable } from '../Pods';
import { DaemonSetDrawer } from './DaemonSetsDrawer';
import { getOwnedResources } from '../../utils/owner';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { V1DaemonSet } from '@kubernetes/client-node';
import type { V1DaemonSet } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import { V1Deployment } from '@kubernetes/client-node';
import type { V1Deployment } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import { renderCondition } from '../../utils/pod';
import Typography from '@material-ui/core/Typography';
@@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import {
import type {
V1Deployment,
V1Pod,
V2HorizontalPodAutoscaler,
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import type { V2HorizontalPodAutoscaler } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
/** @public */
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { V1Ingress } from '@kubernetes/client-node';
import type { V1Ingress } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
@@ -20,7 +20,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Ingress } from '@kubernetes/client-node';
import type { V1Ingress } from '@kubernetes/client-node';
import { IngressDrawer } from './IngressDrawer';
import { GroupedResponsesContext } from '../../hooks';
import { StructuredMetadataTable } from '@backstage/core-components';
@@ -19,11 +19,14 @@ import { JobsAccordions } from './JobsAccordions';
import * as oneCronJobsFixture from '../../__fixtures__/1-cronjobs.json';
import { renderInTestApp } from '@backstage/test-utils';
import { kubernetesProviders } from '../../hooks/test-utils';
import { V1Job } from '@kubernetes/client-node';
import { ObjectSerializer } from '@kubernetes/client-node/dist/gen/models/ObjectSerializer';
import type { V1Job } from '@kubernetes/client-node';
describe('JobsAccordions', () => {
it('should render 2 jobs', async () => {
const { ObjectSerializer } = await import(
'@kubernetes/client-node/dist/gen/models/ObjectSerializer'
);
const wrapper = kubernetesProviders(oneCronJobsFixture, new Set<string>());
const jobs: V1Job[] = oneCronJobsFixture.jobs.map(
@@ -19,7 +19,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Job, V1Pod } from '@kubernetes/client-node';
import type { V1Job, V1Pod } from '@kubernetes/client-node';
import { PodsTable } from '../Pods';
import { JobDrawer } from './JobsDrawer';
import { getOwnedResources } from '../../utils/owner';
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { V1Job } from '@kubernetes/client-node';
import type { V1Job } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
@@ -15,7 +15,7 @@
*/
import { ReactNode, ChangeEvent, useState } from 'react';
import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import type { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import Drawer from '@material-ui/core/Drawer';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
@@ -25,7 +25,7 @@ import Grid from '@material-ui/core/Grid';
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
import Close from '@material-ui/icons/Close';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import { V1ObjectMeta } from '@kubernetes/client-node';
import type { V1ObjectMeta } from '@kubernetes/client-node';
import { withStyles } from '@material-ui/core/styles';
import {
LinkButton as BackstageButton,
+1 -1
View File
@@ -27,7 +27,7 @@ import { Table, TableColumn } from '@backstage/core-components';
import { ClusterContext } from '../../hooks/Cluster';
import { useMatchingErrors } from '../../hooks/useMatchingErrors';
import { Pod } from 'kubernetes-models/v1/Pod';
import { V1Pod } from '@kubernetes/client-node';
import type { V1Pod } from '@kubernetes/client-node';
import { usePodMetrics } from '../../hooks/usePodMetrics';
import Typography from '@material-ui/core/Typography';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { V1Service } from '@kubernetes/client-node';
import type { V1Service } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
@@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Service } from '@kubernetes/client-node';
import type { V1Service } from '@kubernetes/client-node';
import { ServiceDrawer } from './ServiceDrawer';
import { GroupedResponsesContext } from '../../hooks';
import { StructuredMetadataTable } from '@backstage/core-components';
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import { V1StatefulSet } from '@kubernetes/client-node';
import type { V1StatefulSet } from '@kubernetes/client-node';
import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer';
import { renderCondition } from '../../utils/pod';
import Typography from '@material-ui/core/Typography';
@@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import {
import type {
V1Pod,
V2HorizontalPodAutoscaler,
V1StatefulSet,
@@ -18,8 +18,8 @@ import {
DetectedError,
ResourceRef,
} from '@backstage/plugin-kubernetes-common';
import { TypeMeta } from '@kubernetes-models/base';
import { IIoK8sApimachineryPkgApisMetaV1ObjectMeta as V1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import type { TypeMeta } from '@kubernetes-models/base';
import type { IIoK8sApimachineryPkgApisMetaV1ObjectMeta as V1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
/**
* Context for detected errors
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createContext, useContext } from 'react';
import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import type { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta';
import { ClientPodStatus } from '@backstage/plugin-kubernetes-common';
/**
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {
import type {
V1ObjectMeta,
V2HorizontalPodAutoscaler,
V1Pod,
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {
import type {
V1Pod,
V1PodCondition,
V1DeploymentCondition,