Skip to main content

Installation & Setup

Backend Setup

The next steps describe how to install the Soundcheck backend plugin.

Install the backend package

Add the Soundcheck packages as dependencies to your Backstage instance

yarn workspace backend add @spotify/backstage-plugin-soundcheck-backend

Setup Soundcheck backend

The Soundcheck backend handles the ingestion of check results in the database, and serves data to the Soundcheck UI.

You can integrate the Soundcheck backend plugin with your Backstage backend like this:

packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@spotify/backstage-plugin-soundcheck-backend'));
// ...

backend.start();

Legacy Backend

warning

If you are still using the Legacy Backend you can follow these instructions but we highly recommend migrating to the New Backend System.

  1. Create a new file, packages/backend/src/plugins/soundcheck.ts with the following content:
packages/backend/src/plugins/soundcheck.ts
import { SoundcheckBuilder } from '@spotify/backstage-plugin-soundcheck-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return SoundcheckBuilder.create({ ...env }).build();
}
  1. Set up the Soundcheck backend router in /packages/backend/src/index.ts. You will need to import the module from the previous step, create a new plugin environment for Soundcheck and add the router to the express app.
packages/backend/src/index.ts
...

import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
import soundcheck from './plugins/soundcheck';

...

const techdocsEnv = createEnv('techdocs');
const searchEnv = createEnv('search');
const appEnv = createEnv('app');
const soundcheckEnv = createEnv('soundcheck');

...

apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/soundcheck', await soundcheck(soundcheckEnv));

...

Add track config

Tracks, levels, and checks are driven by config and will be covered in detail in the Core-concepts section.

  1. Create soundcheck-programs.yaml in the root of your Backstage repository and fill in all your checks and tracks. A simple example program with a single level and check is listed below.

    Note: this file will be loaded at runtime along with the rest of your Backstage configuration files, so make sure it's available in deployed environments in the same way as your app-config.yaml files.

soundcheck-programs.yaml
- id: test-certified
name: Test Certified
ownerEntityRef: group:default/example-owner
description: >
Improve quality and reliability of your software component
by measuring the use of testing best practices.
documentationURL: https://www.backstage.io
levels:
- ordinal: 1
checks:
- id: tests-run
name: Tests run on CI
description: >
Indicates whether your system is set up correctly to run tests and
report the results. You must have at least one test.
  1. Add a soundcheck field to app-config.yaml and reference the newly created soundcheck-programs.yaml
app-config.yaml
soundcheck:
programs:
$include: ./soundcheck-programs.yaml

Note: tracks were originally called "programs". Any reference to "program" (e.g. the reference to "programs" in the soundcheck section of app-config.yaml) can be thought of as a reference to a track.

Frontend Setup

Install the frontend package

yarn workspace app add @spotify/backstage-plugin-soundcheck

Setup the Soundcheck Entity Content Page & Card

The Soundcheck Entity Page consists of a view on the Catalog Entity page, and lists all the related certifications, levels, checks and check results for a particular entity.

The code below adds the Soundcheck card to the overview tab for all component types. The Soundcheck entity content component needs to be added to each relevant page type within your Backstage EntityPage. The snippets below insert the card/tabs at the end of their respective containers, but it's fine to reorder them as you wish. When reordering the card in particular, consider whether the fluid layout of the grid should be adjusted to ensure the cards fill each row.

packages/app/src/components/catalog/EntityPage.tsx
...
import {
EntitySoundcheckContent,
EntitySoundcheckCard,
} from '@spotify/backstage-plugin-soundcheck';

...

const overviewContent = (
<Grid container spacing={3} alignItems='stretch'>
{/* existing cards... */}

<Grid item md={6} xs={12}>
<EntitySoundcheckCard />
</Grid>
</Grid>
);

...

// Repeat this for all component entity pages which use the `overviewContent`
const serviceEntityPage = (
<EntityLayout>
{/* existing tabs... */}

<EntityLayout.Route path='/soundcheck' title='Soundcheck'>
<EntitySoundcheckContent />
</EntityLayout.Route>
</EntityLayout>
);

Setup Soundcheck Routing Page

Add a new Route element with the path /soundcheck and element of <SoundcheckRoutingPage />.

<SoundcheckRoutingPage /> supports the following props:

{
title: string; // OPTIONAL - Defaults to 'Soundcheck' when excluded
}

The route should look something like this:

packages/app/src/App.tsx
import { SoundcheckRoutingPage } from '@spotify/backstage-plugin-soundcheck';
...

