From cbb0e3c3f47d6b43c70312a02c90c8e0c3f3b187 Mon Sep 17 00:00:00 2001 From: Andres Mauricio Gomez P Date: Fri, 15 Sep 2023 09:39:48 -0500 Subject: [PATCH 1/4] Creating extension point for kubernetesObjectsProvider Signed-off-by: Andres Mauricio Gomez P --- .changeset/nice-ghosts-build.md | 6 ++++ plugins/kubernetes-backend/package.json | 1 + plugins/kubernetes-backend/src/plugin.ts | 37 +++++++++++++++++++++-- plugins/kubernetes-node/.eslintrc.js | 1 + plugins/kubernetes-node/README.md | 5 +++ plugins/kubernetes-node/api-report.md | 17 +++++++++++ plugins/kubernetes-node/catalog-info.yaml | 10 ++++++ plugins/kubernetes-node/package.json | 34 +++++++++++++++++++++ plugins/kubernetes-node/src/extensions.ts | 36 ++++++++++++++++++++++ plugins/kubernetes-node/src/index.ts | 35 +++++++++++++++++++++ plugins/kubernetes-node/src/setupTests.ts | 16 ++++++++++ yarn.lock | 11 +++++++ 12 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 .changeset/nice-ghosts-build.md create mode 100644 plugins/kubernetes-node/.eslintrc.js create mode 100644 plugins/kubernetes-node/README.md create mode 100644 plugins/kubernetes-node/api-report.md create mode 100644 plugins/kubernetes-node/catalog-info.yaml create mode 100644 plugins/kubernetes-node/package.json create mode 100644 plugins/kubernetes-node/src/extensions.ts create mode 100644 plugins/kubernetes-node/src/index.ts create mode 100644 plugins/kubernetes-node/src/setupTests.ts diff --git a/.changeset/nice-ghosts-build.md b/.changeset/nice-ghosts-build.md new file mode 100644 index 0000000000..a0f22aa63f --- /dev/null +++ b/.changeset/nice-ghosts-build.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-node': minor +'@backstage/plugin-kubernetes-backend': patch +--- + +A new Package is introduced to house the backend plugin's extension points for Kubernetes plugin, at the moment only the KubernetesObjectsProviderExtensionPoint is present. The Kubernetes-backed package was modified to use this new extension point diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index bb39a01c6c..dc16ebc1c9 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -61,6 +61,7 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", + "@backstage/plugin-kubernetes-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index e2523b4ecd..2ec7500741 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -21,15 +21,46 @@ import { } from '@backstage/backend-plugin-api'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; -import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; +import { + KubernetesBuilder, + KubernetesObjectsProvider, +} from '@backstage/plugin-kubernetes-backend'; +import { + KubernetesObjectsProviderExtensionPoint, + kubernetesObjectsProviderExtensionPoint, +} from '@backstage/plugin-kubernetes-node'; + +class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { + private objectsProvider: KubernetesObjectsProvider | undefined; + + getObjectsProvider() { + return this.objectsProvider; + } + + addObjectsProvider(provider: KubernetesObjectsProvider) { + if (this.objectsProvider) { + throw new Error( + 'Multiple Kubernetes objects provider is not supported at this time', + ); + } + this.objectsProvider = provider; + } +} /** * This is the backend plugin that provides the Kubernetes integration. * @alpha */ + export const kubernetesPlugin = createBackendPlugin({ pluginId: 'kubernetes', register(env) { + const extensionPoint = new ObjectsProvider(); + env.registerExtensionPoint( + kubernetesObjectsProviderExtensionPoint, + extensionPoint, + ); + env.registerInit({ deps: { http: coreServices.httpRouter, @@ -46,7 +77,9 @@ export const kubernetesPlugin = createBackendPlugin({ config, catalogApi, permissions, - }).build(); + }) + .setObjectsProvider(extensionPoint.getObjectsProvider()) + .build(); http.use(router); }, }); diff --git a/plugins/kubernetes-node/.eslintrc.js b/plugins/kubernetes-node/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/kubernetes-node/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/kubernetes-node/README.md b/plugins/kubernetes-node/README.md new file mode 100644 index 0000000000..afdc299bce --- /dev/null +++ b/plugins/kubernetes-node/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-kubernetes-node + +Welcome to the Node.js library package for the kubernetes plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/kubernetes-node/api-report.md b/plugins/kubernetes-node/api-report.md new file mode 100644 index 0000000000..127a360496 --- /dev/null +++ b/plugins/kubernetes-node/api-report.md @@ -0,0 +1,17 @@ +## API Report File for "@backstage/plugin-kubernetes-node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-backend'; + +// @public +export interface KubernetesObjectsProviderExtensionPoint { + // (undocumented) + addObjectsProvider(provider: KubernetesObjectsProvider): void; +} + +// @public +export const kubernetesObjectsProviderExtensionPoint: ExtensionPoint; +``` diff --git a/plugins/kubernetes-node/catalog-info.yaml b/plugins/kubernetes-node/catalog-info.yaml new file mode 100644 index 0000000000..ad04818e49 --- /dev/null +++ b/plugins/kubernetes-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-kubernetes-node + title: '@backstage/plugin-kubernetes-node' + description: Node.js library for the kubernetes plugin +spec: + lifecycle: experimental + type: backstage-node-library + owner: kubernetes-maintainers diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json new file mode 100644 index 0000000000..c0dad70e21 --- /dev/null +++ b/plugins/kubernetes-node/package.json @@ -0,0 +1,34 @@ +{ + "name": "@backstage/plugin-kubernetes-node", + "description": "Node.js library for the kubernetes plugin", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "scripts": { + "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" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ], + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-kubernetes-backend": "workspace:^" + } +} diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts new file mode 100644 index 0000000000..927ff8a32f --- /dev/null +++ b/plugins/kubernetes-node/src/extensions.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-backend'; + +/** + * The interface for {@link kubernetesObjectsProviderExtensionPoint}. + * + * @public + */ +export interface KubernetesObjectsProviderExtensionPoint { + addObjectsProvider(provider: KubernetesObjectsProvider): void; +} + +/** + * An extension point the exposes the ability to configure a objects provider. + * + * @public + */ +export const kubernetesObjectsProviderExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.objects-provider', + }); diff --git a/plugins/kubernetes-node/src/index.ts b/plugins/kubernetes-node/src/index.ts new file mode 100644 index 0000000000..eb38e591da --- /dev/null +++ b/plugins/kubernetes-node/src/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Node.js library for the kubernetes plugin. + * + * @packageDocumentation + */ + +// In this package you might for example export functions that +// help other plugins or modules interact with your plugin. + +/** + * Node.js library for the kubernetes plugin. + * + * @packageDocumentation + */ + +export { + kubernetesObjectsProviderExtensionPoint, + type KubernetesObjectsProviderExtensionPoint, +} from './extensions'; diff --git a/plugins/kubernetes-node/src/setupTests.ts b/plugins/kubernetes-node/src/setupTests.ts new file mode 100644 index 0000000000..4b9026cde5 --- /dev/null +++ b/plugins/kubernetes-node/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export {}; diff --git a/yarn.lock b/yarn.lock index b029e6280f..6da141bf2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7570,6 +7570,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-kubernetes-common": "workspace:^" + "@backstage/plugin-kubernetes-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" @@ -7702,6 +7703,16 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-kubernetes-node@workspace:^, @backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node": + version: 0.0.0-use.local + resolution: "@backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-kubernetes-backend": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/plugin-kubernetes@workspace:^, @backstage/plugin-kubernetes@workspace:plugins/kubernetes": version: 0.0.0-use.local resolution: "@backstage/plugin-kubernetes@workspace:plugins/kubernetes" From 1b69da21b03a3fb1e855b6fe043f24e0939bad5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Mauricio=20G=C3=B3mez=20P?= Date: Tue, 19 Sep 2023 06:00:11 -0500 Subject: [PATCH 2/4] Update .changeset/nice-ghosts-build.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jamie Klassen Signed-off-by: Andrés Mauricio Gómez P --- .changeset/nice-ghosts-build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/nice-ghosts-build.md b/.changeset/nice-ghosts-build.md index a0f22aa63f..abdad08c3b 100644 --- a/.changeset/nice-ghosts-build.md +++ b/.changeset/nice-ghosts-build.md @@ -3,4 +3,4 @@ '@backstage/plugin-kubernetes-backend': patch --- -A new Package is introduced to house the backend plugin's extension points for Kubernetes plugin, at the moment only the KubernetesObjectsProviderExtensionPoint is present. The Kubernetes-backed package was modified to use this new extension point +A new plugin has been introduced to house the extension points for Kubernetes backend plugin; at the moment only the `KubernetesObjectsProviderExtensionPoint` is present. The `kubernetes-backend` plugin was modified to use this new extension point. From 99c1ef9c2621b8e9e7424abb703a5ebb2fe909ac Mon Sep 17 00:00:00 2001 From: Andres Mauricio Gomez P Date: Wed, 20 Sep 2023 10:50:56 -0500 Subject: [PATCH 3/4] Modifying resourceRoutes tests to use startTestBackend and the kubernetesObjectsProviderExtensionPoint Signed-off-by: Andres Mauricio Gomez P --- plugins/kubernetes-backend/package.json | 1 + .../src/routes/resourceRoutes.test.ts | 212 ++++++++++-------- yarn.lock | 1 + 3 files changed, 126 insertions(+), 88 deletions(-) diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index dc16ebc1c9..e188650999 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -87,6 +87,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/aws4": "^1.5.1", diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index 753be36fb0..b8254d781c 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -14,23 +14,64 @@ * limitations under the License. */ -import { errorHandler } from '@backstage/backend-common'; -import express from 'express'; import request from 'supertest'; -import Router from 'express-promise-router'; -import { addResourceRoutesToRouter } from './resourcesRoutes'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; describe('resourcesRoutes', () => { - let app: express.Express; + let app: ExtendedHttpServer; - beforeAll(() => { - app = express(); - app.use(express.json()); - const router = Router(); - addResourceRoutesToRouter( - router, - { + beforeAll(async () => { + const objectsProviderMock = { + getKubernetesObjectsByEntity: jest.fn().mockImplementation(args => { + if (args.entity.metadata.name === 'inject500') { + return Promise.reject(new Error('some internal error')); + } + + return Promise.resolve({ + items: [ + { + clusterOne: { + pods: [ + { + metadata: { + name: 'pod1', + }, + }, + ], + }, + }, + ], + }); + }), + getCustomResourcesByEntity: jest.fn().mockImplementation(args => { + if (args.entity.metadata.name === 'inject500') { + return Promise.reject(new Error('some internal error')); + } + + return Promise.resolve({ + items: [ + { + clusterOne: { + pods: [ + { + metadata: { + name: 'pod1', + }, + }, + ], + }, + }, + ], + }); + }), + }; + + jest.mock('@backstage/catalog-client', () => ({ + CatalogClient: jest.fn().mockImplementation(() => ({ getEntityByRef: jest.fn().mockImplementation(entityRef => { if (entityRef.name === 'noentity') { return Promise.resolve(undefined); @@ -43,61 +84,50 @@ describe('resourcesRoutes', () => { }, } as Entity); }), - } as any, - { - getKubernetesObjectsByEntity: jest.fn().mockImplementation(args => { - if (args.entity.metadata.name === 'inject500') { - return Promise.reject(new Error('some internal error')); - } + })), + })); - return Promise.resolve({ - items: [ - { - clusterOne: { - pods: [ - { - metadata: { - name: 'pod1', - }, - }, - ], - }, + const { server } = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { + type: 'multiTenant', }, - ], - }); + clusterLocatorMethods: [ + { + type: 'config', + clusters: [], + }, + ], + }, + }, }), - getCustomResourcesByEntity: jest.fn().mockImplementation(args => { - if (args.entity.metadata.name === 'inject500') { - return Promise.reject(new Error('some internal error')); - } + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testObjectsProvider', + register(env) { + env.registerInit({ + deps: { extension: kubernetesObjectsProviderExtensionPoint }, + async init({ extension }) { + extension.addObjectsProvider(objectsProviderMock); + }, + }); + }, + }), + ], + }); - return Promise.resolve({ - items: [ - { - clusterOne: { - pods: [ - { - metadata: { - name: 'pod1', - }, - }, - ], - }, - }, - ], - }); - }), - } as any, - ); - app.use('/', router); - app.use(errorHandler()); + app = server; }); describe('POST /resources/workloads/query', () => { // eslint-disable-next-line jest/expect-expect it('200 happy path', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'kind:namespacec/someComponent', auth: { @@ -125,7 +155,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when missing entity ref', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ auth: { google: 'something', @@ -137,7 +167,7 @@ describe('resourcesRoutes', () => { error: { name: 'InputError', message: 'entity is a required field' }, request: { method: 'POST', - url: '/resources/workloads/query', + url: '/api/kubernetes/resources/workloads/query', }, response: { statusCode: 400 }, }); @@ -145,7 +175,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when bad entity ref', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'ffff', auth: { @@ -162,7 +192,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/workloads/query', + url: '/api/kubernetes/resources/workloads/query', }, response: { statusCode: 400 }, }); @@ -170,7 +200,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when no entity in catalog', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'noentity:noentity', auth: { @@ -186,7 +216,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/workloads/query', + url: '/api/kubernetes/resources/workloads/query', }, response: { statusCode: 400 }, }); @@ -194,7 +224,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('401 when no Auth header', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'component:someComponent', auth: { @@ -206,7 +236,7 @@ describe('resourcesRoutes', () => { error: { name: 'AuthenticationError', message: 'No Backstage token' }, request: { method: 'POST', - url: '/resources/workloads/query', + url: '/api/kubernetes/resources/workloads/query', }, response: { statusCode: 401 }, }); @@ -214,7 +244,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('401 when invalid Auth header', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'component:someComponent', auth: { @@ -227,7 +257,7 @@ describe('resourcesRoutes', () => { error: { name: 'AuthenticationError', message: 'No Backstage token' }, request: { method: 'POST', - url: '/resources/workloads/query', + url: '/api/kubernetes/resources/workloads/query', }, response: { statusCode: 401 }, }); @@ -235,7 +265,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('500 handle gracefully', async () => { await request(app) - .post('/resources/workloads/query') + .post('/api/kubernetes/resources/workloads/query') .send({ entityRef: 'inject500:inject500/inject500', auth: { @@ -249,7 +279,10 @@ describe('resourcesRoutes', () => { name: 'Error', message: 'some internal error', }, - request: { method: 'POST', url: '/resources/workloads/query' }, + request: { + method: 'POST', + url: '/api/kubernetes/resources/workloads/query', + }, response: { statusCode: 500 }, }); }); @@ -258,7 +291,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('200 happy path', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -293,7 +326,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when missing custom resources', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -309,7 +342,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -317,7 +350,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when custom resources not array', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -334,7 +367,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -342,7 +375,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when custom resources empty', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -359,7 +392,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -367,7 +400,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when missing entity ref', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ auth: { google: 'something', @@ -386,7 +419,7 @@ describe('resourcesRoutes', () => { error: { name: 'InputError', message: 'entity is a required field' }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -394,7 +427,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when bad entity ref', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'ffff', auth: { @@ -418,7 +451,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -426,7 +459,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('400 when no entity in catalog', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'noentity:noentity', auth: { @@ -449,7 +482,7 @@ describe('resourcesRoutes', () => { }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 400 }, }); @@ -457,7 +490,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('401 when no Auth header', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -476,7 +509,7 @@ describe('resourcesRoutes', () => { error: { name: 'AuthenticationError', message: 'No Backstage token' }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 401 }, }); @@ -484,7 +517,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('401 when invalid Auth header', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'component:someComponent', auth: { @@ -504,7 +537,7 @@ describe('resourcesRoutes', () => { error: { name: 'AuthenticationError', message: 'No Backstage token' }, request: { method: 'POST', - url: '/resources/custom/query', + url: '/api/kubernetes/resources/custom/query', }, response: { statusCode: 401 }, }); @@ -512,7 +545,7 @@ describe('resourcesRoutes', () => { // eslint-disable-next-line jest/expect-expect it('500 handle gracefully', async () => { await request(app) - .post('/resources/custom/query') + .post('/api/kubernetes/resources/custom/query') .send({ entityRef: 'inject500:inject500/inject500', auth: { @@ -533,7 +566,10 @@ describe('resourcesRoutes', () => { name: 'Error', message: 'some internal error', }, - request: { method: 'POST', url: '/resources/custom/query' }, + request: { + method: 'POST', + url: '/api/kubernetes/resources/custom/query', + }, response: { statusCode: 500 }, }); }); diff --git a/yarn.lock b/yarn.lock index 6da141bf2c..2cbde07d4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7558,6 +7558,7 @@ __metadata: "@aws-sdk/credential-providers": ^3.350.0 "@aws-sdk/signature-v4": ^3.347.0 "@azure/identity": ^3.2.1 + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 6b044e8840ded622ff9efcc3ccfa004f60b02d92 Mon Sep 17 00:00:00 2001 From: Andres Mauricio Gomez P Date: Mon, 25 Sep 2023 12:07:19 -0500 Subject: [PATCH 4/4] Moving KubernetesObjectsProvider interface to kubernetes-node to avoid a cyclic dependency Signed-off-by: Andres Mauricio Gomez P --- plugins/kubernetes-backend/api-report.md | 30 +++-------- plugins/kubernetes-backend/src/plugin.ts | 6 +-- .../src/routes/resourcesRoutes.ts | 2 +- .../src/service/KubernetesBuilder.ts | 2 +- .../src/service/KubernetesFanOutHandler.ts | 6 ++- plugins/kubernetes-backend/src/types/index.ts | 6 +++ plugins/kubernetes-backend/src/types/types.ts | 32 ----------- plugins/kubernetes-node/api-report.md | 34 +++++++++++- plugins/kubernetes-node/package.json | 3 +- plugins/kubernetes-node/src/extensions.ts | 2 +- plugins/kubernetes-node/src/index.ts | 2 + plugins/kubernetes-node/src/types/index.ts | 17 ++++++ plugins/kubernetes-node/src/types/types.ts | 53 +++++++++++++++++++ yarn.lock | 21 ++++---- 14 files changed, 138 insertions(+), 78 deletions(-) create mode 100644 plugins/kubernetes-node/src/types/index.ts create mode 100644 plugins/kubernetes-node/src/types/types.ts diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 158b917a48..3828cdf19f 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -6,16 +6,18 @@ import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import type { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; +import { CustomResourcesByEntity } from '@backstage/plugin-kubernetes-node'; import { Duration } from 'luxon'; import { Entity } from '@backstage/catalog-model'; import express from 'express'; import type { FetchResponse } from '@backstage/plugin-kubernetes-common'; import type { JsonObject } from '@backstage/types'; import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; +import { KubernetesObjectsByEntity } from '@backstage/plugin-kubernetes-node'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger } from 'winston'; -import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { RequestHandler } from 'http-proxy-middleware'; @@ -101,11 +103,7 @@ export interface CustomResource extends ObjectToFetch { objectType: 'customresources'; } -// @public (undocumented) -export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { - // (undocumented) - customResources: CustomResourceMatcher[]; -} +export { CustomResourcesByEntity }; // @public (undocumented) export const DEFAULT_OBJECTS: ObjectToFetch[]; @@ -316,25 +314,9 @@ export interface KubernetesFetcher { ): Promise; } -// @public (undocumented) -export interface KubernetesObjectsByEntity { - // (undocumented) - auth: KubernetesRequestAuth; - // (undocumented) - entity: Entity; -} +export { KubernetesObjectsByEntity }; -// @public (undocumented) -export interface KubernetesObjectsProvider { - // (undocumented) - getCustomResourcesByEntity( - customResourcesByEntity: CustomResourcesByEntity, - ): Promise; - // (undocumented) - getKubernetesObjectsByEntity( - kubernetesObjectsByEntity: KubernetesObjectsByEntity, - ): Promise; -} +export { KubernetesObjectsProvider }; // @public (undocumented) export interface KubernetesObjectsProviderOptions { diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index 2ec7500741..83dbae15a0 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -21,13 +21,11 @@ import { } from '@backstage/backend-plugin-api'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; -import { - KubernetesBuilder, - KubernetesObjectsProvider, -} from '@backstage/plugin-kubernetes-backend'; +import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; import { KubernetesObjectsProviderExtensionPoint, kubernetesObjectsProviderExtensionPoint, + KubernetesObjectsProvider, } from '@backstage/plugin-kubernetes-node'; class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { diff --git a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts index 2b2a48f707..0468799908 100644 --- a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts +++ b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts @@ -21,7 +21,7 @@ import { import { CatalogApi } from '@backstage/catalog-client'; import { InputError, AuthenticationError } from '@backstage/errors'; import express, { Request } from 'express'; -import { KubernetesObjectsProvider } from '../types/types'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; export const addResourceRoutesToRouter = ( diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 9ac1bf6e5d..b05db36398 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -47,13 +47,13 @@ import { CustomResource, KubernetesClustersSupplier, KubernetesFetcher, - KubernetesObjectsProvider, KubernetesObjectsProviderOptions, KubernetesObjectTypes, KubernetesServiceLocator, ObjectsByEntityRequest, ServiceLocatorMethod, } from '../types/types'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { DEFAULT_OBJECTS, KubernetesFanOutHandler, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index f2f1c6d38f..02f7ab86df 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -25,8 +25,6 @@ import { FetchResponseWrapper, ObjectToFetch, CustomResource, - CustomResourcesByEntity, - KubernetesObjectsByEntity, } from '../types/types'; import { AuthenticationStrategy, KubernetesCredential } from '../auth/types'; import { @@ -46,6 +44,10 @@ import { CurrentResourceUsage, PodStatus, } from '@kubernetes/client-node'; +import { + CustomResourcesByEntity, + KubernetesObjectsByEntity, +} from '@backstage/plugin-kubernetes-node'; /** * diff --git a/plugins/kubernetes-backend/src/types/index.ts b/plugins/kubernetes-backend/src/types/index.ts index db229eae34..c8413c1aa9 100644 --- a/plugins/kubernetes-backend/src/types/index.ts +++ b/plugins/kubernetes-backend/src/types/index.ts @@ -15,3 +15,9 @@ */ export * from './types'; + +export type { + CustomResourcesByEntity, + KubernetesObjectsByEntity, + KubernetesObjectsProvider, +} from '@backstage/plugin-kubernetes-node'; diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 3767791589..9236711758 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -21,9 +21,7 @@ import type { CustomResourceMatcher, FetchResponse, KubernetesFetchError, - KubernetesRequestAuth, KubernetesRequestBody, - ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; import { KubernetesCredential } from '../auth/types'; @@ -229,33 +227,3 @@ export interface KubernetesObjectsProviderOptions { * @public */ export type ObjectsByEntityRequest = KubernetesRequestBody; - -/** - * - * @public - */ -export interface KubernetesObjectsByEntity { - entity: Entity; - auth: KubernetesRequestAuth; -} - -/** - * - * @public - */ -export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { - customResources: CustomResourceMatcher[]; -} - -/** - * - * @public - */ -export interface KubernetesObjectsProvider { - getKubernetesObjectsByEntity( - kubernetesObjectsByEntity: KubernetesObjectsByEntity, - ): Promise; - getCustomResourcesByEntity( - customResourcesByEntity: CustomResourcesByEntity, - ): Promise; -} diff --git a/plugins/kubernetes-node/api-report.md b/plugins/kubernetes-node/api-report.md index 127a360496..d2981b32d1 100644 --- a/plugins/kubernetes-node/api-report.md +++ b/plugins/kubernetes-node/api-report.md @@ -3,13 +3,43 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; +import { Entity } from '@backstage/catalog-model'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; -import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; +import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; + +// @public (undocumented) +export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { + // (undocumented) + customResources: CustomResourceMatcher[]; +} + +// @public (undocumented) +export interface KubernetesObjectsByEntity { + // (undocumented) + auth: KubernetesRequestAuth; + // (undocumented) + entity: Entity; +} + +// @public (undocumented) +export interface KubernetesObjectsProvider { + // (undocumented) + getCustomResourcesByEntity( + customResourcesByEntity: CustomResourcesByEntity, + ): Promise; + // (undocumented) + getKubernetesObjectsByEntity( + kubernetesObjectsByEntity: KubernetesObjectsByEntity, + ): Promise; +} // @public export interface KubernetesObjectsProviderExtensionPoint { // (undocumented) - addObjectsProvider(provider: KubernetesObjectsProvider): void; + addObjectsProvider(provider: KubernetesObjectsProvider_2): void; } // @public diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index c0dad70e21..cba5d6afa1 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -29,6 +29,7 @@ ], "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/plugin-kubernetes-backend": "workspace:^" + "@backstage/catalog-model": "workspace:^", + "@backstage/plugin-kubernetes-common": "workspace:^" } } diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts index 927ff8a32f..152622cf96 100644 --- a/plugins/kubernetes-node/src/extensions.ts +++ b/plugins/kubernetes-node/src/extensions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; -import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; /** * The interface for {@link kubernetesObjectsProviderExtensionPoint}. diff --git a/plugins/kubernetes-node/src/index.ts b/plugins/kubernetes-node/src/index.ts index eb38e591da..09ea4123ce 100644 --- a/plugins/kubernetes-node/src/index.ts +++ b/plugins/kubernetes-node/src/index.ts @@ -33,3 +33,5 @@ export { kubernetesObjectsProviderExtensionPoint, type KubernetesObjectsProviderExtensionPoint, } from './extensions'; + +export * from './types'; diff --git a/plugins/kubernetes-node/src/types/index.ts b/plugins/kubernetes-node/src/types/index.ts new file mode 100644 index 0000000000..34ed8a9604 --- /dev/null +++ b/plugins/kubernetes-node/src/types/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types'; diff --git a/plugins/kubernetes-node/src/types/types.ts b/plugins/kubernetes-node/src/types/types.ts new file mode 100644 index 0000000000..182f3ecfeb --- /dev/null +++ b/plugins/kubernetes-node/src/types/types.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entity } from '@backstage/catalog-model'; +import { + CustomResourceMatcher, + KubernetesRequestAuth, + ObjectsByEntityResponse, +} from '@backstage/plugin-kubernetes-common'; + +/** + * + * @public + */ + +export interface KubernetesObjectsProvider { + getKubernetesObjectsByEntity( + kubernetesObjectsByEntity: KubernetesObjectsByEntity, + ): Promise; + getCustomResourcesByEntity( + customResourcesByEntity: CustomResourcesByEntity, + ): Promise; +} + +/** + * + * @public + */ + +export interface KubernetesObjectsByEntity { + entity: Entity; + auth: KubernetesRequestAuth; +} +/** + * + * @public + */ + +export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { + customResources: CustomResourceMatcher[]; +} diff --git a/yarn.lock b/yarn.lock index 2cbde07d4a..be85418d6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7665,6 +7665,17 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-kubernetes-node@workspace:^, @backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node": + version: 0.0.0-use.local + resolution: "@backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-kubernetes-common": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/plugin-kubernetes-react@workspace:^, @backstage/plugin-kubernetes-react@workspace:plugins/kubernetes-react": version: 0.0.0-use.local resolution: "@backstage/plugin-kubernetes-react@workspace:plugins/kubernetes-react" @@ -7704,16 +7715,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-kubernetes-node@workspace:^, @backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node": - version: 0.0.0-use.local - resolution: "@backstage/plugin-kubernetes-node@workspace:plugins/kubernetes-node" - dependencies: - "@backstage/backend-plugin-api": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/plugin-kubernetes-backend": "workspace:^" - languageName: unknown - linkType: soft - "@backstage/plugin-kubernetes@workspace:^, @backstage/plugin-kubernetes@workspace:plugins/kubernetes": version: 0.0.0-use.local resolution: "@backstage/plugin-kubernetes@workspace:plugins/kubernetes"