feat: add a new system metadata service
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./auditor": "./src/entrypoints/auditor/index.ts",
|
||||
"./alpha/systemMetadata": "./src/entrypoints/systemMetadata/index.ts",
|
||||
"./auth": "./src/entrypoints/auth/index.ts",
|
||||
"./cache": "./src/entrypoints/cache/index.ts",
|
||||
"./database": "./src/entrypoints/database/index.ts",
|
||||
@@ -49,6 +50,9 @@
|
||||
"auditor": [
|
||||
"src/entrypoints/auditor/index.ts"
|
||||
],
|
||||
"alpha/systemMetadata": [
|
||||
"src/entrypoints/systemMetadata/index.ts"
|
||||
],
|
||||
"auth": [
|
||||
"src/entrypoints/auth/index.ts"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { systemMetadataServiceFactory } from './systemMetadataServiceFactory';
|
||||
export { DefaultSystemMetadataService } from './lib/DefaultSystemMetadataService';
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
BackstageInstance,
|
||||
SystemMetadataService,
|
||||
} from '@backstage/backend-plugin-api/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;
|
||||
}
|
||||
|
||||
public static create(pluginEnv: {
|
||||
logger: LoggerService;
|
||||
config: RootConfigService;
|
||||
}) {
|
||||
return new DefaultSystemMetadataService(pluginEnv);
|
||||
}
|
||||
|
||||
listInstances() {
|
||||
const endpoints =
|
||||
this.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);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DefaultSystemMetadataService } from './lib/DefaultSystemMetadataService';
|
||||
import { systemMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
|
||||
/**
|
||||
* Metadata about an entire Backstage system, a collection of Backstage instances.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const systemMetadataServiceFactory = createServiceFactory({
|
||||
service: systemMetadataServiceRef,
|
||||
deps: {
|
||||
logger: coreServices.logger,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async factory({ logger, config }) {
|
||||
return DefaultSystemMetadataService.create({
|
||||
logger,
|
||||
config,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -23,3 +23,16 @@ export type {
|
||||
export type { ActionsService, ActionsServiceAction } from './ActionsService';
|
||||
|
||||
export { actionsRegistryServiceRef, actionsServiceRef } from './refs';
|
||||
|
||||
import { createServiceRef } from '@backstage/backend-plugin-api';
|
||||
|
||||
export const systemMetadataServiceRef = createServiceRef<
|
||||
import('./services/definitions/SystemMetadataService').SystemMetadataService
|
||||
>({
|
||||
id: 'core.systemMetadata',
|
||||
});
|
||||
|
||||
export type {
|
||||
BackstageInstance,
|
||||
SystemMetadataService,
|
||||
} from './services/definitions/SystemMetadataService';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
type Target = string | { internal: string; external: string };
|
||||
|
||||
export interface BackstageInstance {
|
||||
url: Target;
|
||||
}
|
||||
|
||||
export interface SystemMetadataService {
|
||||
listInstances(): Promise<BackstageInstance[]>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
# example-backend
|
||||
|
||||
This package is an EXAMPLE of a Backstage backend using the [new backend system](https://backstage.io/docs/backend-system/).
|
||||
|
||||
The main purpose of this package is to provide a test bed for Backstage split deployment work. You can deploy both this package and the main `packages/backend` together by running the `start:split` command in both packages. This will run the following backends:
|
||||
|
||||
1. `packages/backend` running on `:7007` with the default plugins installed.
|
||||
2. `packages/backend-split` running on `:7008` with a subset of plugins installed for testing.
|
||||
@@ -0,0 +1,14 @@
|
||||
backend:
|
||||
baseUrl: http://localhost:7008
|
||||
listen:
|
||||
port: 7008
|
||||
|
||||
discovery:
|
||||
endpoints:
|
||||
- target: http://localhost:7007/api/{{pluginId}}
|
||||
plugins: [proxy]
|
||||
- target: http://localhost:7008/api/{{pluginId}}
|
||||
plugins: [catalog]
|
||||
instances:
|
||||
- baseUrl: http://localhost:7007
|
||||
- baseUrl: http://localhost:7008
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: example-backend-split
|
||||
title: example-backend-split
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-backend
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,12 @@
|
||||
# Knip report
|
||||
|
||||
## Unused dependencies (5)
|
||||
|
||||
| Name | Location | Severity |
|
||||
| :----------------------------------------------- | :----------- | :------- |
|
||||
| @backstage/plugin-catalog-backend-module-openapi | package.json | error |
|
||||
| @backstage/plugin-search-backend-node | package.json | error |
|
||||
| @backstage/plugin-permission-common | package.json | error |
|
||||
| @backstage/plugin-permission-node | package.json | error |
|
||||
| @backstage/backend-tasks | package.json | error |
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "example-backend-split",
|
||||
"version": "0.0.33-next.2",
|
||||
"backstage": {
|
||||
"role": "backend"
|
||||
},
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/backend"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"start": "backstage-cli package start --require ./src/instrumentation.js",
|
||||
"start:split": "backstage-cli package start --require ./src/instrumentation.js --config ../../app-config.yaml --config app-config.split.yaml",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/plugin-app-backend": "workspace:^",
|
||||
"@backstage/plugin-auth-backend": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "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-scaffolder-entity-model": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
|
||||
"@backstage/plugin-devtools-backend": "workspace:^",
|
||||
"@backstage/plugin-events-backend": "workspace:^",
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^",
|
||||
"@backstage/plugin-notifications-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-backend": "workspace:^",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
"@backstage/plugin-proxy-backend": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^",
|
||||
"@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^",
|
||||
"@backstage/plugin-search-backend": "workspace:^",
|
||||
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
|
||||
"@backstage/plugin-search-backend-module-explore": "workspace:^",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
|
||||
"@backstage/plugin-search-backend-node": "workspace:^",
|
||||
"@backstage/plugin-signals-backend": "workspace:^",
|
||||
"@backstage/plugin-techdocs-backend": "workspace:^",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.54.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.54.0",
|
||||
"@opentelemetry/sdk-node": "^0.54.0",
|
||||
"example-app": "link:../app",
|
||||
"express-promise-router": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
GET http://localhost:7007/.backstage/systemInfo/features/installed
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:7008/.backstage/systemInfo/features/installed
|
||||
+10
-2
@@ -26,14 +26,22 @@ export default createBackendPlugin({
|
||||
deps: {
|
||||
instanceMetadata: coreServices.rootInstanceMetadata,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async init({ instanceMetadata, logger }) {
|
||||
const plugins = await instanceMetadata.getInstalledPlugins();
|
||||
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);
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 installed features.
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'system-metadata-logging',
|
||||
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);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 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 { createBackend } from '@backstage/backend-defaults';
|
||||
import { systemMetadataServiceFactory } from '@backstage/backend-defaults/alpha/systemMetadata';
|
||||
|
||||
const backend = createBackend();
|
||||
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
|
||||
backend.add(
|
||||
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
|
||||
);
|
||||
backend.add(import('@backstage/plugin-catalog-backend'));
|
||||
|
||||
backend.add(
|
||||
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
|
||||
);
|
||||
backend.add(import('@backstage/plugin-permission-backend'));
|
||||
|
||||
backend.add(import('./experimental/instanceMetadata'));
|
||||
backend.add(import('./experimental/systemMetadata'));
|
||||
backend.add(systemMetadataServiceFactory);
|
||||
|
||||
backend.start();
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
||||
const {
|
||||
getNodeAutoInstrumentations,
|
||||
} = require('@opentelemetry/auto-instrumentations-node');
|
||||
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
|
||||
|
||||
// Expose opentelemetry metrics using a Prometheus exporter on
|
||||
// http://localhost:9464/metrics. See packages/backend/prometheus.yml for
|
||||
// more information on how to scrape it.
|
||||
const prometheus = new PrometheusExporter();
|
||||
|
||||
const sdk = new NodeSDK({
|
||||
// traceExporter: ...,
|
||||
metricReader: prometheus,
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
@@ -0,0 +1,9 @@
|
||||
discovery:
|
||||
endpoints:
|
||||
- target: http://localhost:7007/api/{{pluginId}}
|
||||
plugins: [proxy]
|
||||
- target: http://localhost:7008/api/{{pluginId}}
|
||||
plugins: [catalog]
|
||||
instances:
|
||||
- baseUrl: http://localhost:7007
|
||||
- baseUrl: http://localhost:7008
|
||||
@@ -26,6 +26,7 @@
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"start": "backstage-cli package start --require ./src/instrumentation.js",
|
||||
"start:split": "backstage-cli package start --require ./src/instrumentation.js --config ../../app-config.yaml --config app-config.split.yaml",
|
||||
"start:prometheus": "docker run --mount type=bind,source=./prometheus.yml,destination=/etc/prometheus/prometheus.yml --publish published=9090,target=9090,protocol=tcp prom/prometheus",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
@@ -69,7 +70,8 @@
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.61.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.54.0",
|
||||
"@opentelemetry/sdk-node": "^0.54.0",
|
||||
"example-app": "link:../app"
|
||||
"example-app": "link:../app",
|
||||
"express-promise-router": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
import Router from 'express-promise-router';
|
||||
|
||||
// Example usage of the instance metadata service to log the installed features.
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'instance-metadata-logging',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
instanceMetadata: instanceMetadataServiceRef,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async init({ instanceMetadata, logger, httpRouter }) {
|
||||
logger.info(
|
||||
`Installed features on this instance: ${JSON.stringify(
|
||||
instanceMetadata.getInstalledFeatures(),
|
||||
)}`,
|
||||
);
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/features/installed', (_, res) => {
|
||||
res.json({ items: instanceMetadata.getInstalledFeatures() });
|
||||
});
|
||||
|
||||
httpRouter.use('/.backstage/instanceInfo', router);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 installed features.
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'system-metadata-logging',
|
||||
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);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
coreServices,
|
||||
createBackendFeatureLoader,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { systemMetadataServiceFactory } from '@backstage/backend-defaults/alpha/systemMetadata';
|
||||
|
||||
const backend = createBackend();
|
||||
|
||||
@@ -69,7 +70,9 @@ backend.add(searchLoader);
|
||||
backend.add(import('@backstage/plugin-techdocs-backend'));
|
||||
backend.add(import('@backstage/plugin-signals-backend'));
|
||||
backend.add(import('@backstage/plugin-notifications-backend'));
|
||||
backend.add(import('./instanceMetadata'));
|
||||
backend.add(import('./experimental/instanceMetadata'));
|
||||
backend.add(import('./experimental/systemMetadata'));
|
||||
backend.add(systemMetadataServiceFactory);
|
||||
|
||||
backend.add(import('@backstage/plugin-events-backend-module-google-pubsub'));
|
||||
backend.add(import('@backstage/plugin-mcp-actions-backend'));
|
||||
|
||||
@@ -30232,6 +30232,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: soft
|
||||
|
||||
"example-app@link:../app::locator=example-backend-split%40workspace%3Apackages%2Fbackend-split":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-app@link:../app::locator=example-backend-split%40workspace%3Apackages%2Fbackend-split"
|
||||
languageName: node
|
||||
linkType: soft
|
||||
|
||||
"example-app@workspace:packages/app":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-app@workspace:packages/app"
|
||||
@@ -30300,6 +30306,51 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-backend-split@workspace:packages/backend-split":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-backend-split@workspace:packages/backend-split"
|
||||
dependencies:
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/plugin-app-backend": "workspace:^"
|
||||
"@backstage/plugin-auth-backend": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "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-scaffolder-entity-model": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^"
|
||||
"@backstage/plugin-devtools-backend": "workspace:^"
|
||||
"@backstage/plugin-events-backend": "workspace:^"
|
||||
"@backstage/plugin-kubernetes-backend": "workspace:^"
|
||||
"@backstage/plugin-notifications-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-backend": "workspace:^"
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^"
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
"@backstage/plugin-proxy-backend": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^"
|
||||
"@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^"
|
||||
"@backstage/plugin-search-backend": "workspace:^"
|
||||
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
|
||||
"@backstage/plugin-search-backend-module-explore": "workspace:^"
|
||||
"@backstage/plugin-search-backend-module-techdocs": "workspace:^"
|
||||
"@backstage/plugin-search-backend-node": "workspace:^"
|
||||
"@backstage/plugin-signals-backend": "workspace:^"
|
||||
"@backstage/plugin-techdocs-backend": "workspace:^"
|
||||
"@opentelemetry/auto-instrumentations-node": "npm:^0.54.0"
|
||||
"@opentelemetry/exporter-prometheus": "npm:^0.54.0"
|
||||
"@opentelemetry/sdk-node": "npm:^0.54.0"
|
||||
example-app: "link:../app"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-backend@workspace:packages/backend":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-backend@workspace:packages/backend"
|
||||
@@ -30345,6 +30396,7 @@ __metadata:
|
||||
"@opentelemetry/exporter-prometheus": "npm:^0.54.0"
|
||||
"@opentelemetry/sdk-node": "npm:^0.54.0"
|
||||
example-app: "link:../app"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
||||
Reference in New Issue
Block a user