Creating Backstage Scaffolder Custom Actions
Table of Contents:
1- Prerequisites and Installation
2- Software Templates in Backstage
3- Creating Custom Actions
4- Integrating your new action
5- Frequent Issue
6- Conclusion
1- Prerequisites and Installation
In this post, we will build upon the Backstage setup created in the 'Set Up Backstage' post.
2- Software Templates in Backstage
Backstage features a robust functionality known as Software Templates, which is designed to automate various processes. These templates are YAML files, similar to other entities within Backstage, but with special parameters, steps, and output objects. They are used to receiving user input, perform actions based on that input, and then produce output data for the user.
A basic example of a template could be:
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: create-repository
title: Create new repository
spec:
owner: backstage-developers
type: golden
parameters:
- title: New repository information
required:
- name
- description
properties:
title:
title: Name
type: string
description: Repository name
ui:autofocus: true
description:
title: Description
type: string
description: Repository description
steps:
- id: createRepository
name: Create repository
action: github:repo:create
input:
repoUrl: https://github.com/backstage/${{ parameters.name }}
description: ${{ parameters.description }}
output:
links:
- url: ${{ steps.createRepository.output.remoteUrl }}
title: 'Repository remote URL'
text:
- title: New repository created
content: Wohoo, you can start working at ${{ steps.createRepository.output.remoteUrl }}
The templates utilize React JSON Schema Form (RJSF) to render the input form. When defining template parameters, you can use RJSF options to customize the form's behavior and appearance.
To ensure that your custom template is available for selection when creating a new entity in Backstage, you need to register it in the app-config.yaml
file. This file is located in the root directory of your Backstage app.
Here's how you can register your template:
- Open the
app-config.yaml
file. - Add the following code snippet under the catalog - location :
- type: file
target: ../../Template/myCustomTemplate.yaml #your file name
rules:
- allow: [ Template ]
3- Creating Custom Actions
The template above uses only one action, github:repo:create
. Backstage comes with a variety of built-in actions, and you can include more from different plugins. Explore the repository containing all scaffolder-backend-module-*
plugins to discover additional actions that you can incorporate into your instance.
Additionally, you may find yourself needing to create your own actions. This becomes necessary when existing actions do not fulfill a specific requirement, such as integrating with internal systems or processing data within a template
To begin creating new actions, you need to create a new plugin using this command .
yarn new
Choose the type scaffolder-module
and provide a descriptive name. This name typically reflects the external system your actions will interact with, such as Jira or Jenkins, depending on the target of your actions.
Once the command completes, you'll find a new folder in your plugin's directory containing an example action.
Each action is created by using the createTemplateAction
function which takes parameters about the id of the action, its description, schema, and the actual action handler.
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { writeFile } from 'fs';
export const createNewFileAction = () => {
return createTemplateAction<{ contents: string; filename: string }>({
id: 'acme:file:create',
schema: {
input: {
required: ['contents', 'filename'],
type: 'object',
properties: {
contents: {
type: 'string',
title: 'Contents',
description: 'The contents of the file',
},
filename: {
type: 'string',
title: 'Filename',
description: 'The filename of the file that will be created',
},
},
},
},
async handler(ctx) {
const { signal } = ctx;
await writeFile(
`${ctx.workspacePath}/${ctx.input.filename}`,
ctx.input.contents,
{ signal },
_ => {},
);
},
});
};
Let’s start from the beginning. The action has an id
that is util:encode
. I highly recommend scoping the action names with :
into specific functionalities to make them easier to find in the Installed Actions section (by default /create/actions
) of your instance. It’s also good to give your action a proper description so that people know what it does.
Next is the schema. This describes what kind of inputs and outputs your action takes and produces.
Last, but not least, you have the actual handler of the action. This is an asynchronous method that does the work. It has one parameter, ctx
, which contains all data to be used in your action :
ctx.baseUrl
- a string where the template is locatedctx.logger
- a Winston logger for additional logging inside your actionctx.logStream
- a stream version of the logger if neededctx.workspacePath
- a string of the working directory of the template runctx.input
- an object which should match thezod
or JSON schema provided in theschema.input
part of the action definitionctx.output
- a function which you can call to set outputs that match the JSON schema orzod
inschema.output
for ex.ctx.output('downloadUrl', myDownloadUrl)
createTemporaryDirectory
a function to call to give you a temporary directory somewhere on the runner so you can store some files there rather than polluting theworkspacePath
ctx.metadata
- an object containing aname
field, indicating the template name. More metadata fields may be added later.
4- Integrating your new action
To make your action available for the templates, Backstage is transitioning to a new backend system, which involves some changes in how actions are added. To support this new system, you should create a new module.ts
file in your plugin and add your action there. Here's how you can do it:
import {scaffolderActionsExtensionPoint} from "@backstage/plugin-scaffolder-node/alpha";
import {createAcmeExampleAction} from "./actions/example/example";
import { createBackendModule } from '@backstage/backend-plugin-api';
export const scaffolderMyModule = createBackendModule({
pluginId: 'scaffolder',
moduleId: 'my-module',
register(reg) {
reg.registerInit({
deps: {
scaffolder: scaffolderActionsExtensionPoint,
},
async init({ scaffolder, config }) {
scaffolder.addActions(new createAcmeExampleAction());
},
});
},
});
then we should add this module to the backend
import {scaffolderMyModule} from "../../../plugins/scaffolder-backend-module-test/src/module";
backend.add(scaffolderMyModule)
Once you have installed your action, test it by modifying the example template above and adding a new step
steps:
- id: fileCreate
name: Fetch Base
action: acme:file:create
input:
contents: "test data"
filename: "test filename"
5- Frequent Issue
When using Node.js version 20, you may encounter an error when trying to execute certain actions in your Backstage app. To resolve this issue, you should execute the following command before starting your Backstage app:
export NODE_OPTIONS=--no-node-snapshot
6- Conclusion
🎉 And now you're all set! Just keep in mind that if you create actions that could be beneficial for other Backstage adopters, you might want to consider open-sourcing them for the benefit of the community! 🎉