const routes = (
<FlatRoutes>
{/* existing routes... */}

<Route
path='/soundcheck'
element={<SoundcheckRoutingPage title='My Optional Title' />}
/>
</FlatRoutes>
);

Add a sidebar item

Add a sidebar menu item that routes to the path setup in the previous step

packages/app/src/components/Root.tsx
import DoneAllIcon from '@material-ui/icons/DoneAll';
...

export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
<SidebarLogo />
{/* existing sidebar items... */}

<SidebarScrollWrapper>
{/* existing sidebar items... */}

<SidebarItem icon={DoneAllIcon} to='soundcheck' text='Soundcheck' />
</SidebarScrollWrapper>
</Sidebar>
</SidebarPage>
);

Install Soundcheck Group Content Page

The Soundcheck Group Content Page is a Soundcheck Overview Page that can be pinned to a selected group entity. It can only be added to a group page type within your Backstage EntityPage.

packages/app/src/components/catalog/EntityPage.tsx
import { GroupSoundcheckContent } from '@spotify/backstage-plugin-soundcheck';

const groupPage = (
<EntityLayout>
{/* existing tabs... */}

<EntityLayout.Route path="/soundcheck" title="Soundcheck">
<GroupSoundcheckContent />
</EntityLayout.Route>
</EntityLayout>
);

Check everything is working

If you have followed all steps up to this point, Soundcheck is set up and running. The backend successfully starts up if the program config is valid, and when you navigate to a catalog page for one of the entity types you configured above, you'll see the Soundcheck tab containing the applicable tracks for the current entity. If you visit /soundcheck or click the "Soundcheck" entry on the sidebar, you should see the overview page.

Configure Access Controls

Soundcheck's No-Code UI integrates with Backstage's permission framework and the RBAC plugin. This integration enables restricting which users/groups can Create, Read, Update, or Delete (CRUD) Soundcheck checks and tracks.

The recommended approaches to setting access controls for this plugin require the Backstage permissions framework. You may have set up this up already when configuring other plugins from the Spotify bundle, but if not, follow this guide to setting up the permissions framework.

"The RBAC plugin is a no-code management UI for restricting access to plugins, routes, and data within Backstage. Admins can quickly define roles, assign users and groups, and configure permissions to encode authorization decisions according to your organization's evolving security and compliance needs."

To have access to Soundcheck permissions within the RBAC UI, be sure to add soundcheck to the list of permissionedPlugins in your app-config.yaml.

app-config.yaml
  permission:
enabled: true
permissionedPlugins:
- catalog
- scaffolder
- soundcheck
...

Using the RBAC plugin, it's easy to define access to Soundcheck by selecting the permission you want.

Soundcheck RBAC

See the Available Permissions section for details on what the permissions entail.

Here is a basic sample any-allow policy that allows the default user general access to the catalog but restrict access to create, edit, delete checks or tracks.

sample-policy.yaml
name: Sample Soundcheck Policy
description: null
options:
resolutionStrategy: any-allow
roles:
- name: Default user
members: '*'
permissions:
- match:
resourceType: catalog-entity
decision: allow
- match:
actions:
- read
resourceType: soundcheck-track
decision: allow
- match:
actions:
- read
resourceType: soundcheck-check
decision: allow
- name: Admin
members:
- group:default/backstage-admins
permissions:
- match: '*'
decision: allow

To get started with this sample policy, save the content as a yaml file and follow the RBAC import policy instructions.

Option 2: Using custom policy handlers

It's also possible to define custom handlers for policy queries made by Soundcheck.

packages/backend/src/plugins/permissions.ts
class ExamplePolicy implements PermissionPolicy {
async handle(request: PolicyQuery): Promise<PolicyDecision> {
if (request.permission.name === 'soundcheck.track.read') {
const isAllowed = await someEvaluationFunction();

if (isAllowed) {
return {
result: AuthorizeResult.ALLOW,
};
}
}

return { result: AuthorizeResult.DENY };
}
}

Integrate the Plugin with Your Frontend App

Install the Soundcheck routes in your app within packages/app/src/App.tsx and configure the route location for the Search plugin. You can also add a sidebar item for easy access to Soundcheck if needed.

Verify Functionality

After setting up access controls, users with the right permission should see a Soundcheck sidebar menu item. Ensure that the Backend plugin is correctly configured.

Available Permissions

The Soundcheck plugin has the following permissions that can be used to control access to the plugin and its features:

