If our goal is to make our lives and the lives of those that work with us easier and happier, Backstage Software Templates can give us a hand. This tool can help us create Components inside Backstage that will load code skeletons, fill in some variables and publish that template somewhere.

Let’s get this party started

If you’ve been following along, in Backstage intro we set up our Backstage installation locally and, conveniently, bootstrapped a lot of components, including Software Templates. Let’s right to it.

We’ll start by selecting Create Component.

We’ll then select Golang Microservice.

We’ll be displayed a form requesting some necessary information. We’ll fill it in and click Next Step.

A new form will be displayed that will allow us to set up VCS integration.

At last, we will be shown a review and we can proceed with Create.

And Backstage will do its magic.

A new component will be created.

As well as a new repo with a lot of code.

Let’s take a moment to take a look around and let that sink in.

What just happened?

Based on a template, Backstage was able to create a new repo, full of code and integrations. The setup we did in Backstage intro brought all of that free of charge. Taking a look at app.config.yaml we can see:

catalog:
  rules:
    - allow: [Component, System, API, Group, User, Template, Location]
  locations:
    - type: url
      target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
      rules:
        - allow: [Template]

This points to the following template:

apiVersion: backstage.io/v1alpha1
kind: Template
metadata:
  name: golang-starter
  title: Golang Microservice
  description: Create a Golang repo with this template built by members of the Go community
  tags:
    - experimental
    - go
spec:
  owner: web@example.com
  templater: cookiecutter
  type: service
  path: "."
  schema:
    required:
      - component_id
      - project_short_description
      - docker_image
      - docker_build_image
      - docker_build_image_version
      - use_logrus_logging
      - use_viper_config
      - use_ci
      - use_cobra_cmd
    properties:
      component_id:
        title: Name
        type: string
        description: Unique name of the component
      project_short_description:
        title: Description
        type: string
        description: Description of the component
      docker_image:
        title: Docker Image
        type: string
        description: The docker base image to use when running the service
        default: alpine-base-image:latest
      docker_build_image:
        title: Docker Build Image
        type: string
        description: The docker base image to use when building the service
        default: golang
      docker_build_image_version:
        title: Docker Build Image Version
        description: The image version to use when building the service
        type: string
        enum:
          - alpine
        default: alpine
      use_logrus_logging:
        title: Enable Logrus Logging (https://github.com/sirupsen/logrus)
        type: string
        enum:
          - "y"
          - "n"
        default: "y"
      use_viper_config:
        title: Enable Viper Config (https://github.com/spf13/viper)
        type: string
        enum:
          - "y"
          - "n"
        default: "y"
      use_cobra_cmd:
        title: Enable Cobra CLI Tools (https://github.com/spf13/cobra)
        type: string
        enum:
          - "y"
          - "n"
        default: "y"
      use_ci:
        title: Add CI
        description: Add a CI config to the repo, Gitub Actions, Circle or Travis are the only supported right now
        type: string
        enum:
          - github
          - travis
          - circle
          - none
        default: github

Now something has to interpret this and do some magic. This is where packages/backend/src/plugins/scaffolder.ts comes into play:

import { SingleHostDiscovery } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import {
  CookieCutter,
  CreateReactAppTemplater,
  createRouter,
  Preparers,
  Publishers,
  Templaters
} from '@backstage/plugin-scaffolder-backend';
import Docker from 'dockerode';
import { Router } from 'express';
import type { PluginEnvironment } from '../types';

export default async function createPlugin({
  logger,
  config,
  database,
  reader,
}: PluginEnvironment): Promise<Router> {
  const cookiecutterTemplater = new CookieCutter();
  const craTemplater = new CreateReactAppTemplater();
  const templaters = new Templaters();

  templaters.register('cookiecutter', cookiecutterTemplater);
  templaters.register('cra', craTemplater);

  const preparers = await Preparers.fromConfig(config, { logger });
  const publishers = await Publishers.fromConfig(config, { logger });

  const dockerClient = new Docker();

  const discovery = SingleHostDiscovery.fromConfig(config);
  const catalogClient = new CatalogClient({ discoveryApi: discovery });

  return await createRouter({
    preparers,
    templaters,
    publishers,
    logger,
    config,
    dockerClient,
    database,
    catalogClient,
    reader
  });
}

@backstage/plugin-scaffolder-backend through CookieCutter “knows” how to bootstrap Golang projects based on cookiecutter-golang. And that’s how all the magic happens.

Eventually, we will need to add our own templates and we will need help writing them. And if the builtin actions are not enough this might include writing custom actions.

Backstage Software Templates takes a big leap into providing standardization and help teams become more productive faster.