Skip to content

Commit 8051f90

Browse files
committed
feat(backend): add service factories by default
This change explicitely adds the default service factories to the backend statically to prevent dynamic plugins from being able to override them by default. It's possible to override each statically added service factory via an environment variable derived from the service factory ID. So for example to add a "core.rootHttpService" service factory configuration from a dynamic plugin, set ENABLE_CORE_ROOTHTTPSERVICE_OVERRIDE to "true". This change also adds a logger to the backend main. Finally, a unit test has been added that checks the installed backend-defaults value for the defaultServiceFactories list against what this change adds to catch future regressions. Signed-off-by: Stan Lewis <[email protected]>
1 parent 3179768 commit 8051f90

File tree

6 files changed

+146
-1
lines changed

6 files changed

+146
-1
lines changed

packages/backend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@backstage/plugin-catalog-backend-module-openapi": "0.2.3",
3838
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.1",
3939
"@backstage/plugin-events-backend": "0.3.15",
40+
"@backstage/plugin-events-node": "^0.4.4",
4041
"@backstage/plugin-proxy-backend": "0.5.7",
4142
"@backstage/plugin-scaffolder-backend": "1.26.2",
4243
"@backstage/plugin-search-backend": "1.6.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ServiceFactory } from '@backstage/backend-plugin-api';
2+
3+
import { defaultServiceFactories } from './defaultServiceFactories';
4+
5+
// explicitly check this against the module inside the installed package
6+
const {
7+
defaultServiceFactories: upstreamDefaultServiceFactories,
8+
// eslint-disable-next-line
9+
} = require('../../../node_modules/@backstage/backend-defaults/dist/CreateBackend.cjs.js');
10+
11+
function findDifference(a1: string[], a2: string[]) {
12+
const set = new Set(a2);
13+
return a1.filter(i => !set.has(i));
14+
}
15+
16+
function findSymmetricDifference(a1: string[], a2: string[]) {
17+
return [...new Set([...findDifference(a1, a2), ...findDifference(a2, a1)])];
18+
}
19+
20+
/**
21+
* Validate that the installed backend-defaults package contains the expected
22+
* list of default service factories. A failure in this test indicates that
23+
* either the export was removed, the list was moved, or the list in
24+
* "defaultServiceFactories" should be updated.
25+
*/
26+
describe('Default service factory list comparison', () => {
27+
it('Should produce an expected difference of service factories as compared to the upstream implementation', () => {
28+
const upstreamServiceFactoryIds = upstreamDefaultServiceFactories.map(
29+
(serviceFactory: ServiceFactory) => serviceFactory.service.id,
30+
);
31+
const serviceFactoryIds = defaultServiceFactories.map(
32+
(serviceFactory: ServiceFactory) => serviceFactory.service.id,
33+
);
34+
expect(
35+
findSymmetricDifference(upstreamServiceFactoryIds, serviceFactoryIds),
36+
).toEqual(expect.arrayContaining(['core.rootLogger']));
37+
});
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { authServiceFactory } from '@backstage/backend-defaults/auth';
2+
import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
3+
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
4+
import { discoveryServiceFactory } from '@backstage/backend-defaults/discovery';
5+
import { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth';
6+
import { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter';
7+
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
8+
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
9+
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
10+
import { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig';
11+
import { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';
12+
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
13+
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
14+
import { WinstonLogger } from '@backstage/backend-defaults/rootLogger';
15+
import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';
16+
import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader';
17+
import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';
18+
import { eventsServiceFactory } from '@backstage/plugin-events-node';
19+
20+
/**
21+
* Service factories that are added to the backend statically by default. This
22+
* should be kept up to date with the upstream package code, which is currently
23+
* not exported.
24+
*/
25+
export const defaultServiceFactories = [
26+
authServiceFactory,
27+
cacheServiceFactory,
28+
rootConfigServiceFactory,
29+
databaseServiceFactory,
30+
discoveryServiceFactory,
31+
httpAuthServiceFactory,
32+
httpRouterServiceFactory,
33+
lifecycleServiceFactory,
34+
loggerServiceFactory,
35+
permissionsServiceFactory,
36+
rootHealthServiceFactory,
37+
rootHttpRouterServiceFactory,
38+
rootLifecycleServiceFactory,
39+
// rootLoggerServiceFactory,
40+
schedulerServiceFactory,
41+
userInfoServiceFactory,
42+
urlReaderServiceFactory,
43+
eventsServiceFactory,
44+
];
45+
46+
export const getDefaultServiceFactories = ({
47+
logger,
48+
}: {
49+
logger: WinstonLogger;
50+
}) => {
51+
return defaultServiceFactories.filter(serviceFactory => {
52+
const envName = `ENABLE_${serviceFactory.service.id.toLocaleUpperCase().replace('.', '_')}_OVERRIDE`;
53+
if ((process.env[envName] || '').toLocaleLowerCase() !== 'true') {
54+
logger.debug(
55+
`Adding service factory "${serviceFactory.service.id}", to override set "${envName}" to "true"`,
56+
);
57+
return true;
58+
}
59+
logger.warn(
60+
`Allowing override for service factory "${serviceFactory.service.id}"`,
61+
);
62+
return false;
63+
});
64+
};

packages/backend/src/index.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,32 @@ import { PackageRoles } from '@backstage/cli-node';
55
import * as path from 'path';
66

77
import { configureCorporateProxyAgent } from './corporate-proxy';
8+
import { getDefaultServiceFactories } from './defaultServiceFactories';
89
import { CommonJSModuleLoader } from './loader';
9-
import { transports } from './logger';
10+
import { createStaticLogger, transports } from './logger';
1011
import {
1112
healthCheckPlugin,
1213
metricsPlugin,
1314
pluginIDProviderService,
1415
rbacDynamicPluginsProvider,
1516
} from './modules';
1617

18+
// Create a logger to cover logging static initialization tasks
19+
const staticLogger = createStaticLogger({ service: 'developer-hub-init' });
20+
staticLogger.info('Starting Developer Hub backend');
21+
1722
// RHIDP-2217: adds support for corporate proxy
1823
configureCorporateProxyAgent();
1924

2025
const backend = createBackend();
2126

27+
const defaultServiceFactories = getDefaultServiceFactories({
28+
logger: staticLogger,
29+
});
30+
defaultServiceFactories.forEach(serviceFactory => {
31+
backend.add(serviceFactory);
32+
});
33+
2234
backend.add(
2335
dynamicPluginsFeatureLoader({
2436
schemaLocator(pluginPackage) {

packages/backend/src/logger/customLogger.ts

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { WinstonLogger } from '@backstage/backend-defaults/rootLogger';
12
import type { Config } from '@backstage/config';
23

34
import * as winston from 'winston';
@@ -73,3 +74,18 @@ export const transports = {
7374
];
7475
},
7576
};
77+
78+
export const createStaticLogger = ({ service }: { service: string }) => {
79+
const logger = WinstonLogger.create({
80+
meta: {
81+
service,
82+
},
83+
level: process.env.LOG_LEVEL || 'info',
84+
format:
85+
process.env.NODE_ENV === 'production'
86+
? defaultFormat
87+
: WinstonLogger.colorFormat(),
88+
transports: transports.log,
89+
});
90+
return logger;
91+
};

yarn.lock

+14
Original file line numberDiff line numberDiff line change
@@ -6057,6 +6057,19 @@ __metadata:
60576057
languageName: node
60586058
linkType: hard
60596059

6060+
"@backstage/plugin-events-node@npm:^0.4.4":
6061+
version: 0.4.4
6062+
resolution: "@backstage/plugin-events-node@npm:0.4.4"
6063+
dependencies:
6064+
"@backstage/backend-plugin-api": ^1.0.1
6065+
"@backstage/errors": ^1.2.4
6066+
"@backstage/types": ^1.1.1
6067+
cross-fetch: ^4.0.0
6068+
uri-template: ^2.0.0
6069+
checksum: e1d665686ae3d2462c40b773f9e005d092a18cd866624799a3174100f3b496d76e6a8abfde8888114ec346fbcb081edbc45e1b9be1e8f875e13ad62f81121a68
6070+
languageName: node
6071+
linkType: hard
6072+
60606073
"@backstage/plugin-home-react@npm:0.1.18, @backstage/plugin-home-react@npm:^0.1.15, @backstage/plugin-home-react@npm:^0.1.16, @backstage/plugin-home-react@npm:^0.1.18":
60616074
version: 0.1.18
60626075
resolution: "@backstage/plugin-home-react@npm:0.1.18"
@@ -20163,6 +20176,7 @@ __metadata:
2016320176
"@backstage/plugin-catalog-backend-module-openapi": 0.2.3
2016420177
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": 0.2.1
2016520178
"@backstage/plugin-events-backend": 0.3.15
20179+
"@backstage/plugin-events-node": ^0.4.4
2016620180
"@backstage/plugin-proxy-backend": 0.5.7
2016720181
"@backstage/plugin-scaffolder-backend": 1.26.2
2016820182
"@backstage/plugin-search-backend": 1.6.1

0 commit comments

Comments
 (0)