From 785fb1ea758f06558099be74894ae6d2277af646 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Tue, 25 Apr 2023 16:52:49 -0400 Subject: [PATCH 01/12] Add the ability to view the catalog's spec from the Backstage interface. Signed-off-by: Aramis Signed-off-by: Aramis Sennyey --- .changeset/gentle-pears-camp.md | 5 + .changeset/thirty-stingrays-grin.md | 5 + app-config.yaml | 7 + packages/backend-next/package.json | 2 + packages/backend-next/src/index.ts | 1 + packages/backend-openapi-utils/api-report.md | 6 + packages/backend-openapi-utils/package.json | 3 + .../backend-openapi-utils/src/constants.ts | 21 ++ packages/backend-openapi-utils/src/index.ts | 3 +- .../backend-openapi-utils/src/stub.test.ts | 75 +++++- packages/backend-openapi-utils/src/stub.ts | 41 ++- .../.eslintrc.js | 1 + .../README.md | 30 +++ .../api-report.md | 20 ++ .../package.json | 54 ++++ .../InternalOpenApiDocumentationProvider.ts | 244 ++++++++++++++++++ .../src/index.ts | 62 +++++ .../src/setupTests.ts | 16 ++ yarn.lock | 66 ++++- 19 files changed, 644 insertions(+), 18 deletions(-) create mode 100644 .changeset/gentle-pears-camp.md create mode 100644 .changeset/thirty-stingrays-grin.md create mode 100644 packages/backend-openapi-utils/src/constants.ts create mode 100644 plugins/catalog-backend-module-openapi-spec/.eslintrc.js create mode 100644 plugins/catalog-backend-module-openapi-spec/README.md create mode 100644 plugins/catalog-backend-module-openapi-spec/api-report.md create mode 100644 plugins/catalog-backend-module-openapi-spec/package.json create mode 100644 plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts create mode 100644 plugins/catalog-backend-module-openapi-spec/src/index.ts create mode 100644 plugins/catalog-backend-module-openapi-spec/src/setupTests.ts diff --git a/.changeset/gentle-pears-camp.md b/.changeset/gentle-pears-camp.md new file mode 100644 index 0000000000..1a9f8768f8 --- /dev/null +++ b/.changeset/gentle-pears-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-openapi-spec': patch +--- + +Adds a new catalog module for ingesting Backstage plugin OpenAPI specs into the catalog for display as an API entity. diff --git a/.changeset/thirty-stingrays-grin.md b/.changeset/thirty-stingrays-grin.md new file mode 100644 index 0000000000..43aaf2c606 --- /dev/null +++ b/.changeset/thirty-stingrays-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-openapi-utils': patch +--- + +Adds a new route, `/openapi.json` to validated routers for displaying their full OpenAPI spec in a standard endpoint. diff --git a/app-config.yaml b/app-config.yaml index a2ee232516..e7b0a07483 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -50,6 +50,7 @@ backend: allow: - host: example.com - host: '*.mozilla.org' + - host: localhost:7007 # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir # See README.md in the proxy-backend plugin for information on the configuration format @@ -464,3 +465,9 @@ stackstorm: permission: enabled: true + +openapi: + plugins: + - catalog + - search + - todo diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 3e5b4f8057..38245de00e 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -34,6 +34,8 @@ "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-backend-module-openapi": "workspace:^", + "@backstage/plugin-catalog-backend-module-openapi-spec": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^", "@backstage/plugin-devtools-backend": "workspace:^", diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index 1b4867c05e..d5793dc0fb 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -43,6 +43,7 @@ backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +backend.add(import('@backstage/plugin-catalog-backend-module-openapi-spec')); backend.add(import('@backstage/plugin-search-backend/alpha')); backend.add(import('@backstage/plugin-techdocs-backend/alpha')); backend.add(import('@backstage/plugin-todo-backend')); diff --git a/packages/backend-openapi-utils/api-report.md b/packages/backend-openapi-utils/api-report.md index 97ea0224d1..cc6d31ac71 100644 --- a/packages/backend-openapi-utils/api-report.md +++ b/packages/backend-openapi-utils/api-report.md @@ -248,6 +248,9 @@ type FullMap< }, > = RequiredMap & OptionalMap; +// @public +export function getOpenApiSpecRoute(baseUrl: string): string; + // @public (undocumented) interface HeaderObject extends ParameterObject { // (undocumented) @@ -442,6 +445,9 @@ type ObjectWithContentSchema< ? SchemaRef : never; +// @public +export const OPENAPI_SPEC_ROUTE = '/openapi.json'; + // @public (undocumented) type OptionalMap< T extends { diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 8fa83bd584..15957dd4ac 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -37,6 +37,8 @@ "dist" ], "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", @@ -45,6 +47,7 @@ "express-promise-router": "^4.1.0", "json-schema-to-ts": "^2.6.2", "lodash": "^4.17.21", + "openapi-merge": "^1.3.2", "openapi3-ts": "^3.1.2" } } diff --git a/packages/backend-openapi-utils/src/constants.ts b/packages/backend-openapi-utils/src/constants.ts new file mode 100644 index 0000000000..b55279f6b2 --- /dev/null +++ b/packages/backend-openapi-utils/src/constants.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * The route that all OpenAPI specs should be served from. + * @public + */ +export const OPENAPI_SPEC_ROUTE = '/openapi.json'; diff --git a/packages/backend-openapi-utils/src/index.ts b/packages/backend-openapi-utils/src/index.ts index 2b612fd617..528358132c 100644 --- a/packages/backend-openapi-utils/src/index.ts +++ b/packages/backend-openapi-utils/src/index.ts @@ -31,4 +31,5 @@ export type { PathParameters, } from './utility'; export type { ApiRouter } from './router'; -export { createValidatedOpenApiRouter } from './stub'; +export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub'; +export * from './constants'; diff --git a/packages/backend-openapi-utils/src/stub.test.ts b/packages/backend-openapi-utils/src/stub.test.ts index c9ec955e95..d4a1c19153 100644 --- a/packages/backend-openapi-utils/src/stub.test.ts +++ b/packages/backend-openapi-utils/src/stub.test.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { createValidatedOpenApiRouter } from './stub'; +import { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub'; import express from 'express'; import request from 'supertest'; import singlePathSpec from './___fixtures__/single-path'; import { Response } from './utility'; +import { OPENAPI_SPEC_ROUTE } from './constants'; describe('createRouter', () => { const pet: Response = { @@ -28,6 +29,33 @@ describe('createRouter', () => { photoUrls: [], }; + const specs = [singlePathSpec]; + const ONCE_NESTED_ROUTER_PREFIX = '/pet-store'; + const TWICE_NESTED_ROUTER_PREFIX = `/api`; + + const routers = specs.flatMap(spec => { + const router = createValidatedOpenApiRouter(spec); + const unnestedApp = express(); + unnestedApp.use('/', router); + + const onceNestedRouter = express.Router(); + onceNestedRouter.use(`${ONCE_NESTED_ROUTER_PREFIX}`, router); + const onceNestedApp = express(); + onceNestedApp.use(`/`, onceNestedRouter); + + const twiceNestedApp = express(); + twiceNestedApp.use(`${TWICE_NESTED_ROUTER_PREFIX}`, onceNestedRouter); + return [ + ['', unnestedApp, router], + [ONCE_NESTED_ROUTER_PREFIX, onceNestedApp, router], + [ + `${TWICE_NESTED_ROUTER_PREFIX}${ONCE_NESTED_ROUTER_PREFIX}`, + twiceNestedApp, + router, + ], + ] as const; + }); + it('does NOT override originalUrl and basePath after execution', async () => { expect.assertions(2); const router = createValidatedOpenApiRouter(singlePathSpec); @@ -61,19 +89,40 @@ describe('createRouter', () => { expect(routerGetFn).toHaveBeenCalledTimes(1); }); - it('handles coercing parameters correctly', async () => { - expect.assertions(1); - const router = createValidatedOpenApiRouter(singlePathSpec); - router.get('/pet/:petId', (req, res) => { - expect(typeof req.params.petId).toBe('integer'); - res.json(pet); - }); + it.each(routers)( + '%s handles coercing parameters correctly', + async (prefix, app, router) => { + expect.assertions(1); + router.get('/pet/:petId', (req, res) => { + expect(typeof req.params.petId).toBe('integer'); + res.send(pet); + }); - const apiRouter = express.Router(); - apiRouter.use('/pet-store', router); - const appRouter = express(); - appRouter.use('/api', apiRouter); + await request(app).get(`${prefix}/pet/1`); + }, + ); - await request(appRouter).get('/api/pet-store/pet/1'); + it.each(routers)( + '%s adds the openapi spec to the router', + async (prefix, app) => { + const response = await request(app) + .get(`${prefix}${OPENAPI_SPEC_ROUTE}`) + .expect(200); + expect(response.body).toHaveProperty('paths'); + Object.keys(response.body.paths).forEach(key => { + const specKey = key.replace(prefix, ''); + expect(singlePathSpec.paths).toHaveProperty(specKey); + expect(response.body.paths[key]).toEqual( + (singlePathSpec.paths as any)[specKey], + ); + }); + }, + ); +}); + +describe('getOpenApiSpecRoute', () => { + it('handles expected values', () => { + expect(getOpenApiSpecRoute('/api/test')).toEqual('/api/test/openapi.json'); + expect(getOpenApiSpecRoute('api/test')).toEqual('api/test/openapi.json'); }); }); diff --git a/packages/backend-openapi-utils/src/stub.ts b/packages/backend-openapi-utils/src/stub.ts index 6ec0e1db3c..0f62bf348c 100644 --- a/packages/backend-openapi-utils/src/stub.ts +++ b/packages/backend-openapi-utils/src/stub.ts @@ -27,6 +27,8 @@ import { } from 'express'; import { InputError } from '@backstage/errors'; import { middleware as OpenApiValidator } from 'express-openapi-validator'; +import { OPENAPI_SPEC_ROUTE } from './constants'; +import { isErrorResult, merge } from 'openapi-merge'; type PropertyOverrideRequest = Request & { [key: symbol]: string; @@ -45,6 +47,16 @@ export function getDefaultRouterMiddleware() { return [json()]; } +/** + * Given a base url for a plugin, find the given OpenAPI spec for that plugin. + * @param baseUrl - Plugin base url. + * @returns OpenAPI spec route for the base url. + * @public + */ +export function getOpenApiSpecRoute(baseUrl: string) { + return `${baseUrl}${OPENAPI_SPEC_ROUTE}`; +} + /** * Create a new OpenAPI router with some default middleware. * @param spec - Your OpenAPI spec imported as a JSON object. @@ -59,7 +71,7 @@ export function createValidatedOpenApiRouter( middleware?: RequestHandler[]; }, ) { - const router = PromiseRouter() as ApiRouter; + const router = PromiseRouter(); router.use(options?.middleware || getDefaultRouterMiddleware()); /** @@ -116,5 +128,30 @@ export function createValidatedOpenApiRouter( // Any errors from the middleware get through here. router.use(validatorErrorTransformer()); - return router; + router.get(OPENAPI_SPEC_ROUTE, async (req, res) => { + const mergeOutput = merge([ + { + oas: spec as any, + pathModification: { + /** + * Get the route that this OpenAPI spec is hosted on. The other + * approach of using the discovery API increases the router constructor + * significantly and since we're just looking for path and not full URL, + * this works. + * + * If we wanted to add a list of servers, there may be a case for adding + * discovery API to get an exhaustive list of upstream servers, but that's + * also not currently supported. + */ + prepend: req.originalUrl.replace(OPENAPI_SPEC_ROUTE, ''), + }, + }, + ]); + if (isErrorResult(mergeOutput)) { + throw new InputError('Invalid spec defined'); + } + res.json(mergeOutput.output); + }); + + return router as ApiRouter; } diff --git a/plugins/catalog-backend-module-openapi-spec/.eslintrc.js b/plugins/catalog-backend-module-openapi-spec/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-openapi-spec/README.md b/plugins/catalog-backend-module-openapi-spec/README.md new file mode 100644 index 0000000000..6aebb037f2 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/README.md @@ -0,0 +1,30 @@ +# catalog-backend-module-openapi-spec + +## Summary + +This module installs an entity provider that exports a single entity, your Backstage instance documentation, which merges as many backend plugins as you have defined in the config value `openapi.plugins`. + +## Notes + +- This only works with the new backend system. +- This requires populating a new config value `openapi.plugins` with an array of plugin IDs. The entity processor expo + +## Installation + +To your new backend file, add + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend-module-openapi-spec')); +``` + +Add a list of plugins to your config like, + +```yaml title="app-config.yaml" +openapi: + plugins: + - catalog + - todo + - search +``` + +We will attempt to load each plugin's OpenAPI spec hosted at `${pluginRoute}/openapi.json`. These are automatically added if you are using `@backstage/backend-openapi-utils`'s `createValidatedOpenApiRouter`. diff --git a/plugins/catalog-backend-module-openapi-spec/api-report.md b/plugins/catalog-backend-module-openapi-spec/api-report.md new file mode 100644 index 0000000000..a3d1933b78 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/api-report.md @@ -0,0 +1,20 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-openapi-spec" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const catalogModuleInternalOpenApiSpec: () => BackendFeature; + +// @public (undocumented) +export type MetaApiDocsPluginOptions = { + exampleOption: boolean; +}; + +// @public (undocumented) +export const metaOpenApiDocsPluginId = 'meta-api-docs'; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/catalog-backend-module-openapi-spec/package.json b/plugins/catalog-backend-module-openapi-spec/package.json new file mode 100644 index 0000000000..ce6bbec95f --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/package.json @@ -0,0 +1,54 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-openapi-spec", + "version": "0.0.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-module" + }, + "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" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-openapi-utils": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/backend-tasks": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", + "@types/express": "*", + "cross-fetch": "^3.1.5", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "lodash": "^4.17.21", + "openapi-merge": "^1.3.2", + "uuid": "^9.0.0", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@types/supertest": "^2.0.8", + "msw": "^1.0.0", + "openapi3-ts": "^3.1.2", + "supertest": "^6.2.4" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts new file mode 100644 index 0000000000..c5b989dbc7 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts @@ -0,0 +1,244 @@ +/* + * 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 type { ApiEntity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { ForwardedError } from '@backstage/errors'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; +import { merge, isErrorResult } from 'openapi-merge'; +import { getOpenApiSpecRoute } from '@backstage/backend-openapi-utils'; +import type { + OpenAPIObject, + OperationObject, + PathItemObject, +} from 'openapi3-ts'; +import fetch from 'cross-fetch'; +import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; +import * as uuid from 'uuid'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; + +const HTTP_VERBS: (keyof PathItemObject)[] = [ + 'get', + 'post', + 'put', + 'delete', + 'patch', + 'trace', + 'options', + 'head', +]; + +const addTagsToSpec = (spec: OpenAPIObject, tag: string) => { + Object.values(spec?.paths).forEach((path: PathItemObject) => { + HTTP_VERBS.forEach(verb => { + if (verb in path) { + if (!('tags' in path[verb])) { + (path[verb] as OperationObject).tags = []; + } + if (!(path[verb] as OperationObject).tags?.includes(tag)) { + (path[verb] as OperationObject).tags?.push(tag); + } + } + }); + }); +}; + +const mergeSpecs = async ({ + baseUrl, + specs, +}: { + baseUrl: string; + specs: OpenAPIObject[]; +}) => { + const mergeResult = merge([ + // Add the full API information as the first item for other items to merge against it with. + { + oas: { + openapi: '3.0.3', + info: { + title: 'Backstage API', + version: '1', + }, + servers: [{ url: baseUrl }], + paths: {}, + }, + }, + // For each plugin, load its spec and the known endpoint that it sits under. + ...specs.map( + spec => + ({ + oas: spec, + // Weird typing differences between this package and the client package's openapi 3. + } as any), + ), + ]); + + if (isErrorResult(mergeResult)) { + throw new ForwardedError( + `${mergeResult.message} (${mergeResult.type})`, + mergeResult, + ); + } else { + return mergeResult.output; + } +}; + +const loadSpecs = async ({ + baseUrl, + discovery, + plugins, + logger, +}: { + baseUrl: string; + plugins: string[]; + discovery: DiscoveryService; + logger: LoggerService; +}) => { + const specs: OpenAPIObject[] = []; + for (const pluginId of plugins) { + const url = await discovery.getExternalBaseUrl(pluginId); + const openApiUrl = getOpenApiSpecRoute(url); + const response = await fetch(openApiUrl); + if (response.ok) { + const spec = await response.json(); + addTagsToSpec(spec, pluginId); + specs.push(spec); + } else if (response.status === 404) { + logger.error( + `Plugin=${pluginId} does not have an OpenAPI spec at '${openApiUrl}'.`, + ); + } else { + logger.error( + `Failed to load spec for plugin=${pluginId} at ${openApiUrl}. Error (${ + response.status + }): ${response.body ? await response.text() : response.statusText}`, + ); + } + } + return mergeSpecs({ baseUrl, specs }); +}; + +export class InternalOpenApiDocumentationProvider implements EntityProvider { + private connection?: EntityProviderConnection; + private readonly scheduleFn: () => Promise; + constructor( + public readonly config: Config, + public readonly discovery: DiscoveryService, + public readonly logger: LoggerService, + taskRunner: TaskRunner, + ) { + this.scheduleFn = this.createScheduleFn(taskRunner); + } + + static fromConfig( + config: Config, + options: { + discovery: DiscoveryService; + logger: LoggerService; + schedule: PluginTaskScheduler; + }, + ) { + const taskRunner = options.schedule.createScheduledTaskRunner({ + frequency: { + minutes: 1, + }, + timeout: { + minutes: 1, + }, + }); + return new InternalOpenApiDocumentationProvider( + config, + options.discovery, + options.logger, + taskRunner, + ); + } + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ + getProviderName() { + return `InternalOpenApiDocumentationProvider`; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ + async connect(connection: EntityProviderConnection) { + this.connection = connection; + return await this.scheduleFn(); + } + + private createScheduleFn(taskRunner: TaskRunner): () => Promise { + return async () => { + const taskId = `${this.getProviderName()}:refresh`; + return taskRunner.run({ + id: taskId, + fn: async () => { + const logger = this.logger.child({ + class: + InternalOpenApiDocumentationProvider.prototype.constructor.name, + taskId, + taskInstanceId: uuid.v4(), + }); + try { + await this.refresh(logger); + } catch (error) { + logger.error(`${this.getProviderName()} refresh failed`, error); + } + }, + }); + }; + } + + async refresh(logger: LoggerService) { + const pluginsToMerge = this.config.getStringArray('openapi.plugins'); + logger.info(`Loading specs from from ${pluginsToMerge}.`); + const documentationEntity: ApiEntity = { + apiVersion: 'backstage.io/v1beta1', + kind: 'API', + metadata: { + name: 'INTERNAL_instance_openapi_doc', + title: 'Your Backstage Instance documentation', + annotations: { + 'backstage.io/managed-by-location': + 'internal-package:@backstage/plugin-catalog-backend-module-openapi-spec', + 'backstage.io/managed-by-origin-location': + 'internal-package:@backstage/plugin-catalog-backend-module-openapi-spec', + }, + }, + spec: { + type: 'openapi', + lifecycle: 'production', + owner: 'backstage', + definition: JSON.stringify( + await loadSpecs({ + baseUrl: this.config.getString('backend.baseUrl'), + discovery: this.discovery, + plugins: pluginsToMerge, + logger, + }), + ), + }, + }; + await this.connection?.applyMutation({ + type: 'full', + entities: [ + { + entity: documentationEntity, + locationKey: 'internal-api-doc', + }, + ], + }); + } +} diff --git a/plugins/catalog-backend-module-openapi-spec/src/index.ts b/plugins/catalog-backend-module-openapi-spec/src/index.ts new file mode 100644 index 0000000000..9cc0cab6d3 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/src/index.ts @@ -0,0 +1,62 @@ +/* + * 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { InternalOpenApiDocumentationProvider } from './InternalOpenApiDocumentationProvider'; + +/** + * @public + */ +export type MetaApiDocsPluginOptions = { exampleOption: boolean }; + +/** + * @public + */ +export const metaOpenApiDocsPluginId = 'meta-api-docs'; + +/** + * @public + */ +export const catalogModuleInternalOpenApiSpec = createBackendModule({ + moduleId: metaOpenApiDocsPluginId, + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + discovery: coreServices.discovery, + scheduler: coreServices.scheduler, + logger: coreServices.logger, + }, + async init({ catalog, config, discovery, scheduler, logger }) { + console.log('testing'); + catalog.addEntityProvider( + InternalOpenApiDocumentationProvider.fromConfig(config, { + discovery, + schedule: scheduler, + logger, + }), + ); + }, + }); + }, +}); + +export default catalogModuleInternalOpenApiSpec; diff --git a/plugins/catalog-backend-module-openapi-spec/src/setupTests.ts b/plugins/catalog-backend-module-openapi-spec/src/setupTests.ts new file mode 100644 index 0000000000..4b9026cde5 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/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 013950f540..11ab2b969f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3544,7 +3544,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/backend-openapi-utils@workspace:packages/backend-openapi-utils" dependencies: + "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 @@ -3553,6 +3555,7 @@ __metadata: express-promise-router: ^4.1.0 json-schema-to-ts: ^2.6.2 lodash: ^4.17.21 + openapi-merge: ^1.3.2 openapi3-ts: ^3.1.2 supertest: ^6.1.3 languageName: unknown @@ -5633,7 +5636,36 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-backend-module-openapi@workspace:plugins/catalog-backend-module-openapi": +"@backstage/plugin-catalog-backend-module-openapi-spec@workspace:^, @backstage/plugin-catalog-backend-module-openapi-spec@workspace:plugins/catalog-backend-module-openapi-spec": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-openapi-spec@workspace:plugins/catalog-backend-module-openapi-spec" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-openapi-utils": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@types/express": "*" + "@types/supertest": ^2.0.8 + cross-fetch: ^3.1.5 + express: ^4.17.1 + express-promise-router: ^4.1.0 + lodash: ^4.17.21 + msw: ^1.0.0 + openapi-merge: ^1.3.2 + openapi3-ts: ^3.1.2 + supertest: ^6.2.4 + uuid: ^9.0.0 + yn: ^4.0.0 + languageName: unknown + linkType: soft + +"@backstage/plugin-catalog-backend-module-openapi@workspace:^, @backstage/plugin-catalog-backend-module-openapi@workspace:plugins/catalog-backend-module-openapi": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-openapi@workspace:plugins/catalog-backend-module-openapi" dependencies: @@ -20154,6 +20186,16 @@ __metadata: languageName: node linkType: hard +"atlassian-openapi@npm:^1.0.8": + version: 1.0.17 + resolution: "atlassian-openapi@npm:1.0.17" + dependencies: + jsonpointer: ^5.0.0 + urijs: ^1.19.10 + checksum: 372a454e8f5e000e016e261b2151019f0b3dabfad908593c71aae23ebd5b9998c374ae3c0bf0d85dd55dbcca94d5d93e38edf02346d0bd2cb34d3fd20968ddaf + languageName: node + linkType: hard + "atomic-sleep@npm:^1.0.0": version: 1.0.0 resolution: "atomic-sleep@npm:1.0.0" @@ -25645,6 +25687,8 @@ __metadata: "@backstage/plugin-azure-devops-backend": "workspace:^" "@backstage/plugin-badges-backend": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-backend-module-openapi": "workspace:^" + "@backstage/plugin-catalog-backend-module-openapi-spec": "workspace:^" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^" "@backstage/plugin-devtools-backend": "workspace:^" @@ -34240,6 +34284,17 @@ __metadata: languageName: node linkType: hard +"openapi-merge@npm:^1.3.2": + version: 1.3.2 + resolution: "openapi-merge@npm:1.3.2" + dependencies: + atlassian-openapi: ^1.0.8 + lodash: ^4.17.15 + ts-is-present: ^1.1.1 + checksum: 53284a563270177422db8c7536544913c133dfc5cc7058a1043f3092b5aa997b8224a83c59569d18620f94ccf0a014fcb735e22941a9259b2c60861002f01638 + languageName: node + linkType: hard + "openapi-sampler@npm:^1.2.1": version: 1.2.1 resolution: "openapi-sampler@npm:1.2.1" @@ -40922,6 +40977,13 @@ __metadata: languageName: node linkType: hard +"ts-is-present@npm:^1.1.1": + version: 1.2.2 + resolution: "ts-is-present@npm:1.2.2" + checksum: 3620ecf48219d0dd108e493260a207f4733d8e39a18dffec23c7ed2b1ef2aba7158d0dfafe36f3f27d0092472535a5e474ce04ade54e972e64b2b6329d20ab0b + languageName: node + linkType: hard + "ts-log@npm:^2.2.3": version: 2.2.5 resolution: "ts-log@npm:2.2.5" @@ -41674,7 +41736,7 @@ __metadata: languageName: node linkType: hard -"urijs@npm:^1.19.11": +"urijs@npm:^1.19.10, urijs@npm:^1.19.11": version: 1.19.11 resolution: "urijs@npm:1.19.11" checksum: f9b95004560754d30fd7dbee44b47414d662dc9863f1cf5632a7c7983648df11d23c0be73b9b4f9554463b61d5b0a520b70df9e1ee963ebb4af02e6da2cc80f3 From 51bd64ee5e5fbbee635a5eb4bb98c82f0cb0fc21 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Mon, 11 Sep 2023 11:42:15 -0400 Subject: [PATCH 02/12] fix api report Signed-off-by: Aramis Sennyey --- packages/backend-openapi-utils/config.d.ts | 23 +++++++++++++++++++ packages/backend-openapi-utils/package.json | 4 +++- .../api-report.md | 4 +++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 packages/backend-openapi-utils/config.d.ts diff --git a/packages/backend-openapi-utils/config.d.ts b/packages/backend-openapi-utils/config.d.ts new file mode 100644 index 0000000000..7256dc55e0 --- /dev/null +++ b/packages/backend-openapi-utils/config.d.ts @@ -0,0 +1,23 @@ +/* + * 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 interface Config { + openapi?: { + /** + * A list of plugins, whose OpenAPI specs you want to collate in `InternalOpenApiDocumentationProvider`. + */ + plugins: string[]; + }; +} diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 15957dd4ac..b7be1acf3c 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -34,8 +34,10 @@ "supertest": "^6.1.3" }, "files": [ - "dist" + "dist", + "config.d.ts" ], + "configSchema": "config.d.ts", "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-openapi-spec/api-report.md b/plugins/catalog-backend-module-openapi-spec/api-report.md index a3d1933b78..62b4c7194e 100644 --- a/plugins/catalog-backend-module-openapi-spec/api-report.md +++ b/plugins/catalog-backend-module-openapi-spec/api-report.md @@ -6,7 +6,9 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @public (undocumented) -export const catalogModuleInternalOpenApiSpec: () => BackendFeature; +const catalogModuleInternalOpenApiSpec: () => BackendFeature; +export { catalogModuleInternalOpenApiSpec }; +export default catalogModuleInternalOpenApiSpec; // @public (undocumented) export type MetaApiDocsPluginOptions = { From d7f87cd15cd10d373b074ba5a9fdda6d59f879d1 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Mon, 18 Sep 2023 19:19:54 -0400 Subject: [PATCH 03/12] add a catalog-info file for the new module Signed-off-by: Aramis Sennyey --- app-config.yaml | 1 - .../catalog-info.yaml | 9 +++++++++ plugins/catalog-backend-module-openapi-spec/src/index.ts | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 plugins/catalog-backend-module-openapi-spec/catalog-info.yaml diff --git a/app-config.yaml b/app-config.yaml index e7b0a07483..0ae2a4aa6e 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -50,7 +50,6 @@ backend: allow: - host: example.com - host: '*.mozilla.org' - - host: localhost:7007 # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir # See README.md in the proxy-backend plugin for information on the configuration format diff --git a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml new file mode 100644 index 0000000000..6aa1223aa0 --- /dev/null +++ b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-openapi-spec + title: '@backstage/plugin-catalog-backend-module-openapi-spec' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/catalog-backend-module-openapi-spec/src/index.ts b/plugins/catalog-backend-module-openapi-spec/src/index.ts index 9cc0cab6d3..226442666c 100644 --- a/plugins/catalog-backend-module-openapi-spec/src/index.ts +++ b/plugins/catalog-backend-module-openapi-spec/src/index.ts @@ -46,7 +46,6 @@ export const catalogModuleInternalOpenApiSpec = createBackendModule({ logger: coreServices.logger, }, async init({ catalog, config, discovery, scheduler, logger }) { - console.log('testing'); catalog.addEntityProvider( InternalOpenApiDocumentationProvider.fromConfig(config, { discovery, From 63b8e90cb6475fc1c1eab6dcb1f1216243dcefdd Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Thu, 5 Oct 2023 16:58:56 -0400 Subject: [PATCH 04/12] update notes Signed-off-by: Aramis Sennyey --- plugins/catalog-backend-module-openapi-spec/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-openapi-spec/README.md b/plugins/catalog-backend-module-openapi-spec/README.md index 6aebb037f2..aa0503229f 100644 --- a/plugins/catalog-backend-module-openapi-spec/README.md +++ b/plugins/catalog-backend-module-openapi-spec/README.md @@ -6,8 +6,7 @@ This module installs an entity provider that exports a single entity, your Backs ## Notes -- This only works with the new backend system. -- This requires populating a new config value `openapi.plugins` with an array of plugin IDs. The entity processor expo +- **This only works with the new backend system.** ## Installation From 1a8c9c732b6bb2d6f00442e9bf8e9b1d7cdeb870 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Fri, 6 Oct 2023 09:28:14 -0400 Subject: [PATCH 05/12] adjust ownership of the new package Signed-off-by: Aramis Sennyey --- .github/CODEOWNERS | 1 + plugins/catalog-backend-module-openapi-spec/catalog-info.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e788a6fe95..b5df047e8a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,6 +29,7 @@ yarn.lock @backstage/maintainers @backst /plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann +/plugins/catalog-backend-module-openapi-spec @backstage/maintainers @backstage/openapi-tooling-maintainers /plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers diff --git a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml index 6aa1223aa0..53a136a5f9 100644 --- a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml +++ b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: openapi-tooling-maintainers From b0562a18b45466e41a1369867d86da973e017a63 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Fri, 6 Oct 2023 14:53:22 -0400 Subject: [PATCH 06/12] add backstage/reviewers to codeowners Signed-off-by: Aramis Sennyey --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b5df047e8a..8807862a20 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,7 +29,7 @@ yarn.lock @backstage/maintainers @backst /plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-openapi-spec @backstage/maintainers @backstage/openapi-tooling-maintainers +/plugins/catalog-backend-module-openapi-spec @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers From c8f6d17da964cef4ce4e607b5e7c90c603b3eb7e Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Mon, 9 Oct 2023 12:21:18 -0400 Subject: [PATCH 07/12] fix prettier Signed-off-by: Aramis Sennyey --- plugins/catalog-backend-module-openapi-spec/catalog-info.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml index 53a136a5f9..89bb18d2c3 100644 --- a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml +++ b/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: openapi-tooling-maintainers + owner: openapi-tooling-maintainers From 99af3023c28e4e275256b5fce50174b6297e5b9c Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Wed, 11 Oct 2023 16:05:08 -0400 Subject: [PATCH 08/12] update changeset to minor Signed-off-by: Aramis Sennyey --- .changeset/thirty-stingrays-grin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/thirty-stingrays-grin.md b/.changeset/thirty-stingrays-grin.md index 43aaf2c606..6dcb4bbd50 100644 --- a/.changeset/thirty-stingrays-grin.md +++ b/.changeset/thirty-stingrays-grin.md @@ -1,5 +1,5 @@ --- -'@backstage/backend-openapi-utils': patch +'@backstage/backend-openapi-utils': minor --- Adds a new route, `/openapi.json` to validated routers for displaying their full OpenAPI spec in a standard endpoint. From d373db2adffe750277ac6fca426a8e6b4c0486e5 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Wed, 11 Oct 2023 16:19:33 -0400 Subject: [PATCH 09/12] adjust to minor as well Signed-off-by: Aramis Sennyey --- .changeset/gentle-pears-camp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/gentle-pears-camp.md b/.changeset/gentle-pears-camp.md index 1a9f8768f8..6455d1e7fb 100644 --- a/.changeset/gentle-pears-camp.md +++ b/.changeset/gentle-pears-camp.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-openapi-spec': patch +'@backstage/plugin-catalog-backend-module-openapi-spec': minor --- Adds a new catalog module for ingesting Backstage plugin OpenAPI specs into the catalog for display as an API entity. From d2059339b13c4983ab270523d78fa0faa0d5a519 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Mon, 23 Oct 2023 14:03:58 -0400 Subject: [PATCH 10/12] update based on PR feedback Signed-off-by: Aramis Sennyey --- .changeset/gentle-pears-camp.md | 2 +- .github/CODEOWNERS | 2 +- app-config.yaml | 12 ++-- packages/backend-next/package.json | 2 +- packages/backend-next/src/index.ts | 4 +- packages/backend-openapi-utils/api-report.md | 3 - packages/backend-openapi-utils/src/index.ts | 1 - .../.eslintrc.js | 0 .../README.md | 20 ++++--- .../api-report.md | 2 +- .../catalog-info.yaml | 4 +- .../package.json | 5 +- .../InternalOpenApiDocumentationProvider.ts | 18 ++++-- .../src/index.ts | 0 .../src/setupTests.ts | 0 yarn.lock | 60 +++++++++---------- 16 files changed, 71 insertions(+), 64 deletions(-) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/.eslintrc.js (100%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/README.md (64%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/api-report.md (97%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/catalog-info.yaml (55%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/package.json (94%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/src/InternalOpenApiDocumentationProvider.ts (95%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/src/index.ts (100%) rename plugins/{catalog-backend-module-openapi-spec => catalog-backend-module-backstage-openapi}/src/setupTests.ts (100%) diff --git a/.changeset/gentle-pears-camp.md b/.changeset/gentle-pears-camp.md index 6455d1e7fb..6d96958a76 100644 --- a/.changeset/gentle-pears-camp.md +++ b/.changeset/gentle-pears-camp.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-openapi-spec': minor +'@backstage/plugin-catalog-backend-module-backstage-openapi': minor --- Adds a new catalog module for ingesting Backstage plugin OpenAPI specs into the catalog for display as an API entity. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8807862a20..ec137d0747 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,7 +29,7 @@ yarn.lock @backstage/maintainers @backst /plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-openapi-spec @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers +/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers diff --git a/app-config.yaml b/app-config.yaml index 0ae2a4aa6e..c53a6475f4 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -235,6 +235,12 @@ catalog: - System - Domain - Location + providers: + openapi: + plugins: + - catalog + - search + - todo processors: ldapOrg: @@ -464,9 +470,3 @@ stackstorm: permission: enabled: true - -openapi: - plugins: - - catalog - - search - - todo diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 38245de00e..469ca622ef 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -34,8 +34,8 @@ "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^", "@backstage/plugin-catalog-backend-module-openapi": "workspace:^", - "@backstage/plugin-catalog-backend-module-openapi-spec": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^", "@backstage/plugin-devtools-backend": "workspace:^", diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index d5793dc0fb..d98661de63 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -43,7 +43,9 @@ backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); -backend.add(import('@backstage/plugin-catalog-backend-module-openapi-spec')); +backend.add( + import('@backstage/plugin-catalog-backend-module-backstage-openapi'), +); backend.add(import('@backstage/plugin-search-backend/alpha')); backend.add(import('@backstage/plugin-techdocs-backend/alpha')); backend.add(import('@backstage/plugin-todo-backend')); diff --git a/packages/backend-openapi-utils/api-report.md b/packages/backend-openapi-utils/api-report.md index cc6d31ac71..553eaac697 100644 --- a/packages/backend-openapi-utils/api-report.md +++ b/packages/backend-openapi-utils/api-report.md @@ -445,9 +445,6 @@ type ObjectWithContentSchema< ? SchemaRef : never; -// @public -export const OPENAPI_SPEC_ROUTE = '/openapi.json'; - // @public (undocumented) type OptionalMap< T extends { diff --git a/packages/backend-openapi-utils/src/index.ts b/packages/backend-openapi-utils/src/index.ts index 528358132c..e8e18ee15e 100644 --- a/packages/backend-openapi-utils/src/index.ts +++ b/packages/backend-openapi-utils/src/index.ts @@ -32,4 +32,3 @@ export type { } from './utility'; export type { ApiRouter } from './router'; export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub'; -export * from './constants'; diff --git a/plugins/catalog-backend-module-openapi-spec/.eslintrc.js b/plugins/catalog-backend-module-backstage-openapi/.eslintrc.js similarity index 100% rename from plugins/catalog-backend-module-openapi-spec/.eslintrc.js rename to plugins/catalog-backend-module-backstage-openapi/.eslintrc.js diff --git a/plugins/catalog-backend-module-openapi-spec/README.md b/plugins/catalog-backend-module-backstage-openapi/README.md similarity index 64% rename from plugins/catalog-backend-module-openapi-spec/README.md rename to plugins/catalog-backend-module-backstage-openapi/README.md index aa0503229f..a7f4727669 100644 --- a/plugins/catalog-backend-module-openapi-spec/README.md +++ b/plugins/catalog-backend-module-backstage-openapi/README.md @@ -1,8 +1,8 @@ -# catalog-backend-module-openapi-spec +# catalog-backend-module-backstage-openapi ## Summary -This module installs an entity provider that exports a single entity, your Backstage instance documentation, which merges as many backend plugins as you have defined in the config value `openapi.plugins`. +This module installs an entity provider that exports a single entity, your Backstage instance documentation, which merges as many backend plugins as you have defined in the config value `catalog.providers.openapi.plugins`. ## Notes @@ -13,17 +13,21 @@ This module installs an entity provider that exports a single entity, your Backs To your new backend file, add ```ts title="packages/backend/src/index.ts" -backend.add(import('@backstage/plugin-catalog-backend-module-openapi-spec')); +backend.add( + import('@backstage/plugin-catalog-backend-module-backstage-openapi'), +); ``` Add a list of plugins to your config like, ```yaml title="app-config.yaml" -openapi: - plugins: - - catalog - - todo - - search +catalog: + providers: + openapi: + plugins: + - catalog + - todo + - search ``` We will attempt to load each plugin's OpenAPI spec hosted at `${pluginRoute}/openapi.json`. These are automatically added if you are using `@backstage/backend-openapi-utils`'s `createValidatedOpenApiRouter`. diff --git a/plugins/catalog-backend-module-openapi-spec/api-report.md b/plugins/catalog-backend-module-backstage-openapi/api-report.md similarity index 97% rename from plugins/catalog-backend-module-openapi-spec/api-report.md rename to plugins/catalog-backend-module-backstage-openapi/api-report.md index 62b4c7194e..d417be049c 100644 --- a/plugins/catalog-backend-module-openapi-spec/api-report.md +++ b/plugins/catalog-backend-module-backstage-openapi/api-report.md @@ -1,4 +1,4 @@ -## API Report File for "@backstage/plugin-catalog-backend-module-openapi-spec" +## API Report File for "@backstage/plugin-catalog-backend-module-backstage-openapi" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml b/plugins/catalog-backend-module-backstage-openapi/catalog-info.yaml similarity index 55% rename from plugins/catalog-backend-module-openapi-spec/catalog-info.yaml rename to plugins/catalog-backend-module-backstage-openapi/catalog-info.yaml index 89bb18d2c3..5e98f11506 100644 --- a/plugins/catalog-backend-module-openapi-spec/catalog-info.yaml +++ b/plugins/catalog-backend-module-backstage-openapi/catalog-info.yaml @@ -1,8 +1,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: backstage-plugin-catalog-backend-module-openapi-spec - title: '@backstage/plugin-catalog-backend-module-openapi-spec' + name: backstage-plugin-catalog-backend-module-backstage-openapi + title: '@backstage/plugin-catalog-backend-module-backstage-openapi' spec: lifecycle: experimental type: backstage-backend-plugin-module diff --git a/plugins/catalog-backend-module-openapi-spec/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json similarity index 94% rename from plugins/catalog-backend-module-openapi-spec/package.json rename to plugins/catalog-backend-module-backstage-openapi/package.json index ce6bbec95f..0f76bc3234 100644 --- a/plugins/catalog-backend-module-openapi-spec/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,10 +1,9 @@ { - "name": "@backstage/plugin-catalog-backend-module-openapi-spec", + "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", "version": "0.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", @@ -27,6 +26,7 @@ "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", + "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", @@ -40,7 +40,6 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/catalog-model": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", "@types/supertest": "^2.0.8", diff --git a/plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts similarity index 95% rename from plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts rename to plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index c5b989dbc7..7700d3081b 100644 --- a/plugins/catalog-backend-module-openapi-spec/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { ApiEntity } from '@backstage/catalog-model'; +import { + ANNOTATION_LOCATION, + ANNOTATION_ORIGIN_LOCATION, + type ApiEntity, +} from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; import { @@ -202,7 +206,9 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { } async refresh(logger: LoggerService) { - const pluginsToMerge = this.config.getStringArray('openapi.plugins'); + const pluginsToMerge = this.config.getStringArray( + 'catalog.providers.openapi.plugins', + ); logger.info(`Loading specs from from ${pluginsToMerge}.`); const documentationEntity: ApiEntity = { apiVersion: 'backstage.io/v1beta1', @@ -211,10 +217,10 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { name: 'INTERNAL_instance_openapi_doc', title: 'Your Backstage Instance documentation', annotations: { - 'backstage.io/managed-by-location': - 'internal-package:@backstage/plugin-catalog-backend-module-openapi-spec', - 'backstage.io/managed-by-origin-location': - 'internal-package:@backstage/plugin-catalog-backend-module-openapi-spec', + [ANNOTATION_LOCATION]: + 'internal-package:@backstage/plugin-catalog-backend-module-backstage-openapi', + [ANNOTATION_ORIGIN_LOCATION]: + 'internal-package:@backstage/plugin-catalog-backend-module-backstage-openapi', }, }, spec: { diff --git a/plugins/catalog-backend-module-openapi-spec/src/index.ts b/plugins/catalog-backend-module-backstage-openapi/src/index.ts similarity index 100% rename from plugins/catalog-backend-module-openapi-spec/src/index.ts rename to plugins/catalog-backend-module-backstage-openapi/src/index.ts diff --git a/plugins/catalog-backend-module-openapi-spec/src/setupTests.ts b/plugins/catalog-backend-module-backstage-openapi/src/setupTests.ts similarity index 100% rename from plugins/catalog-backend-module-openapi-spec/src/setupTests.ts rename to plugins/catalog-backend-module-backstage-openapi/src/setupTests.ts diff --git a/yarn.lock b/yarn.lock index 11ab2b969f..0114aea96d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5388,6 +5388,35 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-catalog-backend-module-backstage-openapi@workspace:^, @backstage/plugin-catalog-backend-module-backstage-openapi@workspace:plugins/catalog-backend-module-backstage-openapi": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-backstage-openapi@workspace:plugins/catalog-backend-module-backstage-openapi" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-openapi-utils": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@types/express": "*" + "@types/supertest": ^2.0.8 + cross-fetch: ^3.1.5 + express: ^4.17.1 + express-promise-router: ^4.1.0 + lodash: ^4.17.21 + msw: ^1.0.0 + openapi-merge: ^1.3.2 + openapi3-ts: ^3.1.2 + supertest: ^6.2.4 + uuid: ^9.0.0 + yn: ^4.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-catalog-backend-module-bitbucket-cloud@workspace:plugins/catalog-backend-module-bitbucket-cloud": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-bitbucket-cloud@workspace:plugins/catalog-backend-module-bitbucket-cloud" @@ -5636,35 +5665,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-backend-module-openapi-spec@workspace:^, @backstage/plugin-catalog-backend-module-openapi-spec@workspace:plugins/catalog-backend-module-openapi-spec": - version: 0.0.0-use.local - resolution: "@backstage/plugin-catalog-backend-module-openapi-spec@workspace:plugins/catalog-backend-module-openapi-spec" - dependencies: - "@backstage/backend-common": "workspace:^" - "@backstage/backend-openapi-utils": "workspace:^" - "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" - "@backstage/catalog-model": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/config": "workspace:^" - "@backstage/errors": "workspace:^" - "@backstage/plugin-catalog-node": "workspace:^" - "@backstage/test-utils": "workspace:^" - "@types/express": "*" - "@types/supertest": ^2.0.8 - cross-fetch: ^3.1.5 - express: ^4.17.1 - express-promise-router: ^4.1.0 - lodash: ^4.17.21 - msw: ^1.0.0 - openapi-merge: ^1.3.2 - openapi3-ts: ^3.1.2 - supertest: ^6.2.4 - uuid: ^9.0.0 - yn: ^4.0.0 - languageName: unknown - linkType: soft - "@backstage/plugin-catalog-backend-module-openapi@workspace:^, @backstage/plugin-catalog-backend-module-openapi@workspace:plugins/catalog-backend-module-openapi": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-openapi@workspace:plugins/catalog-backend-module-openapi" @@ -25687,8 +25687,8 @@ __metadata: "@backstage/plugin-azure-devops-backend": "workspace:^" "@backstage/plugin-badges-backend": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^" "@backstage/plugin-catalog-backend-module-openapi": "workspace:^" - "@backstage/plugin-catalog-backend-module-openapi-spec": "workspace:^" "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^" "@backstage/plugin-devtools-backend": "workspace:^" From 28fa763ba0629ef3cd2143c8d765fcb75d41e990 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Mon, 23 Oct 2023 15:02:29 -0400 Subject: [PATCH 11/12] update to a dev dependency Signed-off-by: Aramis Sennyey --- plugins/catalog-backend-module-backstage-openapi/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 0f76bc3234..d603e22f8a 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -30,7 +30,6 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", - "@types/express": "*", "cross-fetch": "^3.1.5", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -42,6 +41,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", + "@types/express": "*", "@types/supertest": "^2.0.8", "msw": "^1.0.0", "openapi3-ts": "^3.1.2", From f981d5edcfe9213674e9450814ad832828399ccc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Oct 2023 10:50:08 +0200 Subject: [PATCH 12/12] catalog-backend-module-backstage-openapi: move over and tweak config schema Signed-off-by: Patrik Oldsberg --- packages/backend-openapi-utils/package.json | 4 +--- .../config.d.ts | 18 +++++++++++++----- .../package.json | 4 +++- .../InternalOpenApiDocumentationProvider.ts | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) rename {packages/backend-openapi-utils => plugins/catalog-backend-module-backstage-openapi}/config.d.ts (67%) diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index b7be1acf3c..15957dd4ac 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -34,10 +34,8 @@ "supertest": "^6.1.3" }, "files": [ - "dist", - "config.d.ts" + "dist" ], - "configSchema": "config.d.ts", "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-openapi-utils/config.d.ts b/plugins/catalog-backend-module-backstage-openapi/config.d.ts similarity index 67% rename from packages/backend-openapi-utils/config.d.ts rename to plugins/catalog-backend-module-backstage-openapi/config.d.ts index 7256dc55e0..ce51640495 100644 --- a/packages/backend-openapi-utils/config.d.ts +++ b/plugins/catalog-backend-module-backstage-openapi/config.d.ts @@ -13,11 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export interface Config { - openapi?: { - /** - * A list of plugins, whose OpenAPI specs you want to collate in `InternalOpenApiDocumentationProvider`. - */ - plugins: string[]; + catalog?: { + providers?: { + /** + * BackstageOpenApiEntityProvider configuration + */ + backstageOpenapi?: { + /** + * A list of plugins, whose OpenAPI specs you want to collate in `InternalOpenApiDocumentationProvider`. + */ + plugins: string[]; + }; + }; }; } diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index d603e22f8a..05152fc99a 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -47,7 +47,9 @@ "openapi3-ts": "^3.1.2", "supertest": "^6.2.4" }, + "configSchema": "config.d.ts", "files": [ - "dist" + "dist", + "config.d.ts" ] } diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index 7700d3081b..33c461d95a 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -207,7 +207,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { async refresh(logger: LoggerService) { const pluginsToMerge = this.config.getStringArray( - 'catalog.providers.openapi.plugins', + 'catalog.providers.backstageOpenapi.plugins', ); logger.info(`Loading specs from from ${pluginsToMerge}.`); const documentationEntity: ApiEntity = {