Merge pull request #27921 from aramissennyeydd/sennyeya/host-discovery

feat: add an instance metadata experimental service
This commit is contained in:
Patrik Oldsberg
2024-12-03 12:32:07 +01:00
committed by GitHub
8 changed files with 217 additions and 1 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-app-api': minor
'@backstage/backend-plugin-api': minor
---
**EXPERIMENTAL**: Adds a new `instanceMetadataService` to hold information about a specific backend instance.
@@ -27,6 +27,7 @@ import {
createBackendFeatureLoader,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha';
class MockLogger {
debug() {}
@@ -724,4 +725,59 @@ describe('BackendInitializer', () => {
await init.start();
});
it('should properly add plugins + modules to the instance metadata service', async () => {
expect.assertions(1);
const backend = new BackendInitializer(baseFactories);
const plugin = createBackendPlugin({
pluginId: 'test',
register(reg) {
reg.registerInit({
deps: {},
async init() {},
});
},
});
const instanceMetadataPlugin = createBackendPlugin({
pluginId: 'instance-metadata',
register(reg) {
reg.registerInit({
deps: {
instanceMetadata: instanceMetadataServiceRef,
},
async init({ instanceMetadata }) {
expect(instanceMetadata.getInstalledFeatures()).toEqual([
{
pluginId: 'test',
type: 'plugin',
},
{
pluginId: 'test',
moduleId: 'test',
type: 'module',
},
{
pluginId: 'instance-metadata',
type: 'plugin',
},
]);
},
});
},
});
const module = createBackendModule({
pluginId: 'test',
moduleId: 'test',
register(reg) {
reg.registerInit({
deps: {},
async init() {},
});
},
});
backend.add(plugin);
backend.add(module);
backend.add(instanceMetadataPlugin);
await backend.start();
});
});
@@ -22,6 +22,7 @@ import {
ServiceFactory,
LifecycleService,
RootLifecycleService,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { ServiceOrExtensionPoint } from './types';
// Direct internal import to avoid duplication
@@ -34,7 +35,11 @@ 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 } from '@backstage/errors';
import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha';
import {
instanceMetadataServiceRef,
featureDiscoveryServiceRef,
BackendFeatureMeta,
} from '@backstage/backend-plugin-api/alpha';
import { DependencyGraph } from '../lib/DependencyGraph';
import { ServiceRegistry } from './ServiceRegistry';
import { createInitializationLogger } from './createInitializationLogger';
@@ -96,6 +101,39 @@ 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 { type: 'plugin', pluginId: feature.pluginId };
} else if (feature.type === 'module') {
return {
type: 'module',
pluginId: feature.pluginId,
moduleId: feature.moduleId,
};
}
// Ignore unknown feature types.
return undefined;
})
.filter(Boolean) as BackendFeatureMeta[];
}
return [];
})
.flat();
return createServiceFactory({
service: instanceMetadataServiceRef,
deps: {},
factory: async () => ({ getInstalledFeatures: () => installedFeatures }),
});
}
export class BackendInitializer {
#startPromise?: Promise<void>;
#stopPromise?: Promise<void>;
@@ -210,6 +248,10 @@ export class BackendInitializer {
await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders);
this.#serviceRegistry.add(
createInstanceMetadataServiceFactory(this.#registrations),
);
// Initialize all root scoped services
await this.#serviceRegistry.initializeEagerServicesWithScope('root');
@@ -6,6 +6,18 @@
import { BackendFeature } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
// @alpha (undocumented)
export type BackendFeatureMeta =
| {
type: 'plugin';
pluginId: string;
}
| {
type: 'module';
pluginId: string;
moduleId: string;
};
// @alpha (undocumented)
export interface FeatureDiscoveryService {
// (undocumented)
@@ -21,5 +33,18 @@ export const featureDiscoveryServiceRef: ServiceRef<
'singleton'
>;
// @alpha (undocumented)
export interface InstanceMetadataService {
// (undocumented)
getInstalledFeatures: () => BackendFeatureMeta[];
}
// @alpha
export const instanceMetadataServiceRef: ServiceRef<
InstanceMetadataService,
'plugin',
'singleton'
>;
// (No @packageDocumentation comment for this package)
```
+16
View File
@@ -34,3 +34,19 @@ export const featureDiscoveryServiceRef =
id: 'core.featureDiscovery',
scope: 'root',
});
/**
* EXPERIMENTAL: Instance metadata service.
*
* @alpha
*/
export const instanceMetadataServiceRef = createServiceRef<
import('./services/definitions/InstanceMetadataService').InstanceMetadataService
>({
id: 'core.instanceMetadata',
});
export type {
BackendFeatureMeta,
InstanceMetadataService,
} from './services/definitions/InstanceMetadataService';
@@ -0,0 +1,32 @@
/*
* 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.
*/
/** @alpha */
export type BackendFeatureMeta =
| {
type: 'plugin';
pluginId: string;
}
| {
type: 'module';
pluginId: string;
moduleId: string;
};
/** @alpha */
export interface InstanceMetadataService {
getInstalledFeatures: () => BackendFeatureMeta[];
}
+1
View File
@@ -59,5 +59,6 @@ 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.start();
+38
View File
@@ -0,0 +1,38 @@
/*
* 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';
// 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,
},
async init({ instanceMetadata, logger }) {
logger.info(
`Installed features on this instance: ${instanceMetadata.getInstalledFeatures()}`,
);
},
});
},
});