PermissionDescription
soundcheck.check.createAllows a user to create a check via the Soundcheck No-Code UI
soundcheck.check.readAllows a user to view the library of checks
soundcheck.check.updateAllows a user to edit a check that was created via the Soundcheck No-Code UI*
soundcheck.check.deleteAllows a user to delete a existing check that was created via the Soundcheck No-Code UI*
soundcheck.track.createAllows a user to create a track via the Soundcheck UI. Also allows a user to convert a campaign to a track via the campaign archive flow.
soundcheck.track.readAllows a user to view the library of tracks
soundcheck.track.updateAllows a user to edit a track that was created via the Soundcheck No-Code UI*
soundcheck.track.deleteAllows a user to delete a existing track that was created via the Soundcheck No-Code UI*
soundcheck.collector.readAllows a user to view the list of fact collectors currently configured
soundcheck.collector.updateAllows a user to configure the facts collected by the fact collector**
soundcheck.campaign.createAllows a user to create a campaign
soundcheck.campaign.readAllows a user to view the library of campaigns
soundcheck.campaign.updateAllows a user to update a campaign
soundcheck.campaign.deleteAllows a user to archive AND delete a campaign

*Checks and tracks created via config are not editable within the Soundcheck UI.

**Only the Github fact collector is available for configuration using the No-Code UI. If you already have a fact collector config YAML file setup, you will be unable to use the No-Code UI to configure your collector.

Optional Configurations

Applicable Entities Refresh

This configuration option allows you to set the batchSize, frequency, initial delay, and timeout for refreshing Soundcheck's cache of applicable entities for tracks and checks. This caching makes the track and check insight pages load faster by pre-fetching entities relevant to the various tracks and checks in the system.

Defaults are shown below, and this section can be omitted if you do not want to change the defaults.

app-config.yaml
soundcheck:
applicableEntitiesRefresh:
# The number of entities to fetch from catalog at a time.
batchSize: 100
# The remaining options are human durations and accept
# any combination of hours, minutes, and seconds.
frequency:
minutes: 30
initialDelay:
minutes: 1
timeout:
minutes: 5

Scaling & Rate Limiting

To optimize the scalability of Soundcheck, it is highly recommended to configure a Redis connection. This integration enables Soundcheck to efficiently distribute tasks such as fact collection and check executions across multiple instances of the soundcheck-backend.

To configure Soundcheck to use Redis add the following to your app-config.yaml file:

soundcheck:
job:
queues:
type: redis
host: <redis-host>
port: <redis-port>
username: <redis-username>
password: <redis-password>

TLS for redis can be enabled by setting tls: true:

soundcheck:
job:
queues:
type: redis
host: <redis-host>
port: <redis-port>
username: <redis-username>
password: <redis-password>
tls: true

You can also pass in custom tls certificate parameters like so:

soundcheck:
job:
queues:
type: redis
host: <redis-host>
port: <redis-port>
username: <redis-username>
password: <redis-password>
tls:
cert: ${REDIS_TLS_CERT}
key: ${REDIS_TLS_KEY}
ca: ${REDIS_CA}

You can also use a redis secure connection:

soundcheck:
job:
queues:
type: redis
connection: rediss://<username>:<password>@<host>:<port>

Rate Limiting

Soundcheck offers configurable rate limiting to control the frequency of fact collections on a per-fact collector basis. This feature is useful when a fact collector leverages a third-party API that imposes rate limits (e.g., GitHub API).

Configure rate limits for specific fact collectors by enhancing your app-config.yaml file with the following details:

soundcheck:
job:
workers:
<fact_collector_id>:
limiter:
max: <max_number_of_jobs>
duration: <duration_>

The max parameter establishes an upper threshold for the number of fact collection jobs Soundcheck will perform from with the specified collector within the given duration. Should the job count exceed this limit, additional jobs are queued.

The duration parameter determines the time window during which the specified maximum number of collection jobs (set by max) is enforced. After this duration, the count resets, allowing Soundcheck to process a new set of collection jobs up to the specified maximum.

For example we can limit Soundcheck to only requesting 5000 fact collections per hour from the github fact using the following configuration:

soundcheck:
job:
workers:
github:
limiter:
max: 5000
duration: 3600000

By default, each worker has a rate limit of max: 100 and duration: 1000, aka 100 executions per second.

Automatic Rate Limiting

Many of Soundcheck's fact collectors will automatically handle rate limit errors that call third party APIs (provided the third party integration implements this feature) by pausing collection temporarily and retrying after some time. This pause time is determined by the third party integration, but has a default of 60 seconds.

Slack Notifications

To enable Slack notifications, Slack needs to be integrated within Soundcheck.

Note: This is an optional Soundcheck feature. If you choose not to enable this feature, you can skip the instructions below. To integrate Slack within Soundcheck for the first time, follow the steps below.

Manifest file

