From 34f88ea3a261f2b060fe87c72af51485995f7cac Mon Sep 17 00:00:00 2001 From: SamoKopecky Date: Thu, 5 Jan 2023 14:07:20 +0100 Subject: [PATCH] feat: Add update information to the `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 | 160 +++++++++++++++++- plugins/ocm-backend/src/helpers/parser.ts | 40 +++++ plugins/ocm-backend/src/service/router.ts | 37 +++- plugins/ocm-common/src/index.ts | 5 + 7 files changed, 277 insertions(+), 10 deletions(-) diff --git a/plugins/ocm-backend/package.json b/plugins/ocm-backend/package.json index 35af0b50814..35ffab989d1 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 14c7a4e81f6..cb8f198adba 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 4f2d861083f..ceb8f05cc30 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 d7d17c16cf1..6db2d5f4143 100644 --- a/plugins/ocm-backend/src/helpers/parser.test.ts +++ b/plugins/ocm-backend/src/helpers/parser.test.ts @@ -1,5 +1,16 @@ import { ClusterDetails } from '@janus-idp/backstage-plugin-ocm-common'; -import { getClaim, parseManagedCluster, parseResources } from './parser'; +import { createLogger, transports } from 'winston'; +import { + getClaim, + getClusterInfoByName, + parseManagedCluster, + parseResources, + parseUpdateInfo, +} from './parser'; + +const logger = createLogger({ + transports: [new transports.Console({ silent: true })], +}); describe('getClaim', () => { it('should extract a cluster claim value from a cluster object', () => { @@ -274,3 +285,150 @@ 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', () => { + const clusterInfo = { + metadata: {}, + status: { + distributionInfo: { + ocp: {}, + }, + }, + }; + + 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', + }, + }); + }); +}); + +describe('getClusterInfoByName', () => { + it('should get the correct clusterInfo from many clusterInfos', () => { + const clusterInfo = [ + { + metadata: { + name: 'cluster1', + }, + }, + { + metadata: { + name: 'cluster2', + }, + }, + { + metadata: { + name: 'cluster3', + }, + }, + ]; + + const result = getClusterInfoByName(clusterInfo, 'cluster2', logger); + + expect(result).toEqual({ + metadata: { + name: 'cluster2', + }, + }); + }); + + it('should get the first clusterInfo from many clusterInfos if there are duplicate names', () => { + const clusterInfo = [ + { + metadata: { + name: 'cluster1', + uid: '15c0f22e-8d0a-11ed-a1eb-0242ac120002', + }, + }, + { + metadata: { + name: 'cluster1', + uid: '15c0f7d8-8d0a-11ed-a1eb-0242ac120002', + }, + }, + ]; + + const result = getClusterInfoByName(clusterInfo, 'cluster1', logger); + + expect(result).toEqual({ + metadata: { + name: 'cluster1', + uid: '15c0f22e-8d0a-11ed-a1eb-0242ac120002', + }, + }); + }); +}); diff --git a/plugins/ocm-backend/src/helpers/parser.ts b/plugins/ocm-backend/src/helpers/parser.ts index 3479d590a83..a59462845f9 100644 --- a/plugins/ocm-backend/src/helpers/parser.ts +++ b/plugins/ocm-backend/src/helpers/parser.ts @@ -1,5 +1,7 @@ import { CONSOLE_CLAIM } from '../constants'; import { ClusterDetails } from '@janus-idp/backstage-plugin-ocm-common'; +import { Logger } from 'winston'; +import { maxSatisfying } from 'semver'; const convertCpus = (cpus: string | undefined): number | undefined => { if (!cpus) { @@ -58,3 +60,41 @@ export const parseManagedCluster = (cluster: any): ClusterDetails => { ...parsedClusterInfo, }; }; + +export const parseUpdateInfo = (clusterInfo: any) => { + const versionAvailableUpdates = + clusterInfo.status.distributionInfo.ocp.versionAvailableUpdates; + const versionsAvailable = + clusterInfo.status.distributionInfo.ocp.availableUpdates; + const version = versionsAvailable + ? maxSatisfying(versionsAvailable, '*') + : undefined; + const url = versionAvailableUpdates + ? versionAvailableUpdates[versionsAvailable.indexOf(version)].url + : undefined; + + return { + update: { + available: versionsAvailable ? versionsAvailable.length !== 0 : false, + version: version, + url: url, + }, + }; +}; + +export const getClusterInfoByName = ( + clusterInfos: Array, + clusterName: string, + logger: Logger, +) => { + const clusterInfo = clusterInfos.filter( + (cluster: any) => cluster.metadata.name === clusterName, + ); + + if (clusterInfo.length > 1) { + logger.warn( + 'Found more then one clusters with the same name while filtering clusters, picking the first one', + ); + } + return clusterInfo[0]; +}; diff --git a/plugins/ocm-backend/src/service/router.ts b/plugins/ocm-backend/src/service/router.ts index ba7a461c577..7cec9692590 100644 --- a/plugins/ocm-backend/src/service/router.ts +++ b/plugins/ocm-backend/src/service/router.ts @@ -19,14 +19,18 @@ 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 { + getClusterInfoByName, + parseManagedCluster, + parseUpdateInfo, +} from '../helpers/parser'; import { getHubClusterName } from '../helpers/config'; export interface RouterOptions { @@ -56,8 +60,17 @@ export async function createRouter( return ( getManagedCluster(api, normalizedClusterName) as Promise - ).then(resp => { - response.send(parseManagedCluster(resp)); + ).then(async resp => { + response.send({ + ...parseManagedCluster(resp), + ...parseUpdateInfo( + getClusterInfoByName( + (await (getManagedClustersInfo(api) as Promise)).items, + normalizedClusterName, + logger, + ), + ), + }); }); }, ); @@ -65,10 +78,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 d26ce475f37..1e5cb1a1581 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;