From a17d9df2eeacca2f603601492638c508a48b8e4c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Wed, 3 Sep 2025 09:48:34 -0400 Subject: [PATCH] feat: promote instance metadata to stable Signed-off-by: aramissennyeydd --- .changeset/honest-pandas-win.md | 5 + .changeset/moody-plums-add.md | 5 + .changeset/open-items-open.md | 5 + packages/backend-app-api/package.json | 3 +- .../src/wiring/BackendInitializer.test.ts | 31 +++--- .../src/wiring/BackendInitializer.ts | 102 +++++++++--------- .../backend-plugin-api/src/alpha/index.ts | 11 +- packages/backend-plugin-api/src/alpha/refs.ts | 9 -- .../definitions}/InstanceMetadataService.ts | 19 ++-- .../src/services/definitions/coreServices.ts | 11 ++ .../src/services/definitions/index.ts | 4 + packages/backend/src/instanceMetadata.ts | 10 +- plugins/gateway-backend/src/plugin.ts | 3 +- plugins/gateway-backend/src/router.ts | 12 +-- yarn.lock | 1 + 15 files changed, 119 insertions(+), 112 deletions(-) create mode 100644 .changeset/honest-pandas-win.md create mode 100644 .changeset/moody-plums-add.md create mode 100644 .changeset/open-items-open.md rename packages/backend-plugin-api/src/{alpha => services/definitions}/InstanceMetadataService.ts (73%) diff --git a/.changeset/honest-pandas-win.md b/.changeset/honest-pandas-win.md new file mode 100644 index 0000000000..a9dc5d7e9d --- /dev/null +++ b/.changeset/honest-pandas-win.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Promote `instanceMetadata` service to main entrypoint. diff --git a/.changeset/moody-plums-add.md b/.changeset/moody-plums-add.md new file mode 100644 index 0000000000..a88e11b347 --- /dev/null +++ b/.changeset/moody-plums-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-gateway-backend': minor +--- + +Update usage of the `instanceMetadata` service. diff --git a/.changeset/open-items-open.md b/.changeset/open-items-open.md new file mode 100644 index 0000000000..eafce6bd60 --- /dev/null +++ b/.changeset/open-items-open.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Updates API for `instanceMetadata` service to return a list of plugins not features. Also adds an HTTP endpoint that returns information about the current instance. diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 1613e05cc0..0261845093 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -48,7 +48,8 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^" + "@backstage/errors": "workspace:^", + "express-promise-router": "^4.1.0" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 9e8a6579fd..312511f4a9 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -22,9 +22,9 @@ import { createExtensionPoint, createBackendFeatureLoader, ServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; import { mockServices } from '@backstage/backend-test-utils'; const baseFactories = [ @@ -32,6 +32,9 @@ const baseFactories = [ mockServices.lifecycle.factory(), mockServices.rootLogger.factory(), mockServices.logger.factory(), + mockServices.rootConfig.factory(), + mockServices.rootHttpRouter.mock().factory, + mockServices.rootHealth.factory(), ]; function mkNoopFactory(ref: ServiceRef<{}, 'plugin'>) { @@ -1074,7 +1077,7 @@ describe('BackendInitializer', () => { }); it('should properly add plugins + modules to the instance metadata service', async () => { - expect.assertions(2); + expect.assertions(1); const backend = new BackendInitializer(baseFactories); const plugin = createBackendPlugin({ pluginId: 'test', @@ -1090,31 +1093,23 @@ describe('BackendInitializer', () => { register(reg) { reg.registerInit({ deps: { - instanceMetadata: instanceMetadataServiceRef, + instanceMetadata: coreServices.instanceMetadata, }, async init({ instanceMetadata }) { - expect(instanceMetadata.getInstalledFeatures()).toEqual([ + expect(instanceMetadata.getInstalledPlugins()).toEqual([ { pluginId: 'test', - type: 'plugin', - }, - { - pluginId: 'test', - moduleId: 'test', - type: 'module', + modules: [ + { + moduleId: 'test', + }, + ], }, { pluginId: 'instance-metadata', - type: 'plugin', + modules: [], }, ]); - expect(instanceMetadata.getInstalledFeatures().map(String)).toEqual( - [ - 'plugin{pluginId=test}', - 'module{moduleId=test,pluginId=test}', - 'plugin{pluginId=instance-metadata}', - ], - ); }, }); }, diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 09b22e3a7a..75d5fd5a70 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -36,14 +36,13 @@ import type { // eslint-disable-next-line @backstage/no-relative-monorepo-imports import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types'; import { ForwardedError, ConflictError, assertError } from '@backstage/errors'; -import { - instanceMetadataServiceRef, - BackendFeatureMeta, -} from '@backstage/backend-plugin-api/alpha'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; import { createInitializationLogger } from './createInitializationLogger'; import { unwrapFeature } from './helpers'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import type { BackendPlugin } from '../../../backend-plugin-api/src/services/definitions/InstanceMetadataService'; +import Router from 'express-promise-router'; export interface BackendRegisterInit { consumes: Set; @@ -104,53 +103,58 @@ const instanceRegistry = new (class InstanceRegistry { function createInstanceMetadataServiceFactory( registrations: InternalBackendRegistrations[], ) { - const installedFeatures = registrations - .map(registration => { - if (registration.featureType === 'registrations') { - return registration - .getRegistrations() - .map(feature => { - if (feature.type === 'plugin') { - return Object.defineProperty( - { - type: 'plugin', - pluginId: feature.pluginId, - }, - 'toString', - { - enumerable: false, - configurable: true, - value: () => `plugin{pluginId=${feature.pluginId}}`, - }, - ); - } else if (feature.type === 'module') { - return Object.defineProperty( - { - type: 'module', - pluginId: feature.pluginId, - moduleId: feature.moduleId, - }, - 'toString', - { - enumerable: false, - configurable: true, - value: () => - `module{moduleId=${feature.moduleId},pluginId=${feature.pluginId}}`, - }, - ); - } - // Ignore unknown feature types. - return undefined; - }) - .filter(Boolean) as BackendFeatureMeta[]; + const installedPlugins: { [pluginId: string]: BackendPlugin } = {}; + for (const registration of registrations) { + if (registration.featureType === 'registrations') { + for (const feature of registration.getRegistrations()) { + if (feature.type === 'plugin') { + if (!installedPlugins[feature.pluginId]) { + installedPlugins[feature.pluginId] = { + pluginId: feature.pluginId, + modules: [], + }; + } + } else if (feature.type === 'module') { + if (!installedPlugins[feature.pluginId]) { + installedPlugins[feature.pluginId] = { + pluginId: feature.pluginId, + modules: [], + }; + } + installedPlugins[feature.pluginId].modules.push({ + moduleId: feature.moduleId, + }); + } } - return []; - }) - .flat(); + } + } return createServiceFactory({ - service: instanceMetadataServiceRef, - deps: {}, - factory: async () => ({ getInstalledFeatures: () => installedFeatures }), + service: coreServices.instanceMetadata, + deps: { + httpRouter: coreServices.rootHttpRouter, + logger: coreServices.rootLogger, + }, + factory: async ({ logger, httpRouter }) => { + const instanceMetadata = { + getInstalledPlugins: () => Object.values(installedPlugins), + }; + + logger.info( + `Installed plugins on this instance: ${instanceMetadata + .getInstalledPlugins() + .map(p => p.pluginId) + .join(', ')}`, + ); + + const router = Router(); + + router.get('/info', (_, res) => { + res.json({ plugins: Object.values(installedPlugins) }); + }); + + httpRouter.use('/.backstage/instanceMetadata/v1', router); + return instanceMetadata; + }, }); } diff --git a/packages/backend-plugin-api/src/alpha/index.ts b/packages/backend-plugin-api/src/alpha/index.ts index 5bb69eb4e2..b1edd68adc 100644 --- a/packages/backend-plugin-api/src/alpha/index.ts +++ b/packages/backend-plugin-api/src/alpha/index.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -export type { - BackendFeatureMeta, - InstanceMetadataService, -} from './InstanceMetadataService'; - export type { ActionsRegistryService, ActionsRegistryActionOptions, @@ -27,8 +22,4 @@ export type { export type { ActionsService, ActionsServiceAction } from './ActionsService'; -export { - actionsRegistryServiceRef, - actionsServiceRef, - instanceMetadataServiceRef, -} from './refs'; +export { actionsRegistryServiceRef, actionsServiceRef } from './refs'; diff --git a/packages/backend-plugin-api/src/alpha/refs.ts b/packages/backend-plugin-api/src/alpha/refs.ts index 81996679f7..cfbb215615 100644 --- a/packages/backend-plugin-api/src/alpha/refs.ts +++ b/packages/backend-plugin-api/src/alpha/refs.ts @@ -16,15 +16,6 @@ import { createServiceRef } from '@backstage/backend-plugin-api'; -/** - * @alpha - */ -export const instanceMetadataServiceRef = createServiceRef< - import('./InstanceMetadataService').InstanceMetadataService ->({ - id: 'core.instanceMetadata', -}); - /** * Service for calling distributed actions * diff --git a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts b/packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts similarity index 73% rename from packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts rename to packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts index 869d361343..2a42631ec7 100644 --- a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts +++ b/packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts @@ -14,19 +14,14 @@ * limitations under the License. */ -/** @alpha */ -export type BackendFeatureMeta = - | { - type: 'plugin'; - pluginId: string; - } - | { - type: 'module'; - pluginId: string; - moduleId: string; - }; +export interface BackendPlugin { + pluginId: string; + modules: { + moduleId: string; + }[]; +} /** @alpha */ export interface InstanceMetadataService { - getInstalledFeatures: () => BackendFeatureMeta[]; + getInstalledPlugins: () => readonly BackendPlugin[]; } diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 8c8c6c0f82..b8f4494f74 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -277,4 +277,15 @@ export namespace coreServices { export const urlReader = createServiceRef< import('./UrlReaderService').UrlReaderService >({ id: 'core.urlReader' }); + + /** + * Information about the current Backstage instance. + * + * @public + */ + export const instanceMetadata = createServiceRef< + import('./InstanceMetadataService').InstanceMetadataService + >({ + id: 'core.instanceMetadata', + }); } diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index c811a513a8..2c606bc370 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -85,4 +85,8 @@ export type { UrlReaderServiceSearchResponseFile, } from './UrlReaderService'; export type { BackstageUserInfo, UserInfoService } from './UserInfoService'; +export type { + InstanceMetadataService, + BackendPlugin, +} from './InstanceMetadataService'; export { coreServices } from './coreServices'; diff --git a/packages/backend/src/instanceMetadata.ts b/packages/backend/src/instanceMetadata.ts index 5f835f3117..463b2c317d 100644 --- a/packages/backend/src/instanceMetadata.ts +++ b/packages/backend/src/instanceMetadata.ts @@ -17,21 +17,21 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; -// Example usage of the instance metadata service to log the installed features. +// Example usage of the instance metadata service to log the installed plugins. export default createBackendPlugin({ pluginId: 'instance-metadata-logging', register(env) { env.registerInit({ deps: { - instanceMetadata: instanceMetadataServiceRef, + instanceMetadata: coreServices.instanceMetadata, logger: coreServices.logger, }, async init({ instanceMetadata, logger }) { logger.info( - `Installed features on this instance: ${instanceMetadata - .getInstalledFeatures() + `Installed plugins on this instance: ${instanceMetadata + .getInstalledPlugins() + .map(e => e.pluginId) .join(', ')}`, ); }, diff --git a/plugins/gateway-backend/src/plugin.ts b/plugins/gateway-backend/src/plugin.ts index 94b979d1bc..391efb6852 100644 --- a/plugins/gateway-backend/src/plugin.ts +++ b/plugins/gateway-backend/src/plugin.ts @@ -18,7 +18,6 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './router'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; import { Handler } from 'express'; /** @@ -33,7 +32,7 @@ export const gatewayPlugin = createBackendPlugin({ deps: { logger: coreServices.logger, rootHttpRouter: coreServices.rootHttpRouter, - instanceMeta: instanceMetadataServiceRef, + instanceMeta: coreServices.instanceMetadata, discovery: coreServices.discovery, }, async init({ logger, discovery, instanceMeta, rootHttpRouter }) { diff --git a/plugins/gateway-backend/src/router.ts b/plugins/gateway-backend/src/router.ts index 74b3c6e066..4b041ef06d 100644 --- a/plugins/gateway-backend/src/router.ts +++ b/plugins/gateway-backend/src/router.ts @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; -import { InstanceMetadataService } from '@backstage/backend-plugin-api/alpha'; +import { + DiscoveryService, + InstanceMetadataService, + LoggerService, +} from '@backstage/backend-plugin-api'; import { Request, Response, NextFunction } from 'express'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { context } from '@opentelemetry/api'; @@ -29,10 +32,7 @@ export function createRouter({ logger: LoggerService; }) { const localPluginIds = new Set( - instanceMeta - .getInstalledFeatures() - .filter(f => f.type === 'plugin') - .map(f => f.pluginId), + instanceMeta.getInstalledPlugins().map(f => f.pluginId), ); const proxy = createProxyMiddleware({ diff --git a/yarn.lock b/yarn.lock index 9c96dcf272..c4d760f119 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2518,6 +2518,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + express-promise-router: "npm:^4.1.0" languageName: unknown linkType: soft