diff --git a/plugins/scaffolder-backend-module-sentry/.eslintrc.js b/plugins/scaffolder-backend-module-sentry/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-sentry/README.md b/plugins/scaffolder-backend-module-sentry/README.md new file mode 100644 index 0000000000..cbab253bd9 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/README.md @@ -0,0 +1,160 @@ +# scaffolder-backend-module-sentry + +Welcome to the Setnry Module for Scaffolder. + +Here you can find all Sentry related features to imrpove your scaffolder: + +## Getting started + +You need to configure the action in your ackend: + +## From your Backstage root directory + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-sentry +``` + +Configure the action (you can check +the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to +see all options): + +```typescript +const actions = [ + createSentryCreateProjectAction({ + integrations, + reader: env.reader, + containerRunner, + }), +]; + +return await createRouter({ + containerRunner, + catalogClient, + actions, + logger: env.logger, + config: env.config, + database: env.database, + reader: env.reader, +}); +``` + +You need to define your Sentry API Token in your `app-config.yaml`: + +```yaml +scaffolder: + sentry: + token: ${SENTRY_TOKEN} +``` + +After that you can use the action in your template: + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: sentry-demo + title: Sentry template + description: scaffolder sentry app +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Fill in some steps + required: + - name + - owner + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + catalogFilter: + kind: Group + system: + title: System + type: string + description: System of the component + ui:field: EntityPicker + ui:options: + catalogFilter: + kind: System + defaultKind: System + + - title: Choose a location + required: + - repoUrl + - dryRun + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + dryRun: + title: Only perform a dry run, don't publish anything + type: boolean + default: false + + steps: + - id: fetch + name: Fetch + action: fetch:template + input: + url: https://github.com/TEMPLATE + values: + name: ${{ parameters.name }} + + - id: create-sentry-project + if: ${{ parameters.dryRun !== true }} + name: Create Sentry Project + action: sentry:create-project + input: + organizationSlug: ORG-SLUG + teamSlug: TEAM-SLUG + name: ${{ parameters.name }} + + - id: publish + if: ${{ parameters.dryRun !== true }} + name: Publish + action: publish:github + input: + allowedHosts: + - github.com + description: This is ${{ parameters.name }} + repoUrl: ${{ parameters.repoUrl }} + + - id: register + if: ${{ parameters.dryRun !== true }} + name: Register + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' + + - name: Results + if: ${{ parameters.dryRun }} + action: debug:log + input: + listWorkspace: true + + output: + links: + - title: Repository + url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} +``` diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json new file mode 100644 index 0000000000..8eaf5c398d --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -0,0 +1,48 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-sentry", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-scaffolder-backend": "workspace:^" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "*", + "cross-fetch": "^3.1.5", + "msw": "^0.49.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts new file mode 100644 index 0000000000..a3457ae159 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-backend'; +import { InputError } from '@backstage/errors'; +import { Config } from '@backstage/config'; + +/** + * Creates the `sentry:craete-project` Scaffolder action. + * + * @remarks + * + * See {@link https://backstage.io/docs/features/software-templates/writing-custom-actions}. + * + * @param options - Configuration of the Sentry API. + * @public + */ +export function createSentryCreateProjectAction(options: { config: Config }) { + const { config } = options; + + return createTemplateAction<{ + organizationSlug: string; + teamSlug: string; + name: string; + slug: string; + authToken?: string; + }>({ + id: 'sentry:create-project', + schema: { + input: { + required: ['organizationSlug', 'teamSlug', 'name'], + type: 'object', + properties: { + organizationSlug: { + title: 'The slug of the organization the team belongs to', + type: 'string', + }, + teamSlug: { + title: 'The slug of the team to create a new project for', + type: 'string', + }, + name: { + title: 'The name for the new project', + type: 'string', + }, + slug: { + title: + 'Optional slug for the new project. If not provided a slug is generated from the name', + type: 'string', + }, + authToken: { + title: + 'authenticate via bearer auth token. Requires scope: project:write', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + const { organizationSlug, teamSlug, name, slug, authToken } = ctx.input; + + const body: any = { + name: name, + }; + + if (slug) { + body.slug = slug; + } + + const token = authToken + ? authToken + : config.getOptionalString('scaffolder.sentry.token'); + + if (!token) { + throw new InputError(`No valid sentry token given`); + } + + const response = await fetch( + `https://sentry.io/api/0/teams/${organizationSlug}/${teamSlug}/projects/`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }, + ); + + const contentType = response.headers.get('content-type'); + + if (contentType !== 'application/json') { + throw new InputError( + `Unexpected Sentry Response Type: ${await response.text()}`, + ); + } + + const code = response.status; + const result = await response.json(); + + if (code !== 201) { + throw new InputError(`Sentry Response was: ${await result.detail}`); + } + + ctx.output('id', result.id); + ctx.output('result', result); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-sentry/src/index.ts b/plugins/scaffolder-backend-module-sentry/src/index.ts new file mode 100644 index 0000000000..f6c0820874 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { createSentryCreateProjectAction } from './actions/createProject';