Nearshore Digital Lab

Stay tuned

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.

jamie@example.com

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.

Set Up Backstage
Backstage is an open platform designed for companies to build developer portals for managing their software assets. Developing your portal using Backstage is a straightforward process. Simply create an app and configure it according to your requirements. Table of Content: I - Prerequisites and installation II - Create your Backstage

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:

  1. Open the app-config.yaml file.
  2. 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 located
  • ctx.logger - a Winston logger for additional logging inside your action
  • ctx.logStream - a stream version of the logger if needed
  • ctx.workspacePath - a string of the working directory of the template run
  • ctx.input - an object which should match the zod or JSON schema provided in the schema.input part of the action definition
  • ctx.output - a function which you can call to set outputs that match the JSON schema or zod in schema.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 the workspacePath
  • ctx.metadata - an object containing a name 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! 🎉

Latest issue