diff --git a/.changeset/spotty-pandas-deny.md b/.changeset/spotty-pandas-deny.md new file mode 100644 index 0000000000..c3da6afe2b --- /dev/null +++ b/.changeset/spotty-pandas-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +- Move out the `cookiecutter` templating to its own module that is depended on by the `scaffolder-backend` plugin. No breaking change yet, but we will drop first class support for `cookiecutter` in the future and it will become an opt-in feature. diff --git a/plugins/scaffolder-backend-module-cookiecutter/.eslintrc.js b/plugins/scaffolder-backend-module-cookiecutter/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/scaffolder-backend-module-cookiecutter/README.md b/plugins/scaffolder-backend-module-cookiecutter/README.md new file mode 100644 index 0000000000..77ae93c5f3 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/README.md @@ -0,0 +1,162 @@ +# scaffolder-backend-module-cookiecutter + +Welcome to the `fetch:cookiecutter` action for the `scaffolder-backend`. + +## Getting started + +You need to configure the action in your backend: + +## From your Backstage root directory + +``` +cd packages/backend +yarn add @backstage/plugin-scaffolder-backend-module-cookiecutter +``` + +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 +// packages/backend/src/plugins/scaffolder.ts + +const actions = [ + createFetchCookiecutterAction({ + integrations, + reader, + containerRunner, + }), + ...createBuiltInActions({ + ... + }) +]; + +return await createRouter({ + containerRunner, + logger, + config, + database, + catalogClient, + reader, + actions, +}); +``` + +After that you can use the action in your template: + +```yaml +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: cookiecutter-demo + title: Cookiecutter Test + description: Cookiecutter example +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: + allowedKinds: + - Group + system: + title: System + type: string + description: System of the component + ui:field: EntityPicker + ui:options: + allowedKinds: + - 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-base + name: Fetch Base + action: fetch:cookiecutter + input: + url: ./template + values: + name: '{{ parameters.name }}' + owner: '{{ parameters.owner }}' + system: '{{ parameters.system }}' + destination: '{{ parseRepoUrl parameters.repoUrl }}' + + - id: publish + if: '{{ not parameters.dryRun }}' + name: Publish + action: publish:github + input: + allowedHosts: ['github.com'] + description: 'This is {{ parameters.name }}' + repoUrl: '{{ parameters.repoUrl }}' + + - id: register + if: '{{ not parameters.dryRun }}' + 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 }}' +``` + +You can also visit the `/create/actions` route in your Backstage application to find out more about the parameters this action accepts when it's installed to configure how you like. + +### Environment setup + +The environment needs to have either `cookiecutter` installed and be available in the `PATH` or access to a `docker` daemon so it can spin up a docker container with `cookiecutter` available. + +If you are running Backstage from a Docker container and you want to avoid calling a container inside a container, you can set up `cookiecutter` in your own image, this will use the local installation instead. + +You can do so by including the following lines in the last step of your Dockerfile: + +```dockerfile +RUN apt-get update && apt-get install -y python3 python3-pip +RUN pip3 install cookiecutter +``` diff --git a/plugins/scaffolder-backend-module-cookiecutter/api-report.md b/plugins/scaffolder-backend-module-cookiecutter/api-report.md new file mode 100644 index 0000000000..8e46667fce --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/api-report.md @@ -0,0 +1,23 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-cookiecutter" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ContainerRunner } from '@backstage/backend-common'; +import { ScmIntegrations } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-backend'; +import { UrlReader } from '@backstage/backend-common'; + +// Warning: (ae-missing-release-tag) "createFetchCookiecutterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function createFetchCookiecutterAction(options: { + reader: UrlReader; + integrations: ScmIntegrations; + containerRunner: ContainerRunner; +}): TemplateAction; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json new file mode 100644 index 0000000000..f321d6b45c --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", + "version": "0.1.0", + "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.8.6", + "@backstage/errors": "^0.1.1", + "@backstage/integration": "^0.5.7", + "@backstage/plugin-scaffolder-backend": "^0.14.0", + "@backstage/config": "^0.1.5", + "command-exists": "^1.2.9", + "fs-extra": "10.0.0", + "winston": "^3.2.1", + "cross-fetch": "^3.0.6", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.7.3", + "@types/fs-extra": "^9.0.1", + "@types/mock-fs": "^4.13.0", + "@types/jest": "^26.0.7", + "@types/command-exists": "^1.2.0", + "mock-fs": "^4.13.0", + "msw": "^0.29.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts rename to plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index 86f953a6a5..bc3fe3eca2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -17,9 +17,12 @@ const runCommand = jest.fn(); const commandExists = jest.fn(); const fetchContents = jest.fn(); -jest.mock('./helpers', () => ({ fetchContents })); +jest.mock('@backstage/plugin-scaffolder-backend', () => ({ + ...jest.requireActual('@backstage/plugin-scaffolder-backend'), + fetchContents, + runCommand, +})); jest.mock('command-exists', () => commandExists); -jest.mock('../helpers', () => ({ runCommand })); import { getVoidLogger, @@ -33,7 +36,7 @@ import os from 'os'; import { PassThrough } from 'stream'; import { createFetchCookiecutterAction } from './cookiecutter'; import { join } from 'path'; -import { ActionContext } from '../../types'; +import type { ActionContext } from '@backstage/plugin-scaffolder-backend'; describe('fetch:cookiecutter', () => { const integrations = ScmIntegrations.fromConfig( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts similarity index 94% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts rename to plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index 048b2bba64..1f6fbdb7d8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -26,9 +26,11 @@ import commandExists from 'command-exists'; import fs from 'fs-extra'; import path, { resolve as resolvePath } from 'path'; import { Writable } from 'stream'; -import { runCommand } from '../helpers'; -import { createTemplateAction } from '../../createTemplateAction'; -import { fetchContents } from './helpers'; +import { + runCommand, + createTemplateAction, + fetchContents, +} from '@backstage/plugin-scaffolder-backend'; export class CookiecutterRunner { private readonly containerRunner: ContainerRunner; @@ -136,7 +138,7 @@ export function createFetchCookiecutterAction(options: { }>({ id: 'fetch:cookiecutter', description: - "Downloads a template from the given URL into the workspace, and runs cookiecutter on it. This action is deprecated in favor of 'fetch:template'. See https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetch-cookiecutter-to-fetch-template for more details.", + 'Downloads a template from the given URL into the workspace, and runs cookiecutter on it.', schema: { input: { type: 'object', diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/index.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/index.ts new file mode 100644 index 0000000000..bbe5a16a04 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { createFetchCookiecutterAction } from './cookiecutter'; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/index.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/index.ts new file mode 100644 index 0000000000..1b47db9e03 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/index.ts @@ -0,0 +1,16 @@ +/* + * 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 * from './fetch'; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/index.ts b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts new file mode 100644 index 0000000000..4f06b14a86 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts @@ -0,0 +1,16 @@ +/* + * 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 * from './actions'; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index f29274cc24..0e7a25534c 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -8,6 +8,7 @@ import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { ContainerRunner } from '@backstage/backend-common'; +import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import express from 'express'; import { JsonObject } from '@backstage/config'; @@ -78,14 +79,7 @@ export function createCatalogWriteAction(): TemplateAction; // @public export function createDebugLogAction(): TemplateAction; -// Warning: (ae-missing-release-tag) "createFetchCookiecutterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createFetchCookiecutterAction(options: { - reader: UrlReader; - integrations: ScmIntegrations; - containerRunner: ContainerRunner; -}): TemplateAction; +export { createFetchCookiecutterAction }; // Warning: (ae-missing-release-tag) "createFetchPlainAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 8f10c9fa53..e44a22403c 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -35,6 +35,7 @@ "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.0", "@gitbeaker/core": "^30.2.0", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 6541625035..ae5a7d0697 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -24,11 +24,8 @@ import { } from './catalog'; import { createDebugLogAction } from './debug'; -import { - createFetchCookiecutterAction, - createFetchPlainAction, - createFetchTemplateAction, -} from './fetch'; +import { createFetchPlainAction, createFetchTemplateAction } from './fetch'; +import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; import { createFilesystemDeleteAction, createFilesystemRenameAction, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts index fea32df39c..c9a16c274e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/index.ts @@ -15,6 +15,5 @@ */ export { createFetchPlainAction } from './plain'; -export { createFetchCookiecutterAction } from './cookiecutter'; export { createFetchTemplateAction } from './template'; export { fetchContents } from './helpers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 849b056ac9..2a583f369a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -20,4 +20,6 @@ export * from './debug'; export * from './fetch'; export * from './filesystem'; export * from './publish'; + +export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; export { runCommand } from './helpers';