diff --git a/packages/backend/package.json b/packages/backend/package.json index 773a4e317f..b1a0a68f15 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -20,6 +20,7 @@ "@backstage/backend-common": "^0.1.1-alpha.6", "@backstage/plugin-auth-backend": "^0.1.1-alpha.6", "@backstage/plugin-catalog-backend": "^0.1.1-alpha.6", + "@backstage/plugin-sentry-backend": "0.1.1-alpha.6", "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.6", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index ed458dedb3..bb37adbae9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -35,6 +35,7 @@ import helmet from 'helmet'; import knex from 'knex'; import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; +import sentry from './plugins/sentry'; import auth from './plugins/auth'; import { PluginEnvironment } from './types'; @@ -64,6 +65,10 @@ async function main() { app.use(requestLoggingHandler()); app.use('/catalog', await catalog(createEnv('catalog'))); app.use('/scaffolder', await scaffolder(createEnv('scaffolder'))); + app.use( + '/sentry', + await sentry(getRootLogger().child({ type: 'plugin', plugin: 'sentry' })), + ); app.use('/auth', await auth(createEnv('auth'))); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/packages/backend/src/plugins/sentry.ts b/packages/backend/src/plugins/sentry.ts new file mode 100644 index 0000000000..ddb7ddd540 --- /dev/null +++ b/packages/backend/src/plugins/sentry.ts @@ -0,0 +1,22 @@ +/* + * 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 '@backstage/plugin-sentry-backend'; +import { Logger } from 'winston'; + +export default async function(logger: Logger) { + return await createRouter(logger); +} diff --git a/plugins/sentry-backend/.eslintrc.js b/plugins/sentry-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/sentry-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/sentry-backend/README.md b/plugins/sentry-backend/README.md new file mode 100644 index 0000000000..412306dc8b --- /dev/null +++ b/plugins/sentry-backend/README.md @@ -0,0 +1,3 @@ +# sentry-backend + +Simple plugin forwarding requests to [Sentry](https://sentry.io) API. diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json new file mode 100644 index 0000000000..e9b7d01bf4 --- /dev/null +++ b/plugins/sentry-backend/package.json @@ -0,0 +1,34 @@ +{ + "name": "@backstage/plugin-sentry-backend", + "version": "0.1.1-alpha.5", + "main": "dist", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "tsc", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.5", + "axios": "^0.19.2", + "cors": "^2.8.5", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "helmet": "^3.22.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@types/jest": "^25.2.1", + "@types/node": "^12.0.0" + }, + "files": [ + "dist/**/*.{js,d.ts}" + ] +} diff --git a/plugins/sentry-backend/src/index.ts b/plugins/sentry-backend/src/index.ts new file mode 100644 index 0000000000..28c43aee33 --- /dev/null +++ b/plugins/sentry-backend/src/index.ts @@ -0,0 +1,16 @@ +/* + * 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'; diff --git a/plugins/sentry-backend/src/service/router.ts b/plugins/sentry-backend/src/service/router.ts new file mode 100644 index 0000000000..c6a0cbd593 --- /dev/null +++ b/plugins/sentry-backend/src/service/router.ts @@ -0,0 +1,35 @@ +/* + * 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 { Logger } from 'winston'; +import Router from 'express-promise-router'; +import express from 'express'; +import { SentryApiForwarder } from './sentry-api'; + +export async function createRouter( + rootLogger: Logger, +): Promise { + const router = Router(); + const sentryForwarder = new SentryApiForwarder(''); + const logger = rootLogger.child({ plugin: 'sentry' }); + + router.get('*', (req, res) => sentryForwarder.fowardRequest(req, res)); + + const app = express(); + app.set('logger', logger); + app.use('/', router); + + return app; +} diff --git a/plugins/sentry-backend/src/service/sentry-api.ts b/plugins/sentry-backend/src/service/sentry-api.ts new file mode 100644 index 0000000000..f80ad5ea7f --- /dev/null +++ b/plugins/sentry-backend/src/service/sentry-api.ts @@ -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 express from 'express'; +import axios from 'axios'; + +export class SentryApiForwarder { + constructor(private token: string) {} + public fowardRequest(request: express.Request, response: express.Response) { + const sentryUrl = request.path; + axios + .get(`https://sentry.io/${sentryUrl}`, { + headers: { + Authorization: `Bearer ${this.token}`, + }, + }) + .then(res => { + response.send(res.data); + }) + .catch(err => { + return response.status(err.response.status).json({ + detail: err.response.statusText, + }); + }); + } +} diff --git a/plugins/sentry-backend/src/service/standaloneApplication.ts b/plugins/sentry-backend/src/service/standaloneApplication.ts new file mode 100644 index 0000000000..fdaad8bb2e --- /dev/null +++ b/plugins/sentry-backend/src/service/standaloneApplication.ts @@ -0,0 +1,42 @@ +/* + * 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 { + errorHandler, + notFoundHandler, + requestLoggingHandler, +} from '@backstage/backend-common'; +import cors from 'cors'; +import express from 'express'; +import helmet from 'helmet'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export async function createStandaloneApplication( + logger: Logger, +): Promise { + const app = express(); + + app.use(helmet()); + app.use(cors()); + app.use(express.json()); + app.use(requestLoggingHandler()); + app.use('/', await createRouter(logger)); + app.use(notFoundHandler()); + app.use(errorHandler()); + + return app; +} diff --git a/plugins/sentry-backend/src/service/standaloneServer.ts b/plugins/sentry-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..3fc55fb7d0 --- /dev/null +++ b/plugins/sentry-backend/src/service/standaloneServer.ts @@ -0,0 +1,43 @@ +/* + * 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 { Server } from 'http'; +import { Logger } from 'winston'; +import { createStandaloneApplication } from './standaloneApplication'; + +const PORT = 5009; + +export async function startStandaloneServer( + parentLogger: Logger, +): Promise { + const logger = parentLogger.child({ service: 'scaffolder-backend' }); + logger.debug('Creating application...'); + + const app = await createStandaloneApplication(logger); + + logger.debug('Starting application server...'); + return await new Promise((resolve, reject) => { + const server = app.listen(PORT, (err?: Error) => { + if (err) { + reject(err); + return; + } + + logger.info(`Listening on port ${PORT}`); + resolve(server); + }); + }); +} diff --git a/plugins/sentry-backend/tsconfig.json b/plugins/sentry-backend/tsconfig.json new file mode 100644 index 0000000000..7d4ea182e2 --- /dev/null +++ b/plugins/sentry-backend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../packages/backend/tsconfig.json", + "include": [ + "./src" + ], + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "skipLibCheck": true + } +}