To create a custom Slack app, use the manifest file provided below. The provided file lists the scopes required for Soundcheck notifications.

Refer to Slack's Create apps using manifests documentation to learn more.

The first step in the Slack documentation linked above contains a Click to create Slack app button that will allow you to begin the Slack app creation process. Clicking on the button will open a Create an app prompt with two options: from scratch or from an app manifest. Select from an app manifest in order to use the manifest provided below.

Step 1 of 3 in the Slack app creation process consists of picking a workspace to develop your Slack app in. Step 2 of 3 will prompt you to enter the app manifest. Click on the YAML tab and copy/paste the manifest below.

# This file is an example Slack app manifest used for installing a custom app
# in your workspace to deliver Soundcheck notifications. For more information,
# see https://api.slack.com/reference/manifests
display_information:
name: Soundcheck for Backstage
description: Enables notifications from Soundcheck for Backstage in Slack.
background_color: '#9BF0E1'
features:
bot_user:
display_name: Soundcheck for Backstage
always_online: true
oauth_config:
scopes:
bot:
# scopes needed to read user information
- users:read
- users:read.email
# scopes needed to send messages
- chat:write
- chat:write.public
settings:
interactivity:
is_enabled: true
org_deploy_enabled: false
socket_mode_enabled: true
token_rotation_enabled: false

Step 3 of 3 displays an app creation review summary. Click on the Create button to complete the process.

Tokens

A custom Slack app, an app token, an oauth token, and a signing secret must be created to set up Slack integration.

  • app token: The app token can be found, or generated, in the App-Level Token box. If you are generating an app token for the first time, be sure to give it the connections:write scope.

  • oauth token: The oauth token can be found in the Oauth & Permissions section of the Oauth tokens for Your Workspace box. It should look something like xoxb-....

  • signing secret: Go to the Basic Information section of your Slack app. The signing secret can be found in the App Credentials box.

Next, the Slack app token, oauth token, and signing secret must be added to your app-config.yaml (or equivalent) file.

app-config.yaml
soundcheck:
# ...
slack:
token: xoxb-*************-*************-************************ #aka "oauth token", or "bot token"
appToken: xapp-*-***********-*************-****************************************************************
signingSecret: ********************************
# ...

Socket Mode

Socket mode must be enabled for your Slack app. Under Settings, click on Socket Mode. Then, click on the Enable Socket Mode switch to toggle it on. Finally, make sure that the Interactivity & Shortcuts feature is enabled.

Adding the Slack app to a Slack channel

In order to get Slack notifications from your Slack app, it must be added to a Slack channel. The Slack channel can be either a public or a private channel that lives in the same workspace that you selected when creating the Slack app.

Right-click on the desired channel in Slack, select View Channel Details, click on the Integrations tab, and then click on Add Apps. Search for your Slack app by name, find it in the search results, and then click on the Add button.

Email Notifications

To enable email notifications, SMTP server details must be configured within Soundcheck.

Note: This is an optional Soundcheck feature. If you choose not to enable this feature, you can skip the instructions below.

To integrate email notifications within Soundcheck for the first time, an email configuration must be added to your app-config.yaml (or equivalent) file. Examples are provided below.

SMTP Connection Authentication

Here's an example of the email configuration for username and password authentication.

app-config.yaml
soundcheck:
# ...
email:
# hostname or IP address of the SMTP server
host: sandbox.smtp.mailtrap.io
# port to connect to
port: 2525
# sender email address
from: info@soundcheck.com
# authentication details
auth:
# username
user: d92xxxxxxxxxxx
# password
pass: 3f4xxxxxxxxxxx
# ...

Here's an example of the email configuration for OAuth2 authentication.

app-config.yaml
soundcheck:
# ...
email:
# hostname or IP address of the SMTP server
host: smtp.gmail.com
# port to connect to
port: 465
# sender email address
from: info@soundcheck.com
# authentication details
auth:
# username
user: user@example.com
# client id of the application
clientId: 000000000000-xxx0.apps.googleusercontent.com
# client secret of the application
clientSecret: XxxxxXXxX0xxxxxxxx0XXxX0
# optional refresh token (used to generate a new access token if the existing one expires/fails)
refreshToken: 1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx
# optional access token
accessToken: ya29.Xx_XX0xxxxx-xX0X0XxXXxXxXXXxX0x
# ...

Node Options

If you're running Backstage with Node 20 or later, you'll need to pass the flag --no-node-snapshot to Node in order to use the email notifications feature. One way to do this is to specify the NODE_OPTIONS environment variable before starting Backstage.

export NODE_OPTIONS=--no-node-snapshot