In this chapter we will initialize the standalone app for the moment. In the later chapters we will add an external database to it and deploy it to Azure.
To get set up quickly with your own Backstage project you can create a Backstage App.
A Backstage App is a monorepo setup with lerna
that includes everything you need to run Backstage in your own
environment.
Create a new directory for all the workshop files and navigate into it.
mkdir pulumi-backstage-azure-workshop
cd pulumi-backstage-azure-workshop
This will be the root directory for the workshop. Keep this in mind for all the following steps and chapters.
Backstage provides a utility for creating new apps. It guides you through the initial setup of selecting the name of the
app and a database for the backend. The database options are either SQLite
or PostgreSQL
, where the latter requires
you
to set up a separate database instance.
To create a new app, run the following command:
npx @backstage/create-app@latest
If prompted, enter a name for the app. This will be the name of the directory that is created. I suggest to
use backstage
? Enter a name for the app [required] backstage
Creating the app...
Checking if the directory is available:
checking backstage ✔
Creating a temporary app directory:
Preparing files:
copying .dockerignore ✔
copying .eslintignore ✔
templating .eslintrc.js.hbs ✔
templating .gitignore.hbs ✔
copying .prettierignore ✔
copying README.md ✔
copying app-config.local.yaml ✔
copying app-config.production.yaml ✔
templating app-config.yaml.hbs ✔
templating backstage.json.hbs ✔
templating catalog-info.yaml.hbs ✔
copying lerna.json ✔
templating package.json.hbs ✔
copying playwright.config.ts ✔
copying tsconfig.json ✔
copying yarn.lock ✔
copying README.md ✔
copying entities.yaml ✔
copying org.yaml ✔
copying template.yaml ✔
copying catalog-info.yaml ✔
copying index.js ✔
copying package.json ✔
copying README.md ✔
templating .eslintrc.js.hbs ✔
copying Dockerfile ✔
copying README.md ✔
templating package.json.hbs ✔
copying index.test.ts ✔
copying index.ts ✔
copying types.ts ✔
copying app.ts ✔
copying auth.ts ✔
copying catalog.ts ✔
copying proxy.ts ✔
copying scaffolder.ts ✔
templating search.ts.hbs ✔
copying techdocs.ts ✔
copying .eslintignore ✔
templating .eslintrc.js.hbs ✔
templating package.json.hbs ✔
copying android-chrome-192x192.png ✔
copying apple-touch-icon.png ✔
copying favicon-16x16.png ✔
copying favicon-32x32.png ✔
copying favicon.ico ✔
copying index.html ✔
copying manifest.json ✔
copying robots.txt ✔
copying safari-pinned-tab.svg ✔
copying app.test.ts ✔
copying App.test.tsx ✔
copying App.tsx ✔
copying apis.ts ✔
copying index.tsx ✔
copying setupTests.ts ✔
copying LogoFull.tsx ✔
copying LogoIcon.tsx ✔
copying Root.tsx ✔
copying index.ts ✔
copying EntityPage.tsx ✔
copying SearchPage.tsx ✔
Moving to final location:
moving backstage ✔
Installing dependencies:
determining yarn version ✔
executing yarn install ✔
executing yarn tsc ✔
🥇 Successfully created backstage
All set! Now you might want to:
Run the app: cd backstage && yarn dev
Set up the software catalog: https://backstage.io/docs/features/software-catalog/configuration
Add authentication: https://backstage.io/docs/auth/
This can take a few minutes to complete. But you should see the current progress in the terminal.
You should now have a new directory called backstage
in your root directory, which contains following files and
folders:
backstage/
├── README.md
├── app-config.local.yaml
├── app-config.production.yaml
├── app-config.yaml
├── backstage.json
├── catalog-info.yaml
├── dist-types
│ ├── packages
│ └── tsconfig.tsbuildinfo
├── examples
│ ├── entities.yaml
│ ├── org.yaml
│ └── template
├── lerna.json
├── package.json
├── packages
│ ├── README.md
│ ├── app
│ └── backend
├── playwright.config.ts
├── plugins
│ └── README.md
├── tsconfig.json
└── yarn.lock
As soon as the app is created, you can run it by navigating into the backstage
directory and running the following
command:
cd backstage
yarn dev
The yarn dev command will run both the frontend and backend as separate processes (named [0] and [1]) in the same
window. When the command finishes running, it should open up a browser window displaying your app. If not, you can open
a browser and directly navigate to the frontend at http://localhost:3000
.
Let’s have a look on some of the values in the different files and change them to your needs.
First we change the name of our organization in the app-config.yaml
file. Open the file and change the organization
name to a name of your choice.
organization:
name: Pulumi
Because we are still in the development mode, any changes to the app-config.yaml
file will be reflected in the app as
soon as you save the file. You can see the changes in the browser window.
The backbone of Backstage is the rich integration with different tools and services. This will be all done via the plugin system.
There are a huge number of plugins available for Backstage. You can find them in the Backstage Plugin Catalog.
Note: Currently there is some major refactoring going on in the Backstage project. The plugin system will be changend in the near future to support a more declarative approach. This will make it easier to develop and add new plugins to Backstage. See the RFC for more information.
Add this to the app-config.yaml
file:
azureDevOps:
host: dev.azure.com
token: ${AZURE_PAT}
organization: ${AZURE_ORG}
integrations:
azure:
- host: dev.azure.com
credentials:
- personalAccessToken: ${AZURE_PAT}
organizations:
- ${AZURE_ORG}
This will add the Azure DevOps integration to your app. The AZURE_PAT
and AZURE_ORG
are environment variables
that you need to set. You can do this by adding the above lines to your app-config.local.yaml
file for local
development.
azureDevOps:
host: dev.azure.com
token: <your personal access token>
organization: <your organization>
integrations:
azure:
- host: dev.azure.com
credentials:
- personalAccessToken: <your personal access token>
organizations:
- <your organization>
Time to add the Azure DevOps plugin to your Backstage app.
Add the Azure DevOps backend plugin to your Backstage backend, this will allow you to fetch data from Azure DevOps.
# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-azure-devops-backend
Then we will create a new file named packages/backend/src/plugins/azure-devops.ts
, and add the following code snippet
to it:
import {createRouter} from '@backstage/plugin-azure-devops-backend';
import {Router} from 'express';
import type {PluginEnvironment} from '../types';
export default function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return createRouter({
logger: env.logger,
config: env.config,
reader: env.reader,
});
}
Next we wire this into the overall backend router, edit packages/backend/src/index.ts
:
import azureDevOps from './plugins/azure-devops';
// ...
async function main() {
// ...
// Add this line under the other lines that follow the useHotMemoize pattern
const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops'));
// ...
// Insert this line under the other lines that add their routers to apiRouter in the same way
apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv));
Add the Azure DevOps frontend plugin to your frontend. The Azure DevOps frontend plugin contains a number of components that can be used to display Azure DevOps data in the Backstage frontend.
# From your Backstage root directory
yarn add --cwd packages/app @backstage/plugin-azure-devops
Then open packages/app/src/components/catalog/EntityPage.tsx
, and add the following to it:
import {
EntityAzurePullRequestsContent,
isAzureDevOpsAvailable,
} from '@backstage/plugin-azure-devops';
// For example in the Service section
const serviceEntityPage = (
<EntityLayout>
// ...
<EntityLayout.Route if={isAzureDevOpsAvailable} path="/pull-requests" title="Pull Requests">
<EntityAzurePullRequestsContent defaultLimit={25} />
</EntityLayout.Route>
</EntityLayout>
//...
);
Open again packages/app/src/components/catalog/EntityPage.tsx
and add the following to it:
// In packages/app/src/components/catalog/EntityPage.tsx
import {
EntityAzurePipelinesContent,
isAzureDevOpsAvailable,
} from '@backstage/plugin-azure-devops';
// For example in the CI/CD section
const cicdContent = (
<EntitySwitch>
// ...
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
<EntityAzurePipelinesContent defaultLimit={25} />
</EntitySwitch.Case>
// ...
</EntitySwitch>
// ...
);
Open packages/app/src/components/catalog/EntityPage.tsx
, and add the following to it:
import {
EntityAzureReadmeCard,
isAzureDevOpsAvailable,
} from '@backstage/plugin-azure-devops';
// As it is a card, you can customize it the way you prefer
// For example in the Service section
const overviewContent = (
<Grid container spacing={3} alignItems="stretch">
<EntitySwitch>
<EntitySwitch.Case if={isAzureDevOpsAvailable}>
<Grid item md={6}>
...
</Grid>
<Grid item md={6}>
<EntityAzureReadmeCard maxHeight={350} />
</Grid>
</EntitySwitch.Case>
</EntitySwitch>
</Grid>
);
To test that the Azure DevOps plugin is working, you need first to provide a Software Templates.
The Software Templates part of Backstage is a tool that can help you create Components inside Backstage. By default, it has the ability to load skeletons of code, template in some variables, and then publish the template to Azure DevOps.
Let’s edit the existing example template under packages/examples/
to include the Azure DevOps integration.
We start with the packages/examples/template/content/template.yaml
file. Replace the content with the following:
apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
name: example-nodejs-template
title: Example Node.js Template
description: An example template for the scaffolder that creates a simple Node.js service
spec:
owner: user:guest
type: service
# These parameters are used to generate the input form in the frontend, and are
# used to gather input data for the execution of the template.
parameters:
- title: Fill in some steps
required:
- name
properties:
name:
title: Name
type: string
description: Unique name of the component
ui:autofocus: true
ui:options:
rows: 5
- title: Choose a location
required:
- repoUrl
properties:
repoUrl:
title: Repository Location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedOrganizations:
- <your organization>
allowedOwners:
- <your project>
allowedHosts:
- dev.azure.com
# These steps are executed in the scaffolder backend, using data that we gathered
# via the parameters above.
steps: # Each step executes an action, in this case one templates files into the working directory.
- id: fetch-base
name: Fetch Base
action: fetch:template
input:
url: ./content
values:
name: ${{ parameters.name }}
destination: ${{ parameters.repoUrl | parseRepoUrl }}
# This step publishes the contents of the working directory to GitHub.
- id: publish
name: Publish
action: publish:azure
input:
description: This is ${{ parameters.name }}
repoUrl: ${{ parameters.repoUrl }}
defaultBranch: main
# The final step is to register our new component in the catalog.
- id: register
name: Register
action: catalog:register
input:
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
# Outputs are displayed to the user after a successful execution of the template.
output:
links:
- title: Repository
url: ${{ steps['publish'].output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}
And replace the packages/examples/template/content/catalog-info.yaml
directory with the following content:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ values.name | dump }}
annotations:
dev.azure.com/project-repo: ${{values.destination.owner + "/" + values.destination.repo}}
spec:
type: service
owner: user:guest
lifecycle: experimental
Important here is the dev.azure.com/project-repo
annotation. This is the key that is used to link the component to the
Azure DevOps project and repository.
Head over to your browser and navigate to the Create
page in Backstage. You should see the new template there.
Awesome! You have now created a Backstage app and added the Azure DevOps integration to it.
To stop the app, press Ctrl+C
in the terminal where the app is running.