Merge pull request #1563 from spotify/shmidt-i/proxy-plugin

Proxy backend plugin
This commit is contained in:
Ivan Shmidt
2020-07-13 15:02:06 +02:00
committed by GitHub
17 changed files with 342 additions and 11 deletions
+7
View File
@@ -10,6 +10,13 @@ backend:
methods: [GET, POST, PUT, DELETE]
credentials: true
proxy:
'/circleci/api':
target: 'https://circleci.com/api/v1.1'
changeOrigin: true
pathRewrite:
'^/circleci/api/': '/'
organization:
name: Spotify
@@ -95,4 +95,13 @@ There are two things needed for a Backstage app to start making use of a plugin.
Luckily these two steps happen automatically when you create a plugin with the
Backstage CLI.
## Talking to the outside world
If your plugin needs to communicate with services outside the backstage
environment you will probably face challenges like CORS policies and/or
backend-side authorization. To smooth this process out you can use proxy -
either the one you already have (like nginx/haproxy/etc) or the proxy-backend
plugin that we provide for the backstage backend.
[Read more](../../plugins/proxy-backend/README.md)
[Back to Getting Started](README.md)
+4 -1
View File
@@ -78,7 +78,10 @@ export const apis = (config: ConfigApi) => {
);
builder.add(storageApiRef, WebStorage.create({ errorApi }));
builder.add(circleCIApiRef, new CircleCIApi());
builder.add(
circleCIApiRef,
new CircleCIApi(`${backendUrl}/proxy/circleci/api`),
);
builder.add(githubActionsApiRef, new GithubActionsClient());
builder.add(featureFlagsApiRef, new FeatureFlags());
+1
View File
@@ -18,6 +18,7 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.13",
"@backstage/backend-common": "^0.1.1-alpha.13",
"@backstage/catalog-model": "^0.1.1-alpha.13",
"@backstage/config": "^0.1.1-alpha.13",
+4 -1
View File
@@ -36,6 +36,7 @@ import identity from './plugins/identity';
import rollbar from './plugins/rollbar';
import scaffolder from './plugins/scaffolder';
import sentry from './plugins/sentry';
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import { PluginEnvironment } from './types';
@@ -65,6 +66,7 @@ async function main() {
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const identityEnv = useHotMemoize(module, () => createEnv('identity'));
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const service = createServiceBuilder(module)
@@ -83,7 +85,8 @@ async function main() {
)
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv));
.addRouter('/techdocs', await techdocs(techdocsEnv))
.addRouter('/proxy', await proxy(proxyEnv));
await service.start().catch(err => {
console.log(err);
+25
View File
@@ -0,0 +1,25 @@
/*
* 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.
*/
// @ts-ignore
import { createRouter } from '@backstage/plugin-proxy-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
config,
}: PluginEnvironment) {
return await createRouter({ logger, config });
}
-9
View File
@@ -10,15 +10,6 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"proxy": {
"/circleci/api": {
"target": "https://circleci.com/api/v1.1",
"changeOrigin": true,
"pathRewrite": {
"^/circleci/api/": "/"
}
}
},
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
+37
View File
@@ -0,0 +1,37 @@
# Proxy backend plugin
This is the backend plugin that enables proxy definitions to be declared in and read from app-config.yaml.
Relies on the `http-proxy-middleware` package.
## Getting Started
This backend plugin can be started in a standalone mode from directly in this package
with `yarn start`. However, it will have limited functionality and that process is
most convenient when developing the plugin itself.
To run it within the backend do:
1. Register the router in `packages/backend/src/index.ts`:
```
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const service = createServiceBuilder(module)
.loadConfig(configReader)
/** several different routers */
.addRouter('/', await proxy(proxyEnv));
```
2. Start the backend
```bash
yarn workspace example-backend start
```
This will launch the full example backend.
## Links
- (http-proxy-middleware)[https://www.npmjs.com/package/http-proxy-middleware]
- (The Backstage homepage)[https://backstage.io]
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@backstage/plugin-proxy-backend",
"version": "0.1.1-alpha.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.13",
"@backstage/config": "^0.1.1-alpha.13",
"@backstage/config-loader": "^0.1.1-alpha.13",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"http-proxy-middleware": "^0.19.1",
"morgan": "^1.10.0",
"node-fetch": "^2.6.0",
"uuid": "^8.0.0",
"winston": "^3.2.1",
"yaml": "^1.9.2",
"yn": "^4.0.0",
"yup": "^0.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.13",
"@types/node-fetch": "^2.5.7",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.0.0",
"@types/yup": "^0.28.2",
"jest-fetch-mock": "^3.0.3",
"supertest": "^4.0.2"
},
"files": [
"dist"
]
}
+17
View File
@@ -0,0 +1,17 @@
/*
* 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 * from './service/router';
+33
View File
@@ -0,0 +1,33 @@
/*
* 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 { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,32 @@
/*
* 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 { createRouter } from './router';
import winston from 'winston';
import { ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
describe('createRouter', () => {
it('works', async () => {
const logger = winston.createLogger();
const config = ConfigReader.fromConfigs(await loadConfig());
const router = await createRouter({
config,
logger,
});
expect(router).toBeDefined();
});
});
@@ -0,0 +1,38 @@
/*
* 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 express from 'express';
import Router from 'express-promise-router';
import createProxyMiddleware from 'http-proxy-middleware';
import { Logger } from 'winston';
export interface RouterOptions {
logger: Logger;
config: Config;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
const proxyConfig = options.config.get('proxy') ?? {};
Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
router.use(createProxyMiddleware(route, proxyRouteConfig));
});
return router;
}
@@ -0,0 +1,54 @@
/*
* 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 { createServiceBuilder } from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
import { ConfigReader } from '@backstage/config';
import { loadConfig } from '@backstage/config-loader';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'proxy-backend' });
logger.debug('Creating application...');
const config = ConfigReader.fromConfigs(await loadConfig());
const router = await createRouter({
config,
logger,
});
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
.addRouter('/proxy', router);
logger.debug('Starting application server...');
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
+19
View File
@@ -0,0 +1,19 @@
/*
* 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.
*/
require('jest-fetch-mock').enableMocks();
export {};
+10
View File
@@ -10173,6 +10173,16 @@ http-proxy-middleware@0.19.1:
lodash "^4.17.11"
micromatch "^3.1.10"
http-proxy-middleware@^0.19.1:
version "0.19.2"
resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz#ee73dcc8348165afefe8de2ff717751d181608ee"
integrity sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==
dependencies:
http-proxy "^1.18.1"
is-glob "^4.0.0"
lodash "^4.17.11"
micromatch "^3.1.10"
http-proxy@^1.17.0:
version "1.18.0"
resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"