address PR feedback
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
+6
-17
@@ -27,12 +27,9 @@ import {
|
||||
* @alpha
|
||||
*/
|
||||
export class DefaultSystemMetadataService implements SystemMetadataService {
|
||||
private readonly logger: LoggerService;
|
||||
private readonly config: RootConfigService;
|
||||
constructor(options: { logger: LoggerService; config: RootConfigService }) {
|
||||
this.logger = options.logger;
|
||||
this.config = options.config;
|
||||
}
|
||||
constructor(
|
||||
private options: { logger: LoggerService; config: RootConfigService },
|
||||
) {}
|
||||
|
||||
public static create(pluginEnv: {
|
||||
logger: LoggerService;
|
||||
@@ -41,24 +38,16 @@ export class DefaultSystemMetadataService implements SystemMetadataService {
|
||||
return new DefaultSystemMetadataService(pluginEnv);
|
||||
}
|
||||
|
||||
listInstances() {
|
||||
async listInstances() {
|
||||
const endpoints =
|
||||
this.config.getOptionalConfigArray('discovery.instances') ?? [];
|
||||
this.options.config.getOptionalConfigArray('discovery.instances') ?? [];
|
||||
const instances: BackstageInstance[] = [];
|
||||
for (const endpoint of endpoints) {
|
||||
const baseUrl = endpoint.getOptionalString('baseUrl');
|
||||
if (baseUrl) {
|
||||
this.logger.info(`Found instance at ${baseUrl}`);
|
||||
instances.push({ url: baseUrl });
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`Instance ${endpoint.get(
|
||||
'target',
|
||||
)} is missing a 'baseUrl' property. This is required for the system metadata service.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.logger.info(`Found ${instances.length} instances.`);
|
||||
return Promise.resolve(instances);
|
||||
return instances;
|
||||
}
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2025 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 { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
BackendFeatureMeta,
|
||||
SystemMetadataService,
|
||||
} from '@backstage/backend-plugin-api/alpha';
|
||||
import Router from 'express-promise-router';
|
||||
|
||||
export async function createSystemMetadataRouter(options: {
|
||||
logger: LoggerService;
|
||||
systemMetadata: SystemMetadataService;
|
||||
}) {
|
||||
const { logger, systemMetadata } = options;
|
||||
logger.info(
|
||||
`Instances in this system: ${JSON.stringify(
|
||||
await systemMetadata.listInstances(),
|
||||
)}`,
|
||||
);
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/instances', async (_, res) => {
|
||||
res.json(await systemMetadata.listInstances());
|
||||
});
|
||||
|
||||
router.get('/features/installed', async (_, res) => {
|
||||
const instances = await systemMetadata.listInstances();
|
||||
const featurePromises = await Promise.allSettled(
|
||||
instances.map(async instance => {
|
||||
const response = await fetch(
|
||||
`${instance.url}/.backstage/instanceMetadata/v1/features/installed`,
|
||||
);
|
||||
if (response.ok) {
|
||||
return { instance, response: await response.json() };
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to fetch installed features from ${instance.url}`,
|
||||
);
|
||||
}),
|
||||
);
|
||||
const pluginByInstance: Record<string, string[]> = {};
|
||||
for (const result of featurePromises) {
|
||||
if (result.status !== 'fulfilled') {
|
||||
logger.error(`Failed to fetch installed features: ${result.reason}`);
|
||||
continue;
|
||||
}
|
||||
const instance = result.value.instance;
|
||||
const installedFeatures = result.value.response
|
||||
.items as BackendFeatureMeta[];
|
||||
for (const feature of installedFeatures) {
|
||||
if (feature.type === 'plugin') {
|
||||
if (!pluginByInstance[feature.pluginId]) {
|
||||
pluginByInstance[feature.pluginId] = [];
|
||||
}
|
||||
pluginByInstance[feature.pluginId].push(instance.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.json(pluginByInstance);
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
+10
-3
@@ -20,6 +20,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DefaultSystemMetadataService } from './lib/DefaultSystemMetadataService';
|
||||
import { systemMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
import { createSystemMetadataRouter } from './lib/createSystemMetadataRouter';
|
||||
|
||||
/**
|
||||
* Metadata about an entire Backstage system, a collection of Backstage instances.
|
||||
@@ -29,13 +30,19 @@ import { systemMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
export const systemMetadataServiceFactory = createServiceFactory({
|
||||
service: systemMetadataServiceRef,
|
||||
deps: {
|
||||
logger: coreServices.logger,
|
||||
logger: coreServices.rootLogger,
|
||||
config: coreServices.rootConfig,
|
||||
httpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async factory({ logger, config }) {
|
||||
return DefaultSystemMetadataService.create({
|
||||
async factory({ logger, config, httpRouter }) {
|
||||
const systemMetadata = DefaultSystemMetadataService.create({
|
||||
logger,
|
||||
config,
|
||||
});
|
||||
|
||||
const router = await createSystemMetadataRouter({ systemMetadata, logger });
|
||||
|
||||
httpRouter.use('/.backstage/systemMetadata/v1', router);
|
||||
return systemMetadata;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ export const systemMetadataServiceRef = createServiceRef<
|
||||
import('./services/definitions/SystemMetadataService').SystemMetadataService
|
||||
>({
|
||||
id: 'core.systemMetadata',
|
||||
scope: 'root',
|
||||
});
|
||||
|
||||
export type {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Not working yet, the metadata plugin HTTP APIs need to be moved to rootHttpRouter.
|
||||
# GET http://localhost:7007/.backstage/systemInfo/features/installed
|
||||
// Test requests for the split backend to test the system metadata service :)
|
||||
// The 2 below requests should return the same data.
|
||||
GET http://localhost:7007/.backstage/systemMetadata/v1/features/installed
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:7008/.backstage/systemInfo/features/installed
|
||||
GET http://localhost:7008/.backstage/systemMetadata/v1/features/installed
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
// Example usage of the instance metadata service to log the installed plugins.
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'instance-metadata',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
instanceMetadata: coreServices.rootInstanceMetadata,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async init({ instanceMetadata, logger, httpRouter }) {
|
||||
logger.info(
|
||||
`Installed plugins on this instance: ${plugins
|
||||
.map(e => e.pluginId)
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/features/installed', (_, res) => {
|
||||
res.json({ items: instanceMetadata.getInstalledFeatures() });
|
||||
});
|
||||
|
||||
httpRouter.use('/.backstage/instanceInfo', router);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
BackendFeatureMeta,
|
||||
systemMetadataServiceRef,
|
||||
} from '@backstage/backend-plugin-api/alpha';
|
||||
import Router from 'express-promise-router';
|
||||
|
||||
// Example usage of the instance metadata service to log the list of instances and a small HTTP API.
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'system-metadata',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
systemMetadata: systemMetadataServiceRef,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async init({ systemMetadata, logger, httpRouter }) {
|
||||
logger.info(
|
||||
`Instances in this system: ${JSON.stringify(
|
||||
await systemMetadata.listInstances(),
|
||||
)}`,
|
||||
);
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/instances', async (_, res) => {
|
||||
res.json(await systemMetadata.listInstances());
|
||||
});
|
||||
|
||||
router.get('/features/installed', async (_, res) => {
|
||||
const instances = await systemMetadata.listInstances();
|
||||
const featurePromises = await Promise.allSettled(
|
||||
instances.map(async instance => {
|
||||
const response = await fetch(
|
||||
`${instance.url}/.backstage/instanceInfo/features/installed`,
|
||||
);
|
||||
if (response.ok) {
|
||||
return { instance, response: await response.json() };
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to fetch installed features from ${instance.url}`,
|
||||
);
|
||||
}),
|
||||
);
|
||||
const pluginByInstance: Record<string, string[]> = {};
|
||||
for (const result of featurePromises) {
|
||||
if (result.status !== 'fulfilled') {
|
||||
logger.error(
|
||||
`Failed to fetch installed features: ${result.reason}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const instance = result.value.instance;
|
||||
const installedFeatures = result.value.response
|
||||
.items as BackendFeatureMeta[];
|
||||
for (const feature of installedFeatures) {
|
||||
if (feature.type === 'plugin') {
|
||||
if (!pluginByInstance[feature.pluginId]) {
|
||||
pluginByInstance[feature.pluginId] = [];
|
||||
}
|
||||
pluginByInstance[feature.pluginId].push(
|
||||
`${instance.url}/api/${feature.pluginId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.json(pluginByInstance);
|
||||
});
|
||||
|
||||
httpRouter.use('/.backstage/systemInfo', router);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -30,8 +30,6 @@ backend.add(
|
||||
);
|
||||
backend.add(import('@backstage/plugin-permission-backend'));
|
||||
|
||||
backend.add(import('./experimental/instanceMetadata'));
|
||||
backend.add(import('./experimental/systemMetadata'));
|
||||
backend.add(systemMetadataServiceFactory);
|
||||
|
||||
backend.start();
|
||||
|
||||
Reference in New Issue
Block a user