From eb50fec30ca2397931b986421055e46922810d21 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Thu, 5 Jan 2023 14:07:20 +0100 Subject: [PATCH 1/2] feat(ocm): Add cluster update information to the ocm status endpoint Add additional API call to gather information about cluster updates and create additional tests Signed-off-by: SamoKopecky --- plugins/ocm-backend/package.json | 3 +- .../src/helpers/kubernetes.test.ts | 32 +++++ plugins/ocm-backend/src/helpers/kubernetes.ts | 10 ++ .../ocm-backend/src/helpers/parser.test.ts | 120 +++++++++++++++++- plugins/ocm-backend/src/helpers/parser.ts | 27 ++++ plugins/ocm-backend/src/service/router.ts | 32 +++-- plugins/ocm-common/src/index.ts | 5 + 7 files changed, 219 insertions(+), 10 deletions(-) diff --git a/plugins/ocm-backend/package.json b/plugins/ocm-backend/package.json index 35af0b5081..35ffab989d 100644 --- a/plugins/ocm-backend/package.json +++ b/plugins/ocm-backend/package.json @@ -33,12 +33,13 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "node-fetch": "^3.0.0", + "semver": "^7.3.8", "winston": "^3.2.1", "yn": "^5.0.0" }, "devDependencies": { - "@types/express": "4.17.14", "@backstage/cli": "0.21.1", + "@types/express": "4.17.14", "@types/supertest": "2.0.12", "msw": "0.49.1", "nock": "13.2.9", diff --git a/plugins/ocm-backend/src/helpers/kubernetes.test.ts b/plugins/ocm-backend/src/helpers/kubernetes.test.ts index 14c7a4e81f..cb8f198adb 100644 --- a/plugins/ocm-backend/src/helpers/kubernetes.test.ts +++ b/plugins/ocm-backend/src/helpers/kubernetes.test.ts @@ -4,6 +4,7 @@ import { hubApiClient, getManagedCluster, getManagedClusters, + getManagedClustersInfo, } from './kubernetes'; import { createLogger } from 'winston'; import transports from 'winston/lib/winston/transports'; @@ -176,3 +177,34 @@ describe('getManagedCluster', () => { expect(result.name).toBe('NotFound'); }); }); + +describe('getManagedClustersInfo', () => { + it('should return some clusters', async () => { + nock(kubeConfig.clusters[0].server) + .get( + '/apis/internal.open-cluster-management.io/v1beta1/managedclusterinfos', + ) + .reply(200, { + body: { + items: [ + { + kind: 'ManagedClusterInfo', + metadata: { + name: 'cluster1', + }, + }, + { + kind: 'ManagedClusterInfo', + metadata: { + name: 'cluster2', + }, + }, + ], + }, + }); + + const result: any = await getManagedClustersInfo(getApi()); + expect(result.body.items[0].metadata.name).toBe('cluster1'); + expect(result.body.items[1].metadata.name).toBe('cluster2'); + }); +}); diff --git a/plugins/ocm-backend/src/helpers/kubernetes.ts b/plugins/ocm-backend/src/helpers/kubernetes.ts index 4f2d861083..ceb8f05cc3 100644 --- a/plugins/ocm-backend/src/helpers/kubernetes.ts +++ b/plugins/ocm-backend/src/helpers/kubernetes.ts @@ -89,3 +89,13 @@ export const getManagedClusters = (api: CustomObjectsApi) => { ), ); }; + +export const getManagedClustersInfo = (api: CustomObjectsApi) => { + return kubeApiResponseHandler( + api.listClusterCustomObject( + 'internal.open-cluster-management.io', + 'v1beta1', + 'managedclusterinfos', + ), + ); +}; diff --git a/plugins/ocm-backend/src/helpers/parser.test.ts b/plugins/ocm-backend/src/helpers/parser.test.ts index d7d17c16cf..494e6a3727 100644 --- a/plugins/ocm-backend/src/helpers/parser.test.ts +++ b/plugins/ocm-backend/src/helpers/parser.test.ts @@ -1,5 +1,10 @@ import { ClusterDetails } from '@janus-idp/backstage-plugin-ocm-common'; -import { getClaim, parseManagedCluster, parseResources } from './parser'; +import { + getClaim, + parseManagedCluster, + parseResources, + parseUpdateInfo, +} from './parser'; describe('getClaim', () => { it('should extract a cluster claim value from a cluster object', () => { @@ -274,3 +279,116 @@ describe('parseManagedCluster', () => { expect(result).toStrictEqual(expected); }); }); + +describe('parseUpdateInfo', () => { + it('should correctly parse update information from ClusterInfo', () => { + const clusterInfo = { + metadata: {}, + status: { + distributionInfo: { + ocp: { + availableUpdates: ['1.0.1', '1.0.2', '1.0.3', '1.0.0'], + versionAvailableUpdates: [ + { + url: 'http://exampleone.com', + version: '1.0.1', + }, + { + url: 'http://exampletwo.com', + version: '1.0.2', + }, + { + url: 'http://examplethree.com', + version: '1.0.3', + }, + { + url: 'http://examplezero.com', + version: '1.0.0', + }, + ], + }, + }, + }, + }; + + const result = parseUpdateInfo(clusterInfo); + + expect(result).toEqual({ + update: { + available: true, + version: '1.0.3', + url: 'http://examplethree.com', + }, + }); + }); + + it('should correctly parse while there are no updates available with no arrays', () => { + const clusterInfo = { + metadata: {}, + status: { + distributionInfo: { + ocp: {}, + }, + }, + }; + + const result = parseUpdateInfo(clusterInfo); + + expect(result).toEqual({ + update: { + available: false, + }, + }); + }); + + it('should correctly parse while there are no updates available with empty arrays', () => { + const clusterInfo = { + metadata: {}, + status: { + distributionInfo: { + ocp: { + availableUpdates: [], + versionAvailableUpdates: [], + }, + }, + }, + }; + + const result = parseUpdateInfo(clusterInfo); + + expect(result).toEqual({ + update: { + available: false, + }, + }); + }); + + it('should correctly parse while there is only one update available', () => { + const clusterInfo = { + metadata: {}, + status: { + distributionInfo: { + ocp: { + availableUpdates: ['1.0.1'], + versionAvailableUpdates: [ + { + url: 'http://exampleone.com', + version: '1.0.1', + }, + ], + }, + }, + }, + }; + + const result = parseUpdateInfo(clusterInfo); + + expect(result).toEqual({ + update: { + available: true, + version: '1.0.1', + url: 'http://exampleone.com', + }, + }); + }); +}); diff --git a/plugins/ocm-backend/src/helpers/parser.ts b/plugins/ocm-backend/src/helpers/parser.ts index 3479d590a8..746d8d99f2 100644 --- a/plugins/ocm-backend/src/helpers/parser.ts +++ b/plugins/ocm-backend/src/helpers/parser.ts @@ -1,5 +1,6 @@ import { CONSOLE_CLAIM } from '../constants'; import { ClusterDetails } from '@janus-idp/backstage-plugin-ocm-common'; +import { maxSatisfying } from 'semver'; const convertCpus = (cpus: string | undefined): number | undefined => { if (!cpus) { @@ -58,3 +59,29 @@ export const parseManagedCluster = (cluster: any): ClusterDetails => { ...parsedClusterInfo, }; }; + +export const parseUpdateInfo = (clusterInfo: any) => { + const { availableUpdates, versionAvailableUpdates } = + clusterInfo.status.distributionInfo.ocp; + /* + * We assume here that if availableUpdates is empty + * versionAvailableUpdates also has to be empty + */ + if (!availableUpdates || availableUpdates.length === 0) { + return { + update: { + available: false, + }, + }; + } + + const version = maxSatisfying(availableUpdates, '*'); + + return { + update: { + available: true, + version, + url: versionAvailableUpdates[availableUpdates.indexOf(version)].url, + }, + }; +}; diff --git a/plugins/ocm-backend/src/service/router.ts b/plugins/ocm-backend/src/service/router.ts index ba7a461c57..7680a7fa56 100644 --- a/plugins/ocm-backend/src/service/router.ts +++ b/plugins/ocm-backend/src/service/router.ts @@ -19,14 +19,14 @@ import { Config } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; -import { ClusterDetails } from '@janus-idp/backstage-plugin-ocm-common'; import { HUB_CLUSTER_NAME_IN_OCM } from '../constants'; import { getManagedCluster, getManagedClusters, + getManagedClustersInfo, hubApiClient, } from '../helpers/kubernetes'; -import { parseManagedCluster } from '../helpers/parser'; +import { parseManagedCluster, parseUpdateInfo } from '../helpers/parser'; import { getHubClusterName } from '../helpers/config'; export interface RouterOptions { @@ -56,8 +56,16 @@ export async function createRouter( return ( getManagedCluster(api, normalizedClusterName) as Promise - ).then(resp => { - response.send(parseManagedCluster(resp)); + ).then(async resp => { + response.send({ + ...parseManagedCluster(resp), + ...parseUpdateInfo( + (await (getManagedClustersInfo(api) as Promise)).items.find( + (clusterInfo: any) => + clusterInfo.metadata.name === normalizedClusterName, + ), + ), + }); }); }, ); @@ -65,10 +73,18 @@ export async function createRouter( router.get('/status', (_, response) => { logger.info(`Incoming status request for all clusters`); - return (getManagedClusters(api) as Promise).then(resp => { - const parsedClusters: Array = - resp.items.map(parseManagedCluster); - response.send(parsedClusters); + return (getManagedClusters(api) as Promise).then(async resp => { + const clusterInfo = (await (getManagedClustersInfo(api) as Promise)) + .items; + + response.send( + resp.items.map((clusterStatus: any, index: number) => { + return { + ...parseManagedCluster(clusterStatus), + ...parseUpdateInfo(clusterInfo[index]), + }; + }), + ); }); }); diff --git a/plugins/ocm-common/src/index.ts b/plugins/ocm-common/src/index.ts index d26ce475f3..1e5cb1a158 100644 --- a/plugins/ocm-common/src/index.ts +++ b/plugins/ocm-common/src/index.ts @@ -24,6 +24,11 @@ export type ClusterDetails = { memorySize?: string; numberOfPods?: number; }; + update?: { + available?: boolean; + version?: string; + url?: string; + }; status: { available: boolean; reason: string; From ffb7a1265a77866e118830a9a1c0531d549e00b1 Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Wed, 11 Jan 2023 09:15:58 +0100 Subject: [PATCH 2/2] docs(ocm): Update ClusterRole requirement Signed-off-by: SamoKopecky --- plugins/ocm/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/ocm/README.md b/plugins/ocm/README.md index 354d280d83..2534001234 100644 --- a/plugins/ocm/README.md +++ b/plugins/ocm/README.md @@ -22,6 +22,14 @@ This plugin integrates your Backstage instance with Open Cluster Management's Mu - get - watch - list + - apiGroups: + - internal.open-cluster-management.io + resources: + - managedclusterinfos + verbs: + - get + - watch + - list ``` ## Capabilities