-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: transfer Open Cluster Management plugins (#5)
* 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
1 parent
6b5ff5c
commit 1ccf8c3
Showing
48 changed files
with
2,012 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); |
Oops, something went wrong.