Merge pull request #2600 from spotify/rugvip/disco
backend: add service discovery interface and implement for single host deployments
This commit is contained in:
+15
-1
@@ -8,13 +8,27 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
|
||||
|
||||
> Collect changes for the next release below
|
||||
|
||||
### Backend (example-backend, or backends created with @backstage/create-app)
|
||||
|
||||
- The default mount point for backend plugins have been changed to `/api`. These changes are done in the backend package itself, so it is recommended that you sync up existing backend packages with this new pattern. [#2562](https://github.com/spotify/backstage/pull/2562)
|
||||
- A service discovery mechanism for backend plugins has been added, and is now a requirement for several backend plugins. See [packages/backend/src/index.ts](./packages/backend/src/index.ts) for how to set it up using `SingleHostDiscovery` from `@backstage/backend-common`. Note that the default base path for plugins is set to `/api` to that change, but it can be set to use the old behavior via the `basePath` option. [#2600](https://github.com/spotify/backstage/pull/2600)
|
||||
|
||||
### @backstage/core
|
||||
|
||||
- Renamed `SessionStateApi` to `SessionApi` and `logout` to `signOut`. Custom implementations of the `SingInPage` app-component will need to rename their `logout` function. The different auth provider items for the `UserSettingsMenu` have been consolidated into a single `ProviderSettingsItem`, meaning you need to replace existing usages of `OAuthProviderSettings` and `OIDCProviderSettings`. [#2555](https://github.com/spotify/backstage/pull/2555).
|
||||
|
||||
### @backstage/auth-backend
|
||||
|
||||
- The default mount path of backend plugins was changed to `/api/:pluginId`, and as part of that it was needed to enable configuration of the base path of the auth backend, so that it can construct redirect URLs correctly. The default base path is `/api/auth`, but you need to set this to `/auth` if you want to keep using the old path. Note that you will also need to reconfigure any allowed redirect URLs to include `/api` if you switch to the new recommended pattern. [#2562](https://github.com/spotify/backstage/pull/2562)
|
||||
- The default mount path of backend plugins was changed to `/api/:pluginId`, and as part of that it was needed to enable configuration of the base path of the auth backend, so that it can construct redirect URLs correctly. Note that you will also need to reconfigure any allowed redirect URLs to include `/api` if you switch to the new recommended pattern. [#2562](https://github.com/spotify/backstage/pull/2562)
|
||||
- The auth backend now requires an implementation of `PluginEndpointDiscovery` from `@backstage/backend-common` to be passed in as `discovery`. See the changes to `@backstage/backend`.
|
||||
|
||||
### @backstage/proxy-backend
|
||||
|
||||
- The proxy backend now requires an implementation of `PluginEndpointDiscovery` from `@backstage/backend-common` to be passed in as `discovery`. See the changes to `@backstage/backend`.
|
||||
|
||||
### @backstage/techdocs-backend
|
||||
|
||||
- The TechDocs backend now requires an implementation of `PluginEndpointDiscovery` from `@backstage/backend-common` to be passed in as `discovery`. See the changes to `@backstage/backend`.
|
||||
|
||||
## v0.1.1-alpha.22
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { Config } from '@backstage/config';
|
||||
import { PluginEndpointDiscovery } from './types';
|
||||
import { readBaseOptions } from '../service/lib/config';
|
||||
import { DEFAULT_PORT } from '../service/lib/ServiceBuilderImpl';
|
||||
|
||||
/**
|
||||
* SingleHostDiscovery is a basic PluginEndpointDiscovery implementation
|
||||
* that assumes that all plugins are hosted in a single deployment.
|
||||
*
|
||||
* The deployment may be scaled horizontally, as long as the external URL
|
||||
* is the same for all instances. However, internal URLs will always be
|
||||
* resolved to the same host, so there won't be any balancing of internal traffic.
|
||||
*/
|
||||
export class SingleHostDiscovery implements PluginEndpointDiscovery {
|
||||
/**
|
||||
* Creates a new SingleHostDiscovery discovery instance by reading
|
||||
* from the `backend` config section, specifically the `.baseUrl` for
|
||||
* discovering the external URL, and the `.listen` and `.https` config
|
||||
* for the internal one.
|
||||
*
|
||||
* The basePath defaults to `/api`, meaning the default full internal
|
||||
* path for the `catalog` plugin will be `http://localhost:7000/api/catalog`.
|
||||
*/
|
||||
static fromConfig(config: Config, options?: { basePath?: string }) {
|
||||
const basePath = options?.basePath ?? '/api';
|
||||
const externalBaseUrl = config.getString('backend.baseUrl');
|
||||
|
||||
const { listenHost = '::', listenPort = DEFAULT_PORT } = readBaseOptions(
|
||||
config.getConfig('backend'),
|
||||
);
|
||||
const protocol = config.has('backend.https') ? 'https' : 'http';
|
||||
|
||||
// Translate bind-all to localhost, and support IPv6
|
||||
let host = listenHost;
|
||||
if (host === '::') {
|
||||
host = '::1';
|
||||
} else if (host === '0.0.0.0') {
|
||||
host = '127.0.0.1';
|
||||
}
|
||||
if (host.includes(':')) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
|
||||
const internalBaseUrl = `${protocol}://${host}:${listenPort}`;
|
||||
|
||||
return new SingleHostDiscovery(
|
||||
internalBaseUrl + basePath,
|
||||
externalBaseUrl + basePath,
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly internalBaseUrl: string,
|
||||
private readonly externalBaseUrl: string,
|
||||
) {}
|
||||
|
||||
async getBaseUrl(pluginId: string): Promise<string> {
|
||||
return `${this.internalBaseUrl}/${pluginId}`;
|
||||
}
|
||||
|
||||
async getExternalBaseUrl(pluginId: string): Promise<string> {
|
||||
return `${this.externalBaseUrl}/${pluginId}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { SingleHostDiscovery } from './SingleHostDiscovery';
|
||||
export type { PluginEndpointDiscovery } from './types';
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The PluginEndpointDiscovery is used to provide a mechanism for backend
|
||||
* plugins to discover the endpoints for itself or other backend plugins.
|
||||
*
|
||||
* The purpose of the discovery API is to allow for many different deployment
|
||||
* setups and routing methods through a central configuration, instead
|
||||
* of letting each individual plugin manage that configuration.
|
||||
*
|
||||
* Implementations of the discovery API can be as simple as a URL pattern
|
||||
* using the pluginId, but could also have overrides for individual plugins,
|
||||
* or query a separate discovery service.
|
||||
*/
|
||||
export type PluginEndpointDiscovery = {
|
||||
/**
|
||||
* Returns the internal HTTP base URL for a given plugin, without a trailing slash.
|
||||
*
|
||||
* The returned URL should point to an internal endpoint for the plugin, with
|
||||
* the shortest route possible. The URL should be used for service-to-service
|
||||
* communication within a Backstage backend deployment.
|
||||
*
|
||||
* This method must always be called just before making a request, as opposed to
|
||||
* fetching the URL when constructing an API client. That is to ensure that more
|
||||
* flexible routing patterns can be supported.
|
||||
*
|
||||
* For example, asking for the URL for `catalog` may return something
|
||||
* like `http://10.1.2.3/api/catalog`
|
||||
*/
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* Returns the external HTTP base backend URL for a given plugin, without a trailing slash.
|
||||
*
|
||||
* The returned URL should point to an external endpoint for the plugin, such that
|
||||
* it is reachable from the Backstage frontend and other external services. The returned
|
||||
* URL should be usable for example as a callback / webhook URL.
|
||||
*
|
||||
* The returned URL should be stable and in general not change unless other static
|
||||
* or external configuration is changed. Changes should not come as a surprise
|
||||
* to an operator of the Backstage backend.
|
||||
*
|
||||
* For example, asking for the URL for `catalog` may return something
|
||||
* like `https://backstage.example.com/api/catalog`
|
||||
*/
|
||||
getExternalBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export * from './config';
|
||||
export * from './database';
|
||||
export * from './discovery';
|
||||
export * from './errors';
|
||||
export * from './logging';
|
||||
export * from './middleware';
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
import { createHttpServer, createHttpsServer } from './hostFactory';
|
||||
import { metricsHandler } from './metrics';
|
||||
|
||||
const DEFAULT_PORT = 7000;
|
||||
export const DEFAULT_PORT = 7000;
|
||||
// '' is express default, which listens to all interfaces
|
||||
const DEFAULT_HOST = '';
|
||||
// taken from the helmet source code - don't seem to be exported
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader, AppConfig } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
@@ -59,7 +60,8 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) {
|
||||
},
|
||||
},
|
||||
);
|
||||
return { logger, database, config };
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
return { logger, database, config, discovery };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,11 +87,11 @@ async function main() {
|
||||
apiRouter.use('/rollbar', await rollbar(rollbarEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/sentry', await sentry(sentryEnv));
|
||||
apiRouter.use('/auth', await auth(authEnv, '/api/auth'));
|
||||
apiRouter.use('/auth', await auth(authEnv));
|
||||
apiRouter.use('/identity', await identity(identityEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv, '/api/proxy'));
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv));
|
||||
apiRouter.use('/graphql', await graphql(graphqlEnv));
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
import { createRouter } from '@backstage/plugin-auth-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
{ logger, database, config }: PluginEnvironment,
|
||||
basePath: string,
|
||||
) {
|
||||
return await createRouter({ logger, config, database, basePath });
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
database,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config, database, discovery });
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
import { createRouter } from '@backstage/plugin-proxy-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
{ logger, config }: PluginEnvironment,
|
||||
pathPrefix: string,
|
||||
) {
|
||||
return await createRouter({ logger, config, pathPrefix });
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config, discovery });
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import Docker from 'dockerode';
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
const generators = new Generators();
|
||||
const techdocsGenerator = new TechdocsGenerator(logger, config);
|
||||
@@ -51,5 +52,6 @@ export default async function createPlugin({
|
||||
dockerClient,
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
import Knex from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
database: Knex;
|
||||
config: Config;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader, AppConfig } from '@backstage/config';
|
||||
import auth from './plugins/auth';
|
||||
@@ -37,7 +38,8 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) {
|
||||
},
|
||||
},
|
||||
);
|
||||
return { logger, database, config };
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
return { logger, database, config, discovery };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,7 +61,7 @@ async function main() {
|
||||
apiRouter.use('/auth', await auth(authEnv))
|
||||
apiRouter.use('/identity', await identity(identityEnv))
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv))
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv, '/api/proxy'))
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv))
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
|
||||
@@ -5,6 +5,7 @@ export default async function createPlugin({
|
||||
logger,
|
||||
database,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config, database });
|
||||
return await createRouter({ logger, config, database, discovery });
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import { createRouter } from '@backstage/plugin-proxy-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(
|
||||
{ logger, config }: PluginEnvironment,
|
||||
pathPrefix: string,
|
||||
) {
|
||||
return await createRouter({ logger, config, pathPrefix });
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config, discovery });
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import Docker from 'dockerode';
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment) {
|
||||
const generators = new Generators();
|
||||
const techdocsGenerator = new TechdocsGenerator(logger, config);
|
||||
@@ -37,5 +38,6 @@ export default async function createPlugin({
|
||||
dockerClient,
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Knex from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
database: Knex;
|
||||
config: Config;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
};
|
||||
|
||||
@@ -22,13 +22,16 @@ import { Logger } from 'winston';
|
||||
import { createAuthProviderRouter } from '../providers';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DatabaseKeyStore, TokenFactory, createOidcRouter } from '../identity';
|
||||
import { NotFoundError } from '@backstage/backend-common';
|
||||
import {
|
||||
NotFoundError,
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
database: Knex;
|
||||
config: Config;
|
||||
basePath?: string;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
}
|
||||
|
||||
export async function createRouter(
|
||||
@@ -38,9 +41,7 @@ export async function createRouter(
|
||||
const logger = options.logger.child({ plugin: 'auth' });
|
||||
|
||||
const appUrl = options.config.getString('app.baseUrl');
|
||||
const backendUrl = options.config.getString('backend.baseUrl');
|
||||
// TODO(Rugvip): Replace with service discovery of external URL
|
||||
const authUrl = backendUrl + (options.basePath ?? '/api/auth');
|
||||
const authUrl = await options.discovery.getExternalBaseUrl('auth');
|
||||
|
||||
const keyDurationSeconds = 3600;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
createServiceBuilder,
|
||||
useHotMemoize,
|
||||
loadBackendConfig,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
export interface ServerOptions {
|
||||
@@ -34,6 +35,7 @@ export async function startStandaloneServer(
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'auth-backend' });
|
||||
const config = ConfigReader.fromConfigs(await loadBackendConfig());
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
|
||||
const database = useHotMemoize(module, () => {
|
||||
const knex = Knex({
|
||||
@@ -52,6 +54,7 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
config,
|
||||
database,
|
||||
discovery,
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
|
||||
@@ -17,16 +17,20 @@
|
||||
import { createRouter } from './router';
|
||||
import * as winston from 'winston';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
loadBackendConfig,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
describe('createRouter', () => {
|
||||
it('works', async () => {
|
||||
const logger = winston.createLogger();
|
||||
const config = ConfigReader.fromConfigs(await loadBackendConfig());
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const router = await createRouter({
|
||||
config,
|
||||
logger,
|
||||
pathPrefix: '/proxy',
|
||||
discovery,
|
||||
});
|
||||
expect(router).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -23,12 +23,12 @@ import createProxyMiddleware, {
|
||||
} from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
import http from 'http';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
config: Config;
|
||||
// The URL path prefix that the router itself is mounted as, commonly "/proxy"
|
||||
pathPrefix: string;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
}
|
||||
|
||||
export interface ProxyConfig extends ProxyMiddlewareConfig {
|
||||
@@ -76,16 +76,14 @@ export async function createRouter(
|
||||
): Promise<express.Router> {
|
||||
const router = Router();
|
||||
|
||||
const externalUrl = await options.discovery.getExternalBaseUrl('proxy');
|
||||
const { pathname: pathPrefix } = new URL(externalUrl);
|
||||
|
||||
const proxyConfig = options.config.getOptional('proxy') ?? {};
|
||||
Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
|
||||
router.use(
|
||||
route,
|
||||
buildMiddleware(
|
||||
options.pathPrefix,
|
||||
options.logger,
|
||||
route,
|
||||
proxyRouteConfig,
|
||||
),
|
||||
buildMiddleware(pathPrefix, options.logger, route, proxyRouteConfig),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
@@ -37,10 +38,11 @@ export async function startStandaloneServer(
|
||||
logger.debug('Creating application...');
|
||||
|
||||
const config = ConfigReader.fromConfigs(await loadBackendConfig());
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const router = await createRouter({
|
||||
config,
|
||||
logger,
|
||||
pathPrefix: '/proxy',
|
||||
discovery,
|
||||
});
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
PublisherBase,
|
||||
LocalPublish,
|
||||
} from '../techdocs';
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginEndpointDiscovery,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { DocsBuilder } from './helpers';
|
||||
|
||||
@@ -35,6 +38,7 @@ type RouterOptions = {
|
||||
generators: GeneratorBuilder;
|
||||
publisher: PublisherBase;
|
||||
logger: Logger;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
database?: Knex; // TODO: Make database required when we're implementing database stuff.
|
||||
config: Config;
|
||||
dockerClient: Docker;
|
||||
@@ -52,20 +56,25 @@ export async function createRouter({
|
||||
config,
|
||||
dockerClient,
|
||||
logger,
|
||||
discovery,
|
||||
}: RouterOptions): Promise<express.Router> {
|
||||
const router = Router();
|
||||
|
||||
router.get('/docs/:kind/:namespace/:name/*', async (req, res) => {
|
||||
const baseUrl = config.getString('backend.baseUrl');
|
||||
const storageUrl = config.getString('techdocs.storageUrl');
|
||||
|
||||
const { kind, namespace, name } = req.params;
|
||||
|
||||
const entity = (await (
|
||||
await fetch(
|
||||
`${baseUrl}/api/catalog/entities/by-name/${kind}/${namespace}/${name}`,
|
||||
)
|
||||
).json()) as Entity;
|
||||
const catalogUrl = await discovery.getBaseUrl('catalog');
|
||||
const triple = [kind, namespace, name].map(encodeURIComponent).join('/');
|
||||
|
||||
const catalogRes = await fetch(`${catalogUrl}/entities/by-name/${triple}`);
|
||||
if (!catalogRes.ok) {
|
||||
catalogRes.body.pipe(res.status(catalogRes.status));
|
||||
return;
|
||||
}
|
||||
|
||||
const entity: Entity = await catalogRes.json();
|
||||
|
||||
const docsBuilder = new DocsBuilder({
|
||||
preparers,
|
||||
@@ -80,7 +89,7 @@ export async function createRouter({
|
||||
await docsBuilder.build();
|
||||
}
|
||||
|
||||
return res.redirect(`${storageUrl}${req.path.replace('/docs', '')}`);
|
||||
res.redirect(`${storageUrl}${req.path.replace('/docs', '')}`);
|
||||
});
|
||||
|
||||
if (publisher instanceof LocalPublish) {
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createServiceBuilder } from '@backstage/backend-common';
|
||||
import {
|
||||
createServiceBuilder,
|
||||
SingleHostDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
@@ -39,6 +42,7 @@ export async function startStandaloneServer(
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'techdocs-backend' });
|
||||
const config = ConfigReader.fromConfigs([]);
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
|
||||
logger.debug('Creating application...');
|
||||
const preparers = new Preparers();
|
||||
@@ -61,6 +65,7 @@ export async function startStandaloneServer(
|
||||
publisher,
|
||||
dockerClient,
|
||||
config,
|
||||
discovery,
|
||||
});
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
|
||||
Reference in New Issue
Block a user