Skip to content

Commit

Permalink
feat: transfer Open Cluster Management plugins (#5)
Browse files Browse the repository at this point in the history
* feat: Rename RHACM backend plugin (#160)

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* refactor: Move frontend for RHACM into a separate package  (#161)

* feat: Create common package for type sharing

Signed-off-by: Tomas Coufal <[email protected]>

* feat: Create RHACM frontend package

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* Refactor backend plugin (#163)

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* fix: Find hub cluster in config (#165)

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* feat: Create catalog provider for ACM (#164)

* feat: Create catalog provider for ACM

Signed-off-by: Tomas Coufal <[email protected]>

* feat: Create custom preprocessor to add OCP logo

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* feat: Create new error handler (#156)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* feat: Adapt cluster page for backend refractor (#167)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* refactor: Optimize catalog api fetch with filter (#168)

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* fix: Raise proper error when hub cluster is undefined in the config (#184)

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>

* chore: Remove unsued function (#188)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* chore: Update pre-commit to run prettier (#187)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* chore: Bump backstage to 1.8.2 (#192)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* test: Add tests for config.ts and parser.ts (#186)

* feat: Add resource parser

Signed-off-by: SamoKopecky <[email protected]>

* test: Add tests for config.ts and parser.ts

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* feat: Add error handling to frontend (#162)

Signed-off-by: SamoKopecky <[email protected]>

Signed-off-by: SamoKopecky <[email protected]>

* chore: Rename RHACM plugin to upstream OCM

Signed-off-by: Tomas Coufal <[email protected]>

Signed-off-by: Tomas Coufal <[email protected]>
Signed-off-by: SamoKopecky <[email protected]>
Co-authored-by: Samuel Kopecký <[email protected]>
  • Loading branch information
tumido and SamoKopecky authored Dec 6, 2022
1 parent 6b5ff5c commit 1ccf8c3
Show file tree
Hide file tree
Showing 48 changed files with 2,012 additions and 18 deletions.
1 change: 1 addition & 0 deletions plugins/ocm-backend/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
9 changes: 9 additions & 0 deletions plugins/ocm-backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# cluster-status

Welcome to the cluster-status backend plugin! This plugin serves as the backend for showing the status page of resources with `kind: cluster`.

## Documentation

More detailed documentation is located in the [`backstage` component documentation][1].

[1]: https://service-catalog.operate-first.cloud/catalog/default/component/backstage/docs
51 changes: 51 additions & 0 deletions plugins/ocm-backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@janus-idp/backstage-plugin-ocm-backend",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "backend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"configSchema": "schema.d.ts",
"dependencies": {
"@backstage/backend-common": "^0.16.0",
"@backstage/catalog-client": "^1.1.2",
"@backstage/catalog-model": "^1.1.3",
"@backstage/config": "^1.0.4",
"@backstage/plugin-catalog-node": "^1.2.1",
"@janus-idp/backstage-plugin-ocm-common": "*",
"@kubernetes/client-node": "^0.17.1",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"node-fetch": "^2.6.7",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@types/express": "*",
"@backstage/cli": "^0.21.1",
"@types/supertest": "^2.0.8",
"msw": "^0.35.0",
"nock": "^13.2.9",
"supertest": "^4.0.2"
},
"files": [
"dist"
]
}
9 changes: 9 additions & 0 deletions plugins/ocm-backend/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface Config {
ocm: {
/**
* Name of the cluster where the Open Cluster Management operator is installed
* @visiblity frontend
*/
hub: string;
};
}
2 changes: 2 additions & 0 deletions plugins/ocm-backend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CONSOLE_CLAIM = 'consoleurl.cluster.open-cluster-management.io';
export const HUB_CLUSTER_NAME_IN_OCM = 'local-cluster';
110 changes: 110 additions & 0 deletions plugins/ocm-backend/src/helpers/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ConfigReader } from '@backstage/config';
import { getHubClusterFromConfig } from './config';

const createConfig = (clusters: any[]) => ({
kubernetes: {
clusterLocatorMethods: [
{
type: 'config',
clusters: clusters,
},
],
},
ocm: {
hub: 'cluster2',
},
});

const createConfigParseResult = (name: string) => ({
data: { name: name },
context: 'mock-config',
prefix: 'kubernetes.clusterLocatorMethods[0].clusters[1]',
fallback: undefined,
filteredKeys: undefined,
notifiedFilteredKeys: new Set(),
});

describe('getHubClusterFromConfig', () => {
it('should get the correct hub cluster from multiple configured clusters', () => {
const config = new ConfigReader(
createConfig([
{
name: 'cluster1',
},
{
name: 'cluster2',
},
{
name: 'cluster3',
},
]),
);

const result = getHubClusterFromConfig(config);

expect(result).toEqual(createConfigParseResult('cluster2'));
});

it('should throw an error when the hub cluster is not found', () => {
const config = new ConfigReader(
createConfig([
{
name: 'cluster4',
},
]),
);

const result = () => getHubClusterFromConfig(config);

expect(result).toThrow('Hub cluster not defined in kubernetes config');
});

it('should throw an error when there are no cluster configured', () => {
const config = new ConfigReader(createConfig([]));

const result = () => {
getHubClusterFromConfig(config);
};

expect(result).toThrow('Hub cluster not defined in kubernetes config');
});

it('should throw an error when locator methods are empty', () => {
const config = new ConfigReader({
kubernetes: {
clusterLocatorMethods: [],
},
ocm: {
hub: 'cluster2',
},
});

const result = () => getHubClusterFromConfig(config);

expect(result).toThrow('Hub cluster not defined in kubernetes config');
});

it('should throw an error when there is to hub cluster configured', () => {
const config = new ConfigReader({});

const result = () => getHubClusterFromConfig(config);

expect(result).toThrow(
"Hub cluster must be specified in config at 'ocm.hub'",
);
});
});

describe('getHubClusterName', () => {
it('should get the hub cluster name from config', () => {
const config = new ConfigReader({
ocm: {
hub: 'cluster2',
},
});

const result = config.getString('ocm.hub');

expect(result).toBe('cluster2');
});
});
29 changes: 29 additions & 0 deletions plugins/ocm-backend/src/helpers/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Config } from '@backstage/config';

const CLUSTERS_PATH = 'kubernetes.clusterLocatorMethods';
const HUB_CLUSTER_CONFIG_PATH = 'ocm.hub';

export const getHubClusterName = (config: Config) =>
config.getString(HUB_CLUSTER_CONFIG_PATH);

export const getHubClusterFromConfig = (config: Config) => {
try {
getHubClusterName(config);
} catch (err) {
throw new Error(
`Hub cluster must be specified in config at '${HUB_CLUSTER_CONFIG_PATH}'`,
);
}

const hub = config
.getConfigArray(CLUSTERS_PATH)
.flatMap(method => method.getOptionalConfigArray('clusters') || [])
.find(
cluster =>
cluster.getString('name') === config.getString(HUB_CLUSTER_CONFIG_PATH),
);
if (!hub) {
throw new Error('Hub cluster not defined in kubernetes config');
}
return hub;
};
178 changes: 178 additions & 0 deletions plugins/ocm-backend/src/helpers/kubernetes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { ConfigReader } from '@backstage/config';
import {
getCustomObjectsApi,
hubApiClient,
getManagedCluster,
getManagedClusters,
} from './kubernetes';
import { createLogger } from 'winston';
import transports from 'winston/lib/winston/transports';
import { CustomObjectsApi, KubeConfig } from '@kubernetes/client-node';
import nock from 'nock';

const logger = createLogger({
transports: [new transports.Console({ silent: true })],
});

describe('getCustomObjectsApi', () => {
it('should use the default config if there is no service account token configured', () => {
process.env.KUBECONFIG = `${__dirname}/test_data/kubeconfig.yaml`;
const clusterConfig = new ConfigReader({
name: 'cluster1',
});

const result = getCustomObjectsApi(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();
});

it('should use the provided config in the returned api client', () => {
const clusterConfig = new ConfigReader({
name: 'cluster1',
serviceAccountToken: 'TOKEN',
url: 'http://cluster.com',
});

const result = getCustomObjectsApi(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('hubApiClient', () => {
it('should return an api client configured with the hub cluster', () => {
const config = new ConfigReader({
kubernetes: {
clusterLocatorMethods: [
{
type: 'config',
clusters: [
{
name: 'cluster2',
serviceAccountToken: 'TOKEN',
url: 'http://cluster2.com',
},
],
},
],
},
ocm: {
hub: 'cluster2',
},
});

const result = hubApiClient(config, logger);

expect(result.basePath).toBe('http://cluster2.com');
// These fields aren't on the type but are there
const auth = (result as any).authentications.default;
expect(auth.clusters[0].name).toBe('cluster2');
});
});

const kubeConfig = {
clusters: [{ name: 'cluster', server: 'https://127.0.0.1:51010' }],
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 () => {
nock(kubeConfig.clusters[0].server)
.get('/apis/cluster.open-cluster-management.io/v1/managedclusters')
.reply(200, {
body: {
items: [
{
kind: 'ManagedCluster',
metadata: {
name: 'cluster1',
},
},
{
kind: 'ManagedCluster',
metadata: {
name: 'cluster2',
},
},
],
},
});

const result: any = await getManagedClusters(getApi());
expect(result.body.items[0].metadata.name).toBe('cluster1');
expect(result.body.items[1].metadata.name).toBe('cluster2');
});
});

describe('getManagedCluster', () => {
it('should return the correct cluster', async () => {
nock(kubeConfig.clusters[0].server)
.get(
'/apis/cluster.open-cluster-management.io/v1/managedclusters/cluster1',
)
.reply(200, {
body: {
metadata: {
name: 'cluster1',
},
},
});
nock(kubeConfig.clusters[0].server)
.get(
'/apis/cluster.open-cluster-management.io/v1/managedclusters/cluster2',
)
.reply(200, {
body: {
metadata: {
name: 'cluster2',
},
},
});

const result: any = await getManagedCluster(getApi(), 'cluster1');

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

it('should return an error object when cluster is not found', async () => {
const errorResponse = {
kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message:
'managedclusters.cluster.open-cluster-management.io "wrong_cluster" not found',
reason: 'NotFound',
code: 404,
};

nock(kubeConfig.clusters[0].server)
.get(
'/apis/cluster.open-cluster-management.io/v1/managedclusters/wrong_cluster',
)
.reply(404, errorResponse);

const result = await getManagedCluster(getApi(), 'wrong_cluster').catch(
r => r,
);

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

0 comments on commit 1ccf8c3

Please sign in to comment.