Skip to content

Commit

Permalink
chore(dep): update k8s-client for ocm, k8s-scaffolder, topology, tekt…
Browse files Browse the repository at this point in the history
…on plugins (backstage#2843)

* chore(dep): update k8s-client for ocm and k8s scaffolder plugins

Signed-off-by: Oleksandr Andriienko <[email protected]>

* chore(dep): fix unit tests

Signed-off-by: Oleksandr Andriienko <[email protected]>

* chore(dep): update yarn.lock files

Signed-off-by: Oleksandr Andriienko <[email protected]>

* fix(rbac): update topology and tekton plugins

Signed-off-by: Oleksandr Andriienko <[email protected]>

* chore(dep): work on error handling

Signed-off-by: Oleksandr Andriienko <[email protected]>

* chore(dep): remove deps that break application startup

Signed-off-by: Oleksandr Andriienko <[email protected]>

* chore(dep): update react libs instead of removing

Signed-off-by: Oleksandr Andriienko <[email protected]>

---------

Signed-off-by: Oleksandr Andriienko <[email protected]>
Signed-off-by: Harmen Brinkman <[email protected]>
  • Loading branch information
AndrienkoAleksandr authored and harmenbrinkmanTRIMM committed Feb 19, 2025
1 parent 2bdce20 commit bd54309
Show file tree
Hide file tree
Showing 18 changed files with 380 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
import { rest } from 'msw';

const LOCAL_ADDR = 'http://localhost:5000';
const LOCAL_ADDR = 'https://example.com';

export const handlers = [
rest.get(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v1
clusters:
- cluster:
certificate-authority-data: Q0FEQVRBMg==
server: http://example.com
server: https://example.com
insecure-skip-tls-verify: true
name: default-cluster

Expand Down
2 changes: 1 addition & 1 deletion workspaces/ocm/plugins/ocm-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@backstage/plugin-catalog-node": "^1.15.1",
"@backstage/plugin-permission-common": "^0.8.4",
"@backstage/plugin-permission-node": "^0.8.7",
"@kubernetes/client-node": "^0.22.1",
"@kubernetes/client-node": "1.0.0-rc7",
"express": "^4.18.2",
"semver": "^7.5.4"
},
Expand Down
190 changes: 116 additions & 74 deletions workspaces/ocm/plugins/ocm-backend/src/helpers/kubernetes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { OcmConfig } from '../types';
import {
getManagedCluster,
getManagedClusterInfo,
hubApiClient,
listManagedClusterInfos,
listManagedClusters,
} from './kubernetes';
Expand All @@ -37,91 +36,134 @@ afterAll(() => server.close());
const FIXTURES_DIR = `${__dirname}/../../__fixtures__`;
const logger = mockServices.logger.mock();

describe('hubApiClient', () => {
it('should use the default config if there is no service account token configured', () => {
process.env.KUBECONFIG = `${FIXTURES_DIR}/kubeconfig.yaml`;
const clusterConfig = {
id: 'foo',
hubResourceName: 'cluster1',
} as OcmConfig;

const result = hubApiClient(clusterConfig, logger);

expect(result.basePath).toBe('http://example.com');
// These fields aren't on the type but are there
const auth = (result as any).authentications.default;
expect(auth.clusters[0].name).toBe('default-cluster');
expect(auth.users[0].token).toBeUndefined();
});
const mockKubeConfig = {
loadFromOptions: jest.fn(),
loadFromDefault: jest.fn(),
makeApiClient: jest.fn(),
};

it('should use the provided config in the returned api client', () => {
const clusterConfig = {
id: 'foo',
hubResourceName: 'cluster1',
serviceAccountToken: 'TOKEN',
url: 'http://cluster.com',
} as OcmConfig;

const result = hubApiClient(clusterConfig, logger);

expect(result.basePath).toBe('http://cluster.com');
// These fields aren't on the type but are there
const auth = (result as any).authentications.default;
expect(auth.clusters[0].name).toBe('cluster1');
expect(auth.users[0].token).toBe('TOKEN');
describe('kubernetes.ts', () => {
beforeEach(() => {
jest.resetModules();
});
});

const kubeConfig = {
clusters: [{ name: 'cluster', server: 'http://localhost:5000' }],
users: [{ name: 'user', password: 'password' }],
contexts: [{ name: 'currentContext', cluster: 'cluster', user: 'user' }],
currentContext: 'currentContext',
};

const getApi = () => {
const kc = new KubeConfig();
kc.loadFromOptions(kubeConfig);
return kc.makeApiClient(CustomObjectsApi);
};
describe('hubApiClient', () => {
beforeAll(() => {
jest.doMock('@kubernetes/client-node', () => {
const actualModule = jest.requireActual('@kubernetes/client-node');

return {
...actualModule,
KubeConfig: jest.fn().mockImplementation(() => {
return mockKubeConfig;
}),
};
});
});

it('should use the default config if there is no service account token configured', () => {
process.env.KUBECONFIG = `${FIXTURES_DIR}/kubeconfig.yaml`;
const clusterConfig = {
id: 'foo',
hubResourceName: 'cluster1',
} as OcmConfig;

// use require here to ensure the mock is used. It doesn't work with direct import
const { hubApiClient } = require('./kubernetes');

hubApiClient(clusterConfig, logger);

expect(mockKubeConfig.loadFromDefault).toHaveBeenCalled();
expect(mockKubeConfig.makeApiClient).toHaveBeenCalled();
});

it('should use the provided config in the returned api client', () => {
const clusterConfig = {
id: 'foo',
hubResourceName: 'cluster1',
serviceAccountToken: 'TOKEN',
url: 'http://cluster.com',
} as OcmConfig;

// use require here to ensure the mock is used. It doesn't work with direct import
const { hubApiClient } = require('./kubernetes');

hubApiClient(clusterConfig, logger);

expect(mockKubeConfig.makeApiClient).toHaveBeenCalled();
expect(mockKubeConfig.loadFromOptions).toHaveBeenCalledWith({
clusters: [
{
server: 'http://cluster.com',
name: 'cluster1',
skipTLSVerify: undefined,
caData: undefined,
},
],
users: [{ name: 'backstage', token: 'TOKEN' }],
contexts: [
{ cluster: 'cluster1', name: 'cluster1', user: 'backstage' },
],
currentContext: 'cluster1',
});
});
});

describe('getManagedClusters', () => {
it('should return some clusters', async () => {
const result: any = await listManagedClusters(getApi());
expect(result.items[0].metadata.name).toBe('local-cluster');
expect(result.items[1].metadata.name).toBe('cluster1');
const kubeConfig = {
clusters: [{ name: 'cluster', server: 'https://example.com' }],
users: [{ name: 'user', password: 'password' }],
contexts: [{ name: 'currentContext', cluster: 'cluster', user: 'user' }],
currentContext: 'currentContext',
};

const getApi = () => {
const kc = new KubeConfig();
kc.loadFromOptions(kubeConfig);
return kc.makeApiClient(CustomObjectsApi);
};

describe('getManagedClusters', () => {
it('should return some clusters', async () => {
const api = getApi();
const result: any = await listManagedClusters(api);
expect(result.items[0].metadata.name).toBe('local-cluster');
expect(result.items[1].metadata.name).toBe('cluster1');
});
});
});

describe('getManagedCluster', () => {
it('should return the correct cluster', async () => {
const result: any = await getManagedCluster(getApi(), 'cluster1');
describe('getManagedCluster', () => {
it('should return the correct cluster', async () => {
const result: any = await getManagedCluster(getApi(), 'cluster1');

expect(result.metadata.name).toBe('cluster1');
});
expect(result.metadata.name).toBe('cluster1');
});

it('should return an error object when cluster is not found', async () => {
const result = await getManagedCluster(
getApi(),
'non_existent_cluster',
).catch(r => r);
it('should return an error object when cluster is not found', async () => {
const result = await getManagedCluster(
getApi(),
'non_existent_cluster',
).catch(r => r);

expect(result.statusCode).toBe(404);
expect(result.name).toBe('NotFound');
expect(result.statusCode).toBe(404);
expect(result.name).toBe('NotFound');
});
});
});

describe('getManagedClusterInfo', () => {
it('should return cluster', async () => {
const result: any = await getManagedClusterInfo(getApi(), 'local-cluster');
expect(result.metadata.name).toBe('local-cluster');
describe('getManagedClusterInfo', () => {
it('should return cluster', async () => {
const result: any = await getManagedClusterInfo(
getApi(),
'local-cluster',
);
expect(result.metadata.name).toBe('local-cluster');
});
});
});

describe('getManagedClusterInfos', () => {
it('should return some cluster infos', async () => {
const result: any = await listManagedClusterInfos(getApi());
expect(result.items[0].metadata.name).toBe('local-cluster');
expect(result.items[1].metadata.name).toBe('cluster1');
describe('getManagedClusterInfos', () => {
it('should return some cluster infos', async () => {
const result: any = await listManagedClusterInfos(getApi());
expect(result.items[0].metadata.name).toBe('local-cluster');
expect(result.items[1].metadata.name).toBe('cluster1');
});
});
});
94 changes: 44 additions & 50 deletions workspaces/ocm/plugins/ocm-backend/src/helpers/kubernetes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import {
KubernetesListObject,
} from '@kubernetes/client-node';

import http from 'http';

import { ManagedCluster, ManagedClusterInfo, OcmConfig } from '../types';

export const hubApiClient = (
Expand Down Expand Up @@ -66,77 +64,73 @@ export const hubApiClient = (
return kubeConfig.makeApiClient(CustomObjectsApi);
};

const kubeApiResponseHandler = <T extends Object>(
call: Promise<{
response: http.IncomingMessage;
body: object;
}>,
) => {
return call
.then(r => {
return r.body as T;
})
.catch(r => {
if (!r.body) {
throw Object.assign(new Error(r.message), {
// If there is no body, there is no status code, default to 500
statusCode: 500,
name: r.message,
});
} else if (typeof r.body === 'string') {
throw Object.assign(new Error(r.body), {
statusCode: r.body.code || r.statusCode,
name: r.body,
const kubeApiResponseHandler = <T>(call: Promise<T>) => {
return call.catch(e => {
// r.body should be string or blob binary
if ('body' in e && typeof e.body === 'string') {
let body;
try {
body = JSON.parse(e.body);
} catch (error) {
/* eslint-disable-line no-empty */
}
if (body) {
throw Object.assign(new Error(body.reason), {
// Name and statusCode are required by the backstage error handler
statusCode: body.code,
name: body.reason,
...body,
});
}
throw Object.assign(new Error(r.body.reason), {
// Name and statusCode are required by the backstage error handler
statusCode: r.body.code || r.statusCode,
name: r.body.reason,
...r.body,
});
}

throw Object.assign(new Error(e.message), {
// If there is no body, default to 500
statusCode: 500,
name: e.message,
});
});
};

export const getManagedCluster = (api: CustomObjectsApi, name: string) => {
return kubeApiResponseHandler<ManagedCluster>(
api.getClusterCustomObject(
'cluster.open-cluster-management.io',
'v1',
'managedclusters',
api.getClusterCustomObject({
plural: 'managedclusters',
version: 'v1',
group: 'cluster.open-cluster-management.io',
name,
),
}),
);
};

export const listManagedClusters = (api: CustomObjectsApi) => {
return kubeApiResponseHandler<KubernetesListObject<ManagedCluster>>(
api.listClusterCustomObject(
'cluster.open-cluster-management.io',
'v1',
'managedclusters',
),
api.listClusterCustomObject({
group: 'cluster.open-cluster-management.io',
version: 'v1',
plural: 'managedclusters',
}),
);
};

export const getManagedClusterInfo = (api: CustomObjectsApi, name: string) => {
return kubeApiResponseHandler<ManagedClusterInfo>(
api.getNamespacedCustomObject(
'internal.open-cluster-management.io',
'v1beta1',
name,
'managedclusterinfos',
api.getNamespacedCustomObject({
group: 'internal.open-cluster-management.io',
version: 'v1beta1',
name,
),
namespace: name,
plural: 'managedclusterinfos',
}),
);
};

export const listManagedClusterInfos = (api: CustomObjectsApi) => {
return kubeApiResponseHandler<KubernetesListObject<ManagedClusterInfo>>(
api.listClusterCustomObject(
'internal.open-cluster-management.io',
'v1beta1',
'managedclusterinfos',
),
api.listClusterCustomObject({
group: 'internal.open-cluster-management.io',
version: 'v1beta1',
plural: 'managedclusterinfos',
}),
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const config = mockServices.rootConfig.factory({
ocm: {
foo: {
name: 'thisishub',
url: 'http://localhost:5000',
url: 'https://example.com',
serviceAccountToken: 'TOKEN',
},
},
Expand Down
3 changes: 1 addition & 2 deletions workspaces/ocm/plugins/ocm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@
"@types/react-dom": "^18.2.19",
"cross-fetch": "4.0.0",
"msw": "1.3.5",
"prettier": "3.4.2",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "^6.0.0"
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"files": [
"dist",
Expand Down
Loading

0 comments on commit bd54309

Please sign in to comment.