Setup & Installation
Prerequisites
Backend-to-backend authentication
Backend-to-backend authentication lets backend code in Backstage securely verify that a given request originates from another part of the backend, rather than from a user. This is useful for tasks like indexing catalog entities for search. This type of request shouldn’t be permissioned, so this feature must be enabled before turning on permissions.
To set up backend-to-backend authentication, follow the setup instructions in the backstage.io docs.
User entities
Skill Exchange depends on user entities being present in the catalog, as users’ skill profiles are tied to these. Backstage has catalog modules to import users from GitHub, LDAP, or Azure AD. You can also define users in entity YAML files manually, or create a custom EntityProvider.
Sign-in resolver
In order for Skill Exchange to properly identify the user entity associated with the currently signed-in user, the SignInResolver must issue an identity token with a sub (subject) claim that is an entity ref to the user entity in the catalog corresponding to the signed-in user. Many authentication providers already provide this behavior, but if using a custom provider, you may need to add this behavior yourself. For examples, see the Google auth sign-in resolver or the GitHub auth sign-in resolver. For information on configuring a sign-in resolver, see the identity resolver docs on backstage.io.
Slack app token
Slack needs to be integrated to access notification and messaging functionality in Skill Exchange. A custom Slack app and the associated token (oauth token), app token and signing secret, must be created to set up Slack integration. Socked mode needs to be enabled for the app.
You will also need to create a Slack app manifest file. The manifest we provide below lists the required scopes for Skill Exchange's notifications functionality. For more information, you can refer to Slack's Create apps using manifests documentation.
# This file is an example Slack app manifest used for installing a custom app in your workspace to deliver
# Skill Exchange notifications. For more information, see https://api.slack.com/reference/manifests
display_information:
name: Skill Exchange for Backstage
description: Enables notifications from Skill Exchange for Backstage in Slack.
background_color: '#9BF0E1'
features:
bot_user:
display_name: Skill Exchange for Backstage
always_online: true
oauth_config:
scopes:
bot:
# scopes needed to read user information
- users:read
- users:read.email
# scope needed to open DMs to users
- im:write
# 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
The slack configuration must be added to your app-config.yaml
(or equivalent) file
skillExchange:
# ...
slack:
token: xoxb-*************-*************-************************ #aka "oauth token", or "bot token"
appToken: xapp-*-***********-*************-****************************************************************
signingSecret: ********************************
# ...
Backend Installation
Getting the plugin
Add the Skill Exchange packages as dependencies to your Backstage instance:
yarn workspace backend add @spotify/backstage-plugin-skill-exchange-backend @spotify/backstage-plugin-search-backend-module-skill-exchange
Integrate the plugin with your Backstage app
You can integrate the Skill Exchange backend plugin with your Backstage backend. You just need to add the following:
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@spotify/backstage-plugin-skill-exchange-backend'));
backend.add(
import('@spotify/backstage-plugin-search-backend-module-skill-exchange'),
);
// ...
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/skillexchange.ts
with the following content:
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { createRouter } from '@spotify/backstage-plugin-skill-exchange-backend';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
...env,
});
}
Wire up the Skill Exchange backend in packages/backend/src/index.ts
. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app:
import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
import permission from './plugins/permission';
import skillexchange from './plugins/skillexchange';
/* ... */
const techdocsEnv = createEnv('techdocs');
const searchEnv = createEnv('search');
const appEnv = createEnv('app');
const permissionEnv = createEnv('permission');
const skillexchangeEnv = createEnv('skillEx');
/* ... */
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/permission', await permission(permissionEnv));
apiRouter.use('/skill-exchange', await skillexchange(skillexchangeEnv));
Add the SkillProfileDecoratorFactory
to the search plugin in packages/backend/src/search.ts
. This decorates user entities with the skills they add to their profiles and allows them to be found via search.
import { SkillProfileDecoratorFactory } from '@spotify/backstage-plugin-skill-exchange-backend';
export default async function createPlugin({
cache,
logger,
permissions,
discovery,
config,
}: PluginEnvironment) {
// Initialize a connection to a search engine.
const searchEngine = new LunrSearchEngine({ logger });
const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addDecorator({
factory: SkillProfileDecoratorFactory.fromConfig(env.config, {
cache: env.cache,
discovery: env.discovery,
logger: env.logger,
tokenManager: env.tokenManager,
}),
});
return await createRouter({
engine: indexBuilder.getSearchEngine(),
types: indexBuilder.getDocumentTypes(),
permissions,
config,
logger,
});
}
[Optional] Defining default lists
Skill Exchange has the ability to ingest lists of items that can be used across the application. For example, by adding a YAML list of locations, Gigs (Embeds, Mentorships, etc) can have their location to set to one of the locations defined in the YAML list. You can also define a default list of skills that users can select to add to their profiles. This list is defined in a YAML file and passed to the Skill Exchange plugin via configuration either as a local file path or a remote URL.
You can add Skills, Locations, and Embed Priority Labels by creating a new yaml file using the following basic structure. Locations and embed priority labels are defined as a flat list of names. See Defining Skills for Ingestion below for more information on ingesting skills.
# default-lists.yaml
skills:
- name: Web
category: DISCIPLINE
- name: Backend
category: DISCIPLINE
- name: JavaScript
category: LANGUAGES
subset: SKILLS
disciplines:
- Web
- Backend
- name: API Development
category: TECHNIQUES
subset: SKILLS
disciplines:
- Backend
locations:
- Stockholm
- New York
- London
embedPriorityLabels:
- Tech Lead
- Company Bet Lead
Then add the configuration option in your app-config.yaml
(or equivalent) file.
skillExchange:
defaultLists:
sources: # for local files
- /path/to/default-lists.yaml
remoteUrls: # for remote files
- https://github.com/org/repo/blob/main/default-lists.yaml
You can have a different YAML file for skills and locations, and you can also have multiple files for the same type of data. For example, you can have a YAML file for locations in North America, and another YAML file for locations in Asia. The sources
property on defaultLists
takes an array of paths to your YAML files. Similarly, the remoteUrls
property takes an array of URLs to remote YAML files. For more information on remote URLs, see Remote configuration options below.
Ingestion runs when you next restart Backstage. Items are only added and not removed, and duplicates are auto-detected. Therefore, you can leave the configuration settings and restart the Backstage instance multiple times without duplicating items. Modifying the YAML file will allow you to add new default items later.
Defining Skills for Ingestion
Skills defined through YAML for ingestion are treated as the source of truth for all skills within Skill Exchange.
Each skill can be defined with the following fields:
Field | Type | Description | Value |
---|---|---|---|
name | String | [Required] The name of the skill. | |
category | Enum | [Required] The category of the skill. | "DISCIPLINE" | "LANGUAGES" | "FRAMEWORKS" | "INFRASTRUCTURE" | "TECHNIQUES" |
subset | Enum | [Optional] Subset of the skill. Not required for skills with category "DISCIPLINE". | "SKILLS" | "TOOLS" |
disciplines | String[] | [Optional] List of disciplines the skill is associated with. Not required for skills with category "DISCIPLINE". |
Here is an extensive list of skills in YAML format to get you started. To start using these default skills out of the box, provide a link to these skills to the remote configuration portion of Skill Exchange as described below.
Remote configuration options
If you have integrations set up within your Backstage instance, you also have the option to specify a list of remote sources that you'd like Skill Exchange to periodically read from. Types of valid integration URLs that Skill Exchange supports are listed here.
Skill Exchange will de-duplicate lists of both local and remote sources and ingest from all sources it points to within the defaultLists
config.
The cadence at which remote file updates can be polled for can be controlled by using the skillExchange.remoteFileUpdates.frequency
field, which can take in a cron
field:
skillExchange:
remoteFileUpdates:
frequency:
cron: '0 0 * * *'
or a duration
field, which follows this shape:
skillExchange:
remoteFileUpdates:
frequency:
duration:
days: 1
Adding a new Skill
To add a new skill, simply add a new entry to the skills
list to the skills YAML file. The skill will be ingested the next time the Backstage instance is restarted.
Editing and Deleting a Skill
To update or delete a skill, this must be done through the database as we currently do not support editing or deleting skills through the YAML at the moment.
[Optional] Schedule Hack Digest Notifications
For adopters who have their Slack integration running, and would like to schedule periodic Slack updates (edits to the hack, or new comments) to users who follow hacks, they can specify a cron value to their app-config.yaml
(or equivalent) file. This cron value dictates the frequency with which users will receive updates on their hacks, if any updates exist since the last updates were sent. See the example below:
skillExchange:
# ...
hackUpdateNotificationsCron: '0 17 * * 1'
# ...
Frontend Installation
Getting the plugin
Add @spotify/backstage-plugin-skill-exchange
as a dependency to packages/app
yarn workspace app add @spotify/backstage-plugin-skill-exchange
Install the Skill Exchange frontend
Wire up routing to the SkillExchangePage
entry page. Use the enableEmbeds
and enableMentorships
props to enable the features you intend to use. The route should look something like this:
import { SkillExchangePage } from '@spotify/backstage-plugin-skill-exchange';
const routes = (
<FlatRoutes>
<Navigate key="/" to="catalog" />
<Route
path="/skill-exchange"
element={<SkillExchangePage enableEmbeds enableHacks enableMentorships />}
/>
</FlatRoutes>
);
Add a sidebar menu item that routes to the path setup in the previous step:
import SchoolIcon from '@material-ui/icons/School';
<SidebarScrollWrapper>
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
<SidebarItem icon={SchoolIcon} to="skill-exchange" text="Skill Exchange" />
</SidebarScrollWrapper>;
Add the User entity skills card
Skill Exchange integration with the Backstage user pages is required in order to view and edit skill profiles. To enable, add the SkillExchangeUserEntitySkillsCard
to the catalog user entity page:
import { SkillExchangeUserEntitySkillsCard } from '@spotify/backstage-plugin-skill-exchange';
const userPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<Grid container spacing={3}>
{entityWarningContent}
<Grid item xs={12} md={6}>
<EntityUserProfileCard variant="gridItem" />
</Grid>
<Grid item xs={12} md={6}>
<EntityOwnershipCard variant="gridItem" />
</Grid>
<Grid item xs={12} md={6}>
<SkillExchangeUserEntitySkillsCard />
</Grid>
</Grid>
</EntityLayout.Route>
</EntityLayout>
);
Up and running!
Skill Exchange is now up and running! Get started by clicking the link in your Backstage sidebar, or visiting <backstage-instance>/skill-exchange
.
Available Permissions
The Skill Exchange plugin does not implement any permissions to restrict access to the plugin.