Merge pull request #22456 from minkimcello/mk/new-create-app

Migrate create-app template to use the new backend
This commit is contained in:
Patrik Oldsberg
2024-03-19 08:52:13 +01:00
committed by GitHub
18 changed files with 77 additions and 342 deletions
@@ -51,6 +51,7 @@ jest.mock('./versions', () => ({
root: '1.2.3',
'@backstage/cli': '1.0.0',
'@backstage/backend-common': '1.0.0',
'@backstage/backend-defaults': '1.0.0',
'@backstage/backend-tasks': '1.0.0',
'@backstage/catalog-model': '1.0.0',
'@backstage/catalog-client': '1.0.0',
@@ -58,10 +59,14 @@ jest.mock('./versions', () => ({
'@backstage/plugin-app-backend': '1.0.0',
'@backstage/plugin-auth-backend': '1.0.0',
'@backstage/plugin-auth-node': '1.0.0',
'@backstage/plugin-auth-backend-module-github-provider': '1.0.0',
'@backstage/plugin-auth-backend-module-guest-provider': '1.0.0',
'@backstage/plugin-catalog-backend': '1.0.0',
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': '1.0.0',
'@backstage/plugin-permission-common': '1.0.0',
'@backstage/plugin-permission-node': '1.0.0',
'@backstage/plugin-permission-backend': '1.0.0',
'@backstage/plugin-permission-backend-module-allow-all-policy': '1.0.0',
'@backstage/plugin-proxy-backend': '1.0.0',
'@backstage/plugin-scaffolder-backend': '1.0.0',
'@backstage/plugin-search-backend': '1.0.0',
+13
View File
@@ -33,6 +33,7 @@ import { version as root } from '../../../../package.json';
import { version as appDefaults } from '../../../app-defaults/package.json';
import { version as backendCommon } from '../../../backend-common/package.json';
import { version as backendDefaults } from '../../../backend-defaults/package.json';
import { version as backendTasks } from '../../../backend-tasks/package.json';
import { version as catalogClient } from '../../../catalog-client/package.json';
import { version as catalogModel } from '../../../catalog-model/package.json';
@@ -50,6 +51,8 @@ import { version as theme } from '../../../theme/package.json';
import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json';
import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json';
import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json';
import { version as pluginAuthBackendModuleGithubProvider } from '../../../../plugins/auth-backend-module-github-provider/package.json';
import { version as pluginAuthBackendModuleGuestProvider } from '../../../../plugins/auth-backend-module-guest-provider/package.json';
import { version as pluginAuthNode } from '../../../../plugins/auth-node/package.json';
import { version as pluginCatalog } from '../../../../plugins/catalog/package.json';
import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json';
@@ -62,6 +65,8 @@ import { version as pluginExplore } from '../../../../plugins/explore/package.js
import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json';
import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json';
import { version as pluginOrg } from '../../../../plugins/org/package.json';
import { version as pluginPermissionBackend } from '../../../../plugins/permission-backend/package.json';
import { version as pluginPermissionBackendModulePolicyAllowAll } from '../../../../plugins/permission-backend-module-policy-allow-all/package.json';
import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';
import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json';
import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';
@@ -87,6 +92,7 @@ export const packageVersions = {
root,
'@backstage/app-defaults': appDefaults,
'@backstage/backend-common': backendCommon,
'@backstage/backend-defaults': backendDefaults,
'@backstage/backend-tasks': backendTasks,
'@backstage/catalog-client': catalogClient,
'@backstage/catalog-model': catalogModel,
@@ -101,6 +107,10 @@ export const packageVersions = {
'@backstage/plugin-api-docs': pluginApiDocs,
'@backstage/plugin-app-backend': pluginAppBackend,
'@backstage/plugin-auth-backend': pluginAuthBackend,
'@backstage/plugin-auth-backend-module-github-provider':
pluginAuthBackendModuleGithubProvider,
'@backstage/plugin-auth-backend-module-guest-provider':
pluginAuthBackendModuleGuestProvider,
'@backstage/plugin-auth-node': pluginAuthNode,
'@backstage/plugin-catalog': pluginCatalog,
'@backstage/plugin-catalog-common': pluginCatalogCommon,
@@ -114,6 +124,9 @@ export const packageVersions = {
'@backstage/plugin-github-actions': pluginGithubActions,
'@backstage/plugin-lighthouse': pluginLighthouse,
'@backstage/plugin-org': pluginOrg,
'@backstage/plugin-permission-backend': pluginPermissionBackend,
'@backstage/plugin-permission-backend-module-allow-all-policy':
pluginPermissionBackendModulePolicyAllowAll,
'@backstage/plugin-permission-common': pluginPermissionCommon,
'@backstage/plugin-permission-node': pluginPermissionNode,
'@backstage/plugin-permission-react': pluginPermissionReact,
@@ -28,6 +28,10 @@ backend:
# ca: # if you have a CA file and want to verify it you can uncomment this section
# $file: <file-path>/ca/server.crt
auth:
providers:
guest: null
catalog:
# Overrides the default list locations from app-config.yaml as these contain example data.
# See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details
@@ -64,7 +64,9 @@ techdocs:
auth:
# see https://backstage.io/docs/auth/ to learn about auth providers
providers: {}
providers:
# See https://backstage.io/docs/auth/guest/provider
guest: {}
scaffolder:
# see https://backstage.io/docs/features/software-templates/configuration for software template options
@@ -19,5 +19,9 @@ import { test, expect } from '@playwright/test';
test('App should render the welcome page', async ({ page }) => {
await page.goto('/');
const enterButton = page.getByRole('button', { name: 'Enter' });
await expect(enterButton).toBeVisible();
await enterButton.click();
await expect(page.getByText('My Company Catalog')).toBeVisible();
});
@@ -27,7 +27,11 @@ import { entityPage } from './components/catalog/EntityPage';
import { searchPage } from './components/search/SearchPage';
import { Root } from './components/Root';
import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core-components';
import { createApp } from '@backstage/app-defaults';
import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
@@ -53,6 +57,9 @@ const app = createApp({
catalogIndex: catalogPlugin.routes.catalogIndex,
});
},
components: {
SignInPage: props => <SignInPage {...props} auto providers={['guest']} />,
},
});
const routes = (
@@ -17,30 +17,30 @@
},
"dependencies": {
"@backstage/backend-common": "^{{version '@backstage/backend-common'}}",
"@backstage/backend-defaults": "^{{version '@backstage/backend-defaults'}}",
"@backstage/backend-tasks": "^{{version '@backstage/backend-tasks'}}",
"@backstage/catalog-client": "^{{version '@backstage/catalog-client'}}",
"@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}",
"@backstage/config": "^{{version '@backstage/config'}}",
"@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}",
"@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}",
"@backstage/plugin-auth-backend-module-github-provider": "^{{version '@backstage/plugin-auth-backend-module-github-provider'}}",
"@backstage/plugin-auth-backend-module-guest-provider": "^{{version '@backstage/plugin-auth-backend-module-guest-provider'}}",
"@backstage/plugin-auth-node": "^{{version '@backstage/plugin-auth-node'}}",
"@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^{{version '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'}}",
"@backstage/plugin-permission-backend": "^{{version '@backstage/plugin-permission-backend'}}",
"@backstage/plugin-permission-backend-module-allow-all-policy": "^{{version '@backstage/plugin-permission-backend-module-allow-all-policy'}}",
"@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}",
"@backstage/plugin-permission-node": "^{{version '@backstage/plugin-permission-node'}}",
"@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}",
"@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}",
"@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}",
"@backstage/plugin-search-backend-module-catalog": "^{{version '@backstage/plugin-search-backend-module-catalog'}}",
"@backstage/plugin-search-backend-module-pg": "^{{version '@backstage/plugin-search-backend-module-pg'}}",
"@backstage/plugin-search-backend-module-techdocs": "^{{version '@backstage/plugin-search-backend-module-techdocs'}}",
"@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}",
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
"app": "link:../app",
"better-sqlite3": "^9.0.0",
"dockerode": "^3.3.1",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"node-gyp": "^9.0.0",
"pg": "^8.11.3",
"winston": "^3.2.1"
@@ -6,110 +6,36 @@
* Happy hacking!
*/
import Router from 'express-promise-router';
import {
createServiceBuilder,
loadBackendConfig,
getRootLogger,
useHotMemoize,
notFoundHandler,
CacheManager,
DatabaseManager,
HostDiscovery,
UrlReaders,
ServerTokenManager,
} from '@backstage/backend-common';
import { TaskScheduler } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
import app from './plugins/app';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import scaffolder from './plugins/scaffolder';
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
import { PluginEnvironment } from './types';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createBackend } from '@backstage/backend-defaults';
function makeCreateEnv(config: Config) {
const root = getRootLogger();
const reader = UrlReaders.default({ logger: root, config });
const discovery = HostDiscovery.fromConfig(config);
const cacheManager = CacheManager.fromConfig(config);
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
const tokenManager = ServerTokenManager.noop();
const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager });
const backend = createBackend();
const identity = DefaultIdentityClient.create({
discovery,
});
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
root.info(`Created UrlReader ${reader}`);
// auth plugin
backend.add(import('@backstage/plugin-auth-backend'));
// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
// See https://github.com/backstage/backstage/blob/master/docs/auth/guest/provider.md
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const database = databaseManager.forPlugin(plugin);
const cache = cacheManager.forPlugin(plugin);
const scheduler = taskScheduler.forPlugin(plugin);
return {
logger,
database,
cache,
config,
reader,
discovery,
tokenManager,
scheduler,
permissions,
identity,
};
};
}
// catalog plugin
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
async function main() {
const config = await loadBackendConfig({
argv: process.argv,
logger: getRootLogger(),
});
const createEnv = makeCreateEnv(config);
// permission plugin
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
// search plugin
backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
apiRouter.use('/auth', await auth(authEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
// Add backends ABOVE this line; this 404 handler is the catch-all fallback
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
.loadConfig(config)
.addRouter('/api', apiRouter)
.addRouter('', await app(appEnv));
await service.start().catch(err => {
console.log(err);
process.exit(1);
});
}
module.hot?.accept();
main().catch(error => {
console.error('Backend failed to start up', error);
process.exit(1);
});
backend.start();
@@ -1,14 +0,0 @@
import { createRouter } from '@backstage/plugin-app-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
appPackageName: 'app',
});
}
@@ -1,54 +0,0 @@
import {
createRouter,
providers,
defaultAuthProviderFactories,
} from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
discovery: env.discovery,
tokenManager: env.tokenManager,
providerFactories: {
...defaultAuthProviderFactories,
// This replaces the default GitHub auth provider with a customized one.
// The `signIn` option enables sign-in for this provider, using the
// identity resolution logic that's provided in the `resolver` callback.
//
// This particular resolver makes all users share a single "guest" identity.
// It should only be used for testing and trying out Backstage.
//
// If you want to use a production ready resolver you can switch to
// the one that is commented out below, it looks up a user entity in the
// catalog using the GitHub username of the authenticated user.
// That resolver requires you to have user entities populated in the catalog,
// for example using https://backstage.io/docs/integrations/github/org
//
// There are other resolvers to choose from, and you can also create
// your own, see the auth documentation for more details:
//
// https://backstage.io/docs/auth/identity-resolver
github: providers.github.create({
signIn: {
resolver(_, ctx) {
const userRef = 'user:default/guest'; // Must be a full entity reference
return ctx.issueToken({
claims: {
sub: userRef, // The user's own identity
ent: [userRef], // A list of identities that the user claims ownership through
},
});
},
// resolver: providers.github.resolvers.usernameMatchingUserEntityName(),
},
}),
},
});
}
@@ -1,14 +0,0 @@
import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
@@ -1,13 +0,0 @@
import { createRouter } from '@backstage/plugin-proxy-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
discovery: env.discovery,
});
}
@@ -1,22 +0,0 @@
import { CatalogClient } from '@backstage/catalog-client';
import { createRouter } from '@backstage/plugin-scaffolder-backend';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const catalogClient = new CatalogClient({
discoveryApi: env.discovery,
});
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
reader: env.reader,
catalogClient,
identity: env.identity,
permissions: env.permissions,
});
}
@@ -1,66 +0,0 @@
import { useHotCleanup } from '@backstage/backend-common';
import { createRouter } from '@backstage/plugin-search-backend';
import {
IndexBuilder,
LunrSearchEngine,
} from '@backstage/plugin-search-backend-node';
import { PluginEnvironment } from '../types';
import { DefaultCatalogCollatorFactory } from '@backstage/plugin-search-backend-module-catalog';
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-search-backend-module-techdocs';
import { Router } from 'express';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
// Initialize a connection to a search engine.
const searchEngine = new LunrSearchEngine({
logger: env.logger,
});
const indexBuilder = new IndexBuilder({
logger: env.logger,
searchEngine,
});
const schedule = env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 10 },
timeout: { minutes: 15 },
// A 3 second delay gives the backend server a chance to initialize before
// any collators are executed, which may attempt requests against the API.
initialDelay: { seconds: 3 },
});
// Collators are responsible for gathering documents known to plugins. This
// collator gathers entities from the software catalog.
indexBuilder.addCollator({
schedule,
factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
tokenManager: env.tokenManager,
}),
});
// collator gathers entities from techdocs.
indexBuilder.addCollator({
schedule,
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
discovery: env.discovery,
logger: env.logger,
tokenManager: env.tokenManager,
}),
});
// The scheduler controls when documents are gathered from collators and sent
// to the search engine for indexing.
const { scheduler } = await indexBuilder.build();
scheduler.start();
useHotCleanup(module, () => scheduler.stop());
return await createRouter({
engine: indexBuilder.getSearchEngine(),
types: indexBuilder.getDocumentTypes(),
permissions: env.permissions,
config: env.config,
logger: env.logger,
});
}
@@ -1,51 +0,0 @@
import { DockerContainerRunner } from '@backstage/backend-common';
import {
createRouter,
Generators,
Preparers,
Publisher,
} from '@backstage/plugin-techdocs-backend';
import Docker from 'dockerode';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
// Preparers are responsible for fetching source files for documentation.
const preparers = await Preparers.fromConfig(env.config, {
logger: env.logger,
reader: env.reader,
});
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
const dockerClient = new Docker();
const containerRunner = new DockerContainerRunner({ dockerClient });
// Generators are used for generating documentation sites.
const generators = await Generators.fromConfig(env.config, {
logger: env.logger,
containerRunner,
});
// Publisher is used for
// 1. Publishing generated files to storage
// 2. Fetching files from storage and passing them to TechDocs frontend.
const publisher = await Publisher.fromConfig(env.config, {
logger: env.logger,
discovery: env.discovery,
});
// checks if the publisher is working and logs the result
await publisher.getReadiness();
return await createRouter({
preparers,
generators,
publisher,
logger: env.logger,
config: env.config,
discovery: env.discovery,
cache: env.cache,
});
}
@@ -32,7 +32,7 @@ export default defineConfig({
? []
: [
{
command: 'yarn start',
command: 'yarn dev',
port: 3000,
reuseExistingServer: true,
timeout: 60_000,