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:
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
If you are still using the Legacy Backend you can follow these instructions but we highly recommend migrating to the New Backend System.
- Create a new file,
packages/backend/src/plugins/soundcheck.ts
with the following content:
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();
}
- 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.
...
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.
-
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.
- 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.
- Add a soundcheck field to
app-config.yaml
and reference the newly createdsoundcheck-programs.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.
...
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:
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
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
.
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.
Option 1: Using the RBAC plugin (Recommended)
"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
.
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.
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.
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.
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:
Permission | Description |
---|---|
soundcheck.check.create | Allows a user to create a check via the Soundcheck No-Code UI |
soundcheck.check.read | Allows a user to view the library of checks |
soundcheck.check.update | Allows a user to edit a check that was created via the Soundcheck No-Code UI* |
soundcheck.check.delete | Allows a user to delete a existing check that was created via the Soundcheck No-Code UI* |
soundcheck.track.create | Allows 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.read | Allows a user to view the library of tracks |
soundcheck.track.update | Allows a user to edit a track that was created via the Soundcheck No-Code UI* |
soundcheck.track.delete | Allows a user to delete a existing track that was created via the Soundcheck No-Code UI* |
soundcheck.collector.read | Allows a user to view the list of fact collectors currently configured |
soundcheck.collector.update | Allows a user to configure the facts collected by the fact collector** |
soundcheck.campaign.create | Allows a user to create a campaign |
soundcheck.campaign.read | Allows a user to view the library of campaigns |
soundcheck.campaign.update | Allows a user to update a campaign |
soundcheck.campaign.delete | Allows 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.
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.
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.
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.
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