Getting Started with Backstage
This article will teach you how to use Backstage in your app development and create software templates to generate a typical Spring Boot app. Backstage is an open-source framework for building developer portals. It allows us to automate the creation of the infrastructure, CI/CD, and operational knowledge needed to run an application or product. It offers a centralized software catalog and unifies all infrastructure tooling, services, and documentation within a single and intuitive UI. With Backstage, we can create any new software component, such as a new microservice, just with a few clicks. Developers can choose between several standard templates. Platform engineers will create such templates to meet the organization’s best practices.
From the technical point of view, Backstage is a web frontend designed to run on Node.js. It is mostly written in TypeScript using the React framework. It has an extensible nature. Each time we need to integrate Backstage with some third-party software we need to install a dedicated plugin. Plugins are essentially individually packaged React components. Today you will learn how to install and configure plugins to integrate with GitHub, CircleCI, and Sonarqube.
It is the first article about Backstage on my blog. You can expect more in the future. However, before proceeding with this article, it is worth reading the following post. It explains how I create my repositories on GitHub and what tools I’m using to check the quality of the code and be up-to-date.
Source Code
If you would like to try it by yourself, you may always take a look at my source code. This time, there are two sample Git repositories. The first of them contains software templates written in the Backstage technology called Skaffolder. On the other hand, the second repository contains the source code of the simple Spring Boot generated from the Backstage template. Once you clone both of those repos, you should just follow my further instructions.
Writing Software Templates in Backstage
We can create our own software templates using YAML notation or find existing examples on the web. Such software templates are very similar to the definition of Kubernetes objects. They have the apiVersion
, kind
(Template
) fields, metadata
, and spec
fields. Each template must define a list of input variables and then a list of actions that the scaffolding service executes.
The Structure of the Repository with Software Templates
Let’s take a look at the structure of the repository containing our Spring Boot template. As you see, there is the template.yaml
file with the software template YAML manifest and the skeleton
directory with our app source code. Besides Java files, there is the Maven pom.xml
, Renovate and CircleCI configuration manifests. The Scaffolder template input parameters determine the name of Java classes or packages. In the Skaffolder template, we set the default base package name and a domain object name. The catalog-info.yaml
file contains a definition of the object required to register the app in the software catalog. As you can see, we are also parametrizing the names of the files with the domain object names.
.
├── skeleton
│  ├── .circleci
│  │  └── config.yml
│  ├── README.md
│  ├── catalog-info.yaml
│  ├── pom.xml
│  ├── renovate.json
│  └── src
│  ├── main
│  │  ├── java
│  │  │  └── ${{values.javaPackage}}
│  │  │  ├── Application.java
│  │  │  ├── controller
│  │  │  │  └── ${{values.domainName}}Controller.java
│  │  │  └── domain
│  │  │  └── ${{values.domainName}}.java
│  │  └── resources
│  │  └── application.yml
│  └── test
│  └── java
│  └── ${{values.javaPackage}}
│  └── ${{values.domainName}}ControllerTests.java
└── template.yaml
13 directories, 11 files
ShellSessionBuilding Templates with Scaffolder
Now, let’s take a look at the most important element in our repository – the template manifest. It defines several input parameters with default values. We can set the name of our app (appName
), choose a default branch name inside the Git repository (repoBranchName
), Maven group ID (groupId
), the name of the default Java package (javaPackage
), or the base REST API controller path (apiPath
). All these parameters are then used during code generation. If you need more customization in the templates, you should add other parameters in the manifest.
The steps
section in the manifest defines the actions required to create a new app in Backstage. We are doing three things here. In the first step, we need to generate the Spring Boot app source by filling the templates inside the skeleton
directory with parameters defined in the Scaffolder manifest. Then, we are publishing the generated code in the newly created GitHub repository. The name of the repository is the same as the app name (the appName
parameter). The owner of the GitHub repository is determined by the values of the orgName
parameter. Finally, we are registering the new component in the Backstage catalog by calling the catalog:register
action.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spring-boot-basic-template
title: Create a Spring Boot app
description: Create a Spring Boot app
tags:
- spring-boot
- java
- maven
- circleci
- renovate
- sonarqube
spec:
owner: piomin
system: piomin
type: service
parameters:
- title: Provide information about the new component
required:
- orgName
- appName
- domainName
- repoBranchName
- groupId
- javaPackage
- apiPath
- description
properties:
orgName:
title: Organization name
type: string
default: piomin
appName:
title: App name
type: string
default: sample-spring-boot-app
domainName:
title: Name of the domain object
type: string
default: Person
repoBranchName:
title: Name of the branch in the Git repository
type: string
default: master
groupId:
title: Maven Group ID
type: string
default: pl.piomin.services
javaPackage:
title: Java package directory
type: string
default: pl/piomin/services
apiPath:
title: REST API path
type: string
default: /api/v1
description:
title: Description
type: string
default: Sample Spring Boot App
steps:
- id: sourceCodeTemplate
name: Generating the Source Code Component
action: fetch:template
input:
url: ./skeleton
values:
orgName: ${{ parameters.orgName }}
appName: ${{ parameters.appName }}
domainName: ${{ parameters.domainName }}
groupId: ${{ parameters.groupId }}
javaPackage: ${{ parameters.javaPackage }}
apiPath: ${{ parameters.apiPath }}
- id: publish
name: Publishing to the Source Code Repository
action: publish:github
input:
allowedHosts: ['github.com']
description: ${{ parameters.description }}
repoUrl: github.com?owner=${{ parameters.orgName }}&repo=${{ parameters.appName }}
defaultBranch: ${{ parameters.repoBranchName }}
repoVisibility: public
- id: register
name: Registering the Catalog Info Component
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
output:
links:
- title: Open the Source Code Repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Open the Catalog Info Component
icon: catalog
entityRef: ${{ steps.register.output.entityRef }}
YAMLGenerating Spring Boot Source Code
Here’s the template of the Maven pom.xml
. Our app uses the current latest version of the Spring Boot framework and Java 21 for compilation. The Maven groupId
and artifactId
are taken from the Scaffolder template parameters. The pom.xml
file also contains data required to integrate with a specific project on Sonarcloud (sonar.projectKey
and sonar.organization
).
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>${{ values.groupId }}</groupId>
<artifactId>${{ values.appName }}</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>
<properties>
<sonar.projectKey>${{ values.orgName }}_${{ values.appName }}</sonar.projectKey>
<sonar.organization>${{ values.orgName }}</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.instancio</groupId>
<artifactId>instancio-junit</artifactId>
<version>4.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<java.target>${java.version}</java.target>
<time>${maven.build.timestamp}</time>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
XMLThere are several Java classes generated during bootstrap. Here’s the @RestController
class template. It uses three parameters defined in the Scaffolder template: groupId
, domainName
and apiPath
. It imports the domain object class and exposes REST methods for CRUD operations. As you see, the implementation is very simple. It just uses the in-memory Java List
to store the domain objects. However, it perfectly shows the idea behind Scaffolder templates.
package ${{ values.groupId }}.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import ${{ values.groupId }}.domain.${{ values.domainName }};
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("${{ values.apiPath }}")
public class ${{ values.domainName }}Controller {
private final Logger LOG = LoggerFactory.getLogger(${{ values.domainName }}Controller.class);
private final List<${{ values.domainName }}> objs = new ArrayList<>();
@GetMapping
public List<${{ values.domainName }}> findAll() {
return objs;
}
@GetMapping("/{id}")
public ${{ values.domainName }} findById(@PathVariable("id") Long id) {
${{ values.domainName }} obj = objs.stream().filter(it -> it.getId().equals(id))
.findFirst()
.orElseThrow();
LOG.info("Found: {}", obj.getId());
return obj;
}
@PostMapping
public ${{ values.domainName }} add(@RequestBody ${{ values.domainName }} obj) {
obj.setId((long) (objs.size() + 1));
LOG.info("Added: {}", obj);
objs.add(obj);
return obj;
}
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") Long id) {
${{ values.domainName }} obj = objs.stream().filter(it -> it.getId().equals(id)).findFirst().orElseThrow();
objs.remove(obj);
LOG.info("Removed: {}", id);
}
@PutMapping
public void update(@RequestBody ${{ values.domainName }} obj) {
${{ values.domainName }} objTmp = objs.stream()
.filter(it -> it.getId().equals(obj.getId()))
.findFirst()
.orElseThrow();
objs.set(objs.indexOf(objTmp), obj);
LOG.info("Updated: {}", obj.getId());
}
}
JavaThen, we can generate a test class to verify @RestController
endpoints. The app is starting on the random port during the JUnit tests. In the first test, we are adding a new object into the store. Then we are verifying the GET /{id}
endpoint works fine. Finally, we are removing the object from the store by calling the DELETE /{id}
endpoint.
package ${{ values.groupId }};
import org.instancio.Instancio;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import ${{ values.groupId }}.domain.${{ values.domainName }};
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ${{ values.domainName }}ControllerTests {
private static final String API_PATH = "${{values.apiPath}}";
@Autowired
private TestRestTemplate restTemplate;
@Test
@Order(1)
void add() {
${{ values.domainName }} obj = restTemplate.postForObject(API_PATH, Instancio.create(${{ values.domainName }}.class), ${{ values.domainName }}.class);
assertNotNull(obj);
assertEquals(1, obj.getId());
}
@Test
@Order(2)
void findAll() {
${{ values.domainName }}[] objs = restTemplate.getForObject(API_PATH, ${{ values.domainName }}[].class);
assertTrue(objs.length > 0);
}
@Test
@Order(2)
void findById() {
${{ values.domainName }} obj = restTemplate.getForObject(API_PATH + "/{id}", ${{ values.domainName }}.class, 1L);
assertNotNull(obj);
assertEquals(1, obj.getId());
}
@Test
@Order(3)
void delete() {
restTemplate.delete(API_PATH + "/{id}", 1L);
${{ values.domainName }} obj = restTemplate.getForObject(API_PATH + "/{id}", ${{ values.domainName }}.class, 1L);
assertNull(obj.getId());
}
}
JavaIntegrate with CircleCI and Renovate
Once I create a new repository on GitHub I want to automatically integrate it with CircleCI builds. I also want to update Maven dependencies versions automatically with Renovate to keep the project up to date. Since my GitHub account is connected to the CircleCI account and the Renovate app is installed there I just need to provide two configuration manifests inside the generated repository. Here’s the CircleCI config.yaml
file. It runs the Maven build with JUnit tests and performs the Sonarqube scan in the sonarcloud.io
portal.
version: 2.1
jobs:
analyze:
docker:
- image: 'cimg/openjdk:21.0.2'
steps:
- checkout
- run:
name: Analyze on SonarCloud
command: mvn verify sonar:sonar
executors:
jdk:
docker:
- image: 'cimg/openjdk:21.0.2'
orbs:
maven: circleci/maven@1.4.1
workflows:
maven_test:
jobs:
- maven/test:
executor: jdk
- analyze:
context: SonarCloud
YAMLHere’s the renovate.json
manifest. Renovate will create a PR in GitHub each time it detects a new version of Maven dependency.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",":dependencyDashboard"
],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
}
],
"prCreation": "not-pending"
}
YAMLRegister a new Component in the Software Catalog
Once we generate the whole code and publish it as the GitHub repository, we need to register a new component in the Backstage catalog. In order to achieve this, our repository needs to contain the Component
manifest as shown below. Once again, we need to fill it with the parameter values during the bootstrap phase. It contains a reference to the CircleCI and Sonarqube projects and a generated GitHub repository in the annotations
section. Here’s the catalog-info.yaml
template:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ values.appName }}
title: ${{ values.appName }}
annotations:
circleci.com/project-slug: github/${{ values.orgName }}/${{ values.appName }}
github.com/project-slug: ${{ values.orgName }}/${{ values.appName }}
sonarqube.org/project-key: ${{ values.orgName }}_${{ values.appName }}
tags:
- spring-boot
- java
- maven
- circleci
- renovate
- sonarqube
spec:
type: service
owner: piotr.minkowski@gmail.com
lifecycle: experimental
YAMLRunning Backstage
Once we have the whole template ready, we can proceed to run the Backstage on our local machine. As I mentioned before, Backstage is a Node.js app, so we need to have several tools installed to be able to run it. By the way, the list of prerequisites is pretty large. You can find it under the following link. First of all, I had to downgrade the version of Node.js from the latest 22 to 18. We also need to install yarn and npx. If you have the following versions of those tools, you shouldn’t have any problems with running Backstage according to further instructions.
$ node --version
v18.20.3
$ npx --version
10.7.0
$ yarn --version
1.22.22
ShellSessionRunning a Standalone Server Locally
We are going to run Backstage locally in the development mode as a standalone server. In order to achieve this, we first need to run the following command. It will create a new directory with a Backstage app inside.Â
$ npx @backstage/create-app@latest
ShellSessionThis may take some time. But if you see a similar result, it means that your instance is ready. However, before we start it we need to install some plugins and include some configuration settings. In that case, the name of our instance is backstage1
.
Firstly, we should go to the backstage1
directory and take a look at the project structure. The most important elements for us are: the app-config.yaml
file with configuration and the packages
directory with the source code of the backend and frontend (app
) modules.
├── README.md
├── app-config.local.yaml
├── app-config.production.yaml
├── app-config.yaml
├── backstage.json
├── catalog-info.yaml
├── dist-types
├── examples
├── lerna.json
├── node_modules
├── package.json
├── packages
│ ├── app
│ └── backend
├── playwright.config.ts
├── plugins
├── tsconfig.json
└── yarn.lock
ShellSessionThe app is not configured according to our needs yet. However, we can run it with the following command just to try it out:
$ yarn dev
ShellSessionWe can visit the UI available under the http://localhost:3000
:
Provide Configuration and Install Plugins
In the first, we will analyze the app-config.yaml
and add some configuration settings there. I will focus only on the aspects important to our exercise. The default configuration comes with enabled built-in integration with GitHub. We just need to generate a personal access token in GitHub and provide it as the GITHUB_TOKEN
environment variable (1). Then, we need to integrate with Sonarqube also through the access token (2). Our portal will also display a list of CircleCI builds. Therefore, we need to include the CircleCI token as well (3). Finally, we should include the URL address of our custom Scaffolder template in the catalog
section (4). It is located in our sample GitHub repository: https://github.com/piomin/backstage-templates/blob/master/templates/spring-boot-basic/skeleton/catalog-info.yaml.
app:
title: Scaffolded Backstage App
baseUrl: http://localhost:3000
organization:
name: piomin
backend:
baseUrl: http://localhost:7007
listen:
port: 7007
csp:
connect-src: ["'self'", 'http:', 'https:']
cors:
origin: http://localhost:3000
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
credentials: true
database:
client: better-sqlite3
connection: ':memory:'
# (1)
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
# (2)
sonarqube:
baseUrl: https://sonarcloud.io
apiKey: ${SONARCLOUD_TOKEN}
# (3)
proxy:
'/circleci/api':
target: https://circleci.com/api/v1.1
headers:
Circle-Token: ${CIRCLECI_TOKEN}
auth:
providers:
guest: {}
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location]
locations:`
- type: file
target: ../../examples/entities.yaml
- type: file
target: ../../examples/template/template.yaml
rules:
- allow: [Template]
# (4)
- type: url
target: https://github.com/piomin/backstage-templates/blob/master/templates/spring-boot-basic/template.yaml
rules:
- allow: [ Template ]
- type: file
target: ../../examples/org.yaml
rules:
- allow: [User, Group]
YAMLSo, before starting Backstage we need to export both GitHub and Sonarqube tokens.
$ export GITHUB_TOKEN=<YOUR_GITHUB_TOKEN>
$ export SONARCLOUD_TOKEN=<YOUR_SONARCLOUD_TOKEN>
$ export CIRCLECI_TOKEN=<YOUR_CIRCLECI_TOKEN>
ShellSessionFor those of you who didn’t generate Sonarcloud tokens before:
And similar operation for CicleCI:
Unfortunately, that is not all. Now, we need to install several required plugins. To be honest with you, plugin installation in Backstage is quite troublesome. Usually, we not only need to install such a plugin with yarn
but also provide some changes in the packages
directory. Let’s begin!
Enable GitHub Integration
Although integration with GitHub is enabled by default in the configuration settings we still need to install the plugin to be able to make some actions related to repositories. Firstly, from your Backstage instance root directory, you need to execute the following command:
$ yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-github
ShellSessionThen, go to the packages/backend/app/index.ts
file and add a single highlighted line there. It imports the @backstage/plugin-scaffolder-backend-module-github
module to the backend.
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);
backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.add(import('@backstage-community/plugin-sonarqube-backend'));
backend.start();
TypeScriptAfter that, it will be possible to call the publish:github
action defined in our template, which is responsible for creating a new GitHub repository with the Spring Boot app source code.
Enable Sonarqube Integration
In the next step, we need to install and configure the Sonarqube plugin. This time, we need to install both backend and frontend modules. Let’s begin with a frontend part. Firstly, we have to execute the following yarn
command from the project root directory:
$ yarn --cwd packages/app add @backstage-community/plugin-sonarqube
ShellSessionThen, we need to edit the packages/app/src/components/catalog/EntityPage.tsx
file to import the EntitySonarQubeCard
object from the @backstage-community/plugin-sonarqube
plugin. After that, we can include EntitySonarQubeCard
component to the frontend page. For example, it can be placed as a part of the overview content.
import { EntitySonarQubeCard } from '@backstage-community/plugin-sonarqube';
// ... other imports
// ... other contents
const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
{entityWarningContent}
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>
<Grid item md={6} xs={12}>
<EntityCatalogGraphCard variant="gridItem" height={400} />
</Grid>
<Grid item md={6}>
<EntitySonarQubeCard variant="gridItem" />
</Grid>
<Grid item md={4} xs={12}>
<EntityLinksCard />
</Grid>
<Grid item md={8} xs={12}>
<EntityHasSubcomponentsCard variant="gridItem" />
</Grid>
</Grid>
);
TypeScriptThen, we can proceed with the backend plugin. Once again, we are installing with the yarn command:
$ yarn --cwd packages/backend add @backstage-community/plugin-sonarqube-backend
ShellSessionFinally, the same as for the GitHub plugin, go to the packages/backend/app/index.ts
file and add a single highlighted line there to import the @backstage-community/plugin-sonarqube-backend
to the backend module.
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);
backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.add(import('@backstage-community/plugin-sonarqube-backend'));
backend.start();
TypeScriptNote that previously, we added the sonarqube
section with the access token to the app-config.yaml
file and we included annotation with the SonarCloud project key into the Backstage Component
manifest. Thanks to that, we don’t need to do anything more in this part.
Enable CircleCI Integration
In order to install the CircleCI plugin, we need to execute the following yarn command:
$ yarn add --cwd packages/app @circleci/backstage-plugin
ShellSessionThen, we have to edit the packages/app/src/components/catalog/EntityPage.tsx
file. The same as before we need to include the import
section and choose a place on the frontend page to display the content.
// ... other imports
import {
EntityCircleCIContent,
isCircleCIAvailable,
} from '@circleci/backstage-plugin';
// ... other contents
const cicdContent = (
<EntitySwitch>
<EntitySwitch.Case if={isCircleCIAvailable}>
<EntityCircleCIContent />
</EntitySwitch.Case>
<EntitySwitch.Case>
<EmptyState
title="No CI/CD available for this entity"
missing="info"
description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below."
action={
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
>
Read more
</Button>
}
/>
</EntitySwitch.Case>
</EntitySwitch>
);
TypeScriptFinal Run
That’s all we need to configure before running the Backstage instance. Once again, we need to start the instance with the yarn dev
command. After running the app, we should go to the “Create…” section in the left menu pane. You should see our custom template under the name “Create a Spring Boot app”. Click the “CHOOSE” button to create a new component from that template.
Then, we will see the form with several input parameters. I will just change the app name to the sample-spring-boot-app-backstage
and leave the default values everywhere else.
Then, let’s just click the “CREATE” button on the next page.
After that, Backstage will generate all the required things from our sample template.
We can go to the app page in the Backstage catalog. As you see it contains the “Code Quality” section with the latest Sonrqube report for our newly generated app.
We can also switch to the “CI/CD” tab to see the history of the app builds in the CircleCI.
If you want to visit the example repository generated from the sample template discussed today, you can find it here.
Final Thoughts
Backstage is an example of a no-code IDP (Internal Developer Portal). IDP is an important part of the relatively new trend in software development called “Platform Engineering”. In this article, I showed you how to create “Golden Path Templates” using the technology called Scaffolder. Then, you could see how to run Backstage on the local machine and how to create an app source from the template. We installed some useful plugins to integrate our portal with GitHub, CircleCI, and Sonarqube. Plugins installation may cause some problems, especially for people without experience in Node.js and React. Hope it helps!
6 COMMENTS