Merge branch 'backstage:master' into milliehartnt123-create-ui-doc

This commit is contained in:
milliehartnt123
2026-02-17 09:23:28 -05:00
committed by GitHub
2658 changed files with 122677 additions and 44061 deletions
+1 -1
View File
@@ -37,4 +37,4 @@ Below you can find a list of links and references to help you learn about and st
- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.1.0-changelog.md)
- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins)
Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage.
Sign up for our [newsletter](https://spoti.fi/backstagenewsletter) if you want to be informed about what is happening in the world of Backstage.
+1 -1
View File
@@ -185,7 +185,7 @@ const app = createApp({
A common pattern is to export a list of all APIs from `apis.ts`, next to
`App.tsx`. See the
[example app in this repo](https://github.com/backstage/backstage/blob/master/packages/app/src/apis.ts)
[example app in this repo](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/apis.ts)
for an example.
## Custom implementations of Utility APIs
@@ -48,7 +48,7 @@ benefits. A few are:
## Decision
We will stop using default exports except when absolutely necessary (such as
[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules).
[`React.lazy`](https://18.react.dev/reference/react/lazy) modules).
A workaround exists for those that would prefer to never use `default`:
```ts
@@ -32,7 +32,7 @@ const users = await response.json();
```
Frontend plugins and packages should prefer to use the
[`fetchApiRef`](https://backstage.io/docs/reference/core-plugin-api.fetchapiref).
[`fetchApiRef`](https://backstage.io/api/stable/variables/_backstage_core-plugin-api.index.fetchApiRef.html).
It uses `cross-fetch` internally. Example:
```ts
@@ -33,7 +33,7 @@ const users = await response.json();
```
Frontend plugins and packages should prefer to use the
[`fetchApiRef`](https://backstage.io/docs/reference/core-plugin-api.fetchapiref).
[`fetchApiRef`](https://backstage.io/api/stable/variables/_backstage_core-plugin-api.index.fetchApiRef.html).
```ts
import { useApi } from '@backstage/core-plugin-api';
+2
View File
@@ -44,6 +44,7 @@ auth:
audience: ${AUTH_AUTH0_AUDIENCE}
connection: ${AUTH_AUTH0_CONNECTION}
connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE}
organization: ${AUTH_AUTH0_ORGANIZATION_ID}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
session:
@@ -69,6 +70,7 @@ Auth0 requires a session, so you need to give the session a secret key.
- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections).
- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter.
- `sessionDuration`: Lifespan of the user session.
- `organization`: Specify a specific organization ID to be targeted as part of the login flow.
### Resolvers
+1
View File
@@ -80,6 +80,7 @@ This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found, it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found, it will throw a `NotFoundError`.
- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found, it will throw a `NotFoundError`.
- `userIdMatchingUserEntityAnnotation`: Matches the GitHub user ID with the User entity that has a matching `github.com/user-id`. If no match is found, it will throw a `NotFoundError`.
:::note Note
+1
View File
@@ -72,6 +72,7 @@ This provider includes several resolvers out of the box that you can use:
- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found, it will throw a `NotFoundError`.
- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found, it will throw a `NotFoundError`.
- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found, it will throw a `NotFoundError`.
- `userIdMatchingUserEntityAnnotation`: Matches the GitLab user ID with the User entity that has a matching `gitlab.com/user-id` annotation (or `{integration-host}/user-id` for self-hosted GitLab instances). If no match is found, it will throw a `NotFoundError`.
:::note Note
+1 -1
View File
@@ -287,7 +287,7 @@ async signInResolver(info, ctx) {
If you throw an error in the sign in resolver function, the sign in attempt is
immediately rejected, and the error details are presented in the user interface.
The `ctx` context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
The `ctx` context [has several useful functions](https://backstage.io/api/stable/types/_backstage_plugin-auth-node.AuthResolverContext.html)
for issuing tokens in various ways.
### Custom Ownership Resolution
+4 -2
View File
@@ -211,6 +211,8 @@ to get the existing session, which is exactly what the `ProxiedSignInPage` does.
thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this:
```tsx title="packages/app/src/App.tsx"
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="awsalb" />,
@@ -285,7 +287,7 @@ sign-in resolvers so that they resolve to the same identity regardless of the me
## Scaffolder Configuration (Software Templates)
If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates, you will need to configure the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed.
If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates, you will need to configure the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed.
To set it up, you'll need to add an API factory entry to `packages/app/src/apis.ts`. The example below sets up the `ScmAuthApi` for an already configured GitLab authentication provider:
@@ -361,7 +363,7 @@ The default `ScmAuthApi` provides integrations for `github`, `gitlab`, `azure` a
ScmAuth.createDefaultApiFactory();
```
If you require only a subset of these integrations, then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi). It is an API used to authenticate different SCM systems generically, based on what resource is being accessed, and is used for example, by the Scaffolder (Software Templates) and Catalog Import plugins.
If you require only a subset of these integrations, then you will need a custom implementation of the [`ScmAuthApi`](https://backstage.io/api/stable/interfaces/_backstage_integration-react.ScmAuthApi.html). It is an API used to authenticate different SCM systems generically, based on what resource is being accessed, and is used for example, by the Scaffolder (Software Templates) and Catalog Import plugins.
The first step is to remove the code that creates the default providers.
+14 -2
View File
@@ -136,7 +136,15 @@ auth:
- resolver: emailMatchingUserEntityProfileEmail
```
If none of the built-in resolvers are suitable, you can alternatively write a custom resolver. See an example below:
If none of the built-in resolvers are suitable, you can alternatively write a custom resolver.
First, install the OIDC provider module:
```bash
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider
```
Then create a custom resolver as shown below:
```ts title="in packages/backend/src/index.ts"
/* highlight-add-start */
@@ -146,6 +154,10 @@ import {
createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import {
stringifyEntityRef,
DEFAULT_NAMESPACE,
} from '@backstage/catalog-model';
const myAuthProviderModule = createBackendModule({
// This ID must be exactly "auth" because that's the plugin it targets
@@ -168,7 +180,7 @@ const myAuthProviderModule = createBackendModule({
async signInResolver(info, ctx) {
const userRef = stringifyEntityRef({
kind: 'User',
name: info.result.userinfo.sub,
name: info.result.fullProfile.userinfo.sub,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
@@ -65,6 +65,64 @@ export const scaffolderPlugin = createBackendPlugin(
Note that we create a closure that adds to a shared `actions` structure when `addAction` is called by users of your extension point. It is safe for us to then access our `actions` in the `init` method of our plugin, since all modules that extend our plugin will be completely initialized before our plugin gets initialized. That means that at the point where our `init` method is called, all actions have been added and can be accessed.
## Factory-Based Extension Points
In some cases, you may want to be able to attribute startup failures to modules that provided an extension, rather than failing the plugin startup entirely. To do this, you can use a variant of `registerExtensionPoint` that instead of providing a direct implementation, registers a factory function that produces the implementation. This factory receives an `ExtensionPointFactoryContext` with a `reportModuleStartupFailure` method that lets you report startup failures and attribute them to the module.
Here's an example of registering an extension point using a factory:
```ts
import {
createBackendPlugin,
ExtensionPointFactoryContext,
} from '@backstage/backend-plugin-api';
import { assertError, ForwardedError } from '@backstage/errors';
import { createProviderConnection, Provider } from './internal';
type ProviderEntry = {
provider: Provider;
context: ExtensionPointFactoryContext;
};
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
const providers: ProviderEntry[] = [];
// Using the variant of registerExtensionPoint that takes an options object.
env.registerExtensionPoint({
extensionPoint: exampleProvidersExtensionPoint,
// The factory function produces a separate instance for each module.
factory: context => ({
addProvider(provider) {
// Store the context together with the provider so we can report failures later
providers.push({ provider, context });
},
}),
});
env.registerInit({
deps: { database: coreServices.database },
async init({ database }) {
for (const { provider, context } of providers) {
const connection = await createProviderConnection(provider, database);
try {
// This connects each provider that was installed by a module
await provider.connect(connection);
} catch (error: unknown) {
// If the connection fails, we can report this as a failure of the module rather than the plugin
assertError(error);
context.reportModuleStartupFailure({
error: new ForwardedError('Failed to connect provider', error),
});
}
}
},
});
},
});
```
## Module Extension Points
Just like plugins, modules can also provide their own extension points. The API for registering and using extension points is the same as for plugins. However, modules should typically only use extension points to allow for complex internal customizations by users of the plugin module. It is therefore preferred to export the extension point directly from the module package, rather than creating a separate node library for that purpose. Extension points exported by a module are used the same way as extension points exported by a plugin, you create your own separate module and declare a dependency on the extension point that you want to interact with.
@@ -11,10 +11,10 @@ As a rule, all names should be camel case, with the exceptions of plugin and mod
### Plugins
| Description | Pattern | Examples |
| ----------- | ----------------- | ------------------------------------- |
| export | `<camelId>Plugin` | `catalogPlugin`, `userSettingsPlugin` |
| ID | `'<kebab-id>'` | `'catalog'`, `'user-settings'` |
| Description | Pattern | Examples | Notes |
| ----------- | ----------------- | ------------------------------------- | --------------------------------------------------- |
| export | `<camelId>Plugin` | `catalogPlugin`, `userSettingsPlugin` | |
| ID | `'<kebab-id>'` | `'catalog'`, `'user-settings'` | letters, digits, and dashes, starting with a letter |
Example:
@@ -27,10 +27,10 @@ export const userSettingsPlugin = createBackendPlugin({
### Modules
| Description | Pattern | Examples |
| ----------- | ---------------------------- | ----------------------------------- |
| export | `<pluginId>Module<ModuleId>` | `catalogModuleGithubEntityProvider` |
| ID | `'<module-id>'` | `'github-entity-provider'` |
| Description | Pattern | Examples | Notes |
| ----------- | ---------------------------- | ----------------------------------- | --------------------------------------------------- |
| export | `<pluginId>Module<ModuleId>` | `catalogModuleGithubEntityProvider` | |
| ID | `'<module-id>'` | `'github-entity-provider'` | letters, digits, and dashes, starting with a letter |
Example:
@@ -220,7 +220,7 @@ These are the deprecation messages for the most common replacements:
- `getRootLogger` - This function will be removed in the future. If you need to get the root logger in the new system, please check out this documentation: https://backstage.io/docs/backend-system/core-services/logger
- `getVoidLogger` - This function will be removed in the future. If you need to mock the root logger in the new system, please use `mockServices.logger.mock()` from `@backstage/backend-test-utils` instead.
- `legacyPlugin` - Fully use the new backend system instead.
- `loadBackendConfig` - Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the [@backstage/config-loader#ConfigSources](https://backstage.io/docs/reference/config-loader.configsources) facilities if required.
- `loadBackendConfig` - Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the [@backstage/config-loader#ConfigSources](https://backstage.io/api/stable/classes/_backstage_config-loader.ConfigSources.html) facilities if required.
- `loggerToWinstonLogger` - Migrate to use the new `LoggerService` instead.
- `resolveSafeChildPath` - This function is deprecated and will be removed in a future release, see [#24493](https://github.com/backstage/backstage/issues/24493). Please use the `resolveSafeChildPath` function from the `@backstage/backend-plugin-api` package instead.
- `ServerTokenManager` - Please [migrate](https://backstage.io/docs/tutorials/auth-service-migration) to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead.
@@ -660,7 +660,7 @@ depends on the appropriate extension point and interacts with it.
```ts title="packages/backend/src/index.ts"
/* highlight-add-start */
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-end */
@@ -57,7 +57,7 @@ interactions with the running test service.
### mock services
The [`mockServices`](https://backstage.io/docs/reference/backend-test-utils.mockservices) object from `@backstage/backend-test-utils` provides service factory functions, and mocks for all core services that you can use to verify interactions between plugin and services.
The [`mockServices`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html) object from `@backstage/backend-test-utils` provides service factory functions, and mocks for all core services that you can use to verify interactions between plugin and services.
All mock services provide a factory function that is sufficient for most tests. Here's an example:
@@ -106,24 +106,24 @@ describe('myPlugin', () => {
Available services:
- [`auth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.auth/)
- [`cache`](https://backstage.io/docs/reference/backend-test-utils.mockservices.cache/)
- [`database`](https://backstage.io/docs/reference/backend-test-utils.mockservices.database/)
- [`discovery`](https://backstage.io/docs/reference/backend-test-utils.mockservices.discovery/)
- [`events`](https://backstage.io/docs/reference/backend-test-utils.mockservices.events/)
- [`httpAuth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.httpAuth/)
- [`httpRouter`](https://backstage.io/docs/reference/backend-test-utils.mockservices.httpRouter/)
- [`lifecycle`](https://backstage.io/docs/reference/backend-test-utils.mockservices.lifecycle/)
- [`logger`](https://backstage.io/docs/reference/backend-test-utils.mockservices.logger/)
- [`permissions`](https://backstage.io/docs/reference/backend-test-utils.mockservices.permissions/)
- [`rootConfig`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootConfig/)
- [`rootHealth`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootHealth/)
- [`rootHttpRouter`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootHttpRouter/)
- [`rootLifecycle`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootLifecycle/)
- [`rootLogger`](https://backstage.io/docs/reference/backend-test-utils.mockservices.rootLogger/)
- [`scheduler`](https://backstage.io/docs/reference/backend-test-utils.mockservices.scheduler/)
- [`urlReader`](https://backstage.io/docs/reference/backend-test-utils.mockservices.urlReader/)
- [`userInfo`](https://backstage.io/docs/reference/backend-test-utils.mockservices.userInfo/)
- [`auth`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#auth)
- [`cache`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#cache)
- [`database`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#database)
- [`discovery`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#discovery)
- [`events`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#events)
- [`httpAuth`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#httpAuth)
- [`httpRouter`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#httpRouter)
- [`lifecycle`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#lifecycle)
- [`logger`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#logger)
- [`permissions`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#permissions)
- [`rootConfig`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#rootConfig)
- [`rootHealth`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#rootHealth)
- [`rootHttpRouter`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#rootHttpRouter)
- [`rootLifecycle`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#rootLifecycle/)
- [`rootLogger`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#rootLogger/)
- [`scheduler`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#scheduler/)
- [`urlReader`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#urlReader/)
- [`userInfo`](https://backstage.io/api/stable/modules/_backstage_backend-test-utils.index.mockServices.html#userInfo/)
## Testing Remote Service Interactions
+27 -1
View File
@@ -30,7 +30,9 @@ This naming convention ensures that action names are globally unique across all
## Configuration
The Actions Service can be configured to control which plugins' actions are available:
### Restricting action sources by plugin
The `pluginSources` configuration limits which plugins are allowed to register actions.
```yaml
backend:
@@ -39,6 +41,30 @@ backend:
- catalog
```
### Filtering actions
In addition to plugin-level restrictions, the Actions Service supports filtering actions using include and exclude rules. This allows fine-grained control over which actions are exposed or runnable in a Backstage instance.
#### Include specific actions
```yaml
backend:
actions:
filter:
include:
- 'catalog.*'
```
#### Exclude specific actions
```yaml
backend:
actions:
filter:
exclude:
- 'scaffolder.internal.*'
```
## Using the Service
### Listing Available Actions
@@ -89,7 +89,7 @@ backend:
window: 6s # Time window for rate limiting for single client
incomingRequestLimit: 100 # Number of requests to accept from one client during time window
ipAllowList: ['127.0.0.1'] # IPs to bypass rate limiting
skipSuccesfulRequests: false # Rate limit successful requests
skipSuccessfulRequests: false # Rate limit successful requests
skipFailedRequests: false # Rate limit failed requests
plugin:
# Plugin specific rate limiting
@@ -164,7 +164,7 @@ Note that requests towards `/api/*` will never be handled by the `routes` handle
The root HTTP Router service also allows for configuration of the underlying Node.js HTTP server object. This is useful for modifying settings on the HTTP server itself, such as server [`timeout`](https://nodejs.org/api/http.html#servertimeout), [`keepAliveTimeout`](https://nodejs.org/api/http.html#serverkeepalivetimeout), and [`headersTimeout`](https://nodejs.org/api/http.html#serverheaderstimeout).
A `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration
An `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration
```ts
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
+1 -1
View File
@@ -131,7 +131,7 @@ const MyReactComponent = (...) => {
Depending on the config api in another API is slightly different though, as the
`ConfigApi` implementation is supplied via the App itself and not instantiated
like other APIs. See
[packages/app/src/apis.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/app/src/apis.ts#L66)
[packages/app-legacy/src/apis.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/app-legacy/src/apis.ts#L66)
for an example of how this wiring is done.
For standalone plugin setups in `dev/index.ts`, register a factory with a
+83 -68
View File
@@ -103,13 +103,13 @@ Backstage UI is using light by default under `:root` but you can target it more
[data-theme-mode='light'] {
/* Light theme specific styles */
  --bui-bg-surface-0: #f8f8f8;
  --bui-bg-app: #f8f8f8;
--bui-fg-primary: #000;
}
[data-theme-mode='dark'] {
/* Dark theme specific styles */
  --bui-bg-surface-0: #333333;
  --bui-bg-app: #333333;
--bui-fg-primary: #fff;
}
```
@@ -122,92 +122,107 @@ We recommend starting with a core set of CSS variables to quickly achieve a bran
And if youd like to go even further, you can target specific component class names for advanced customization.
| Token Name | Description |
| -------------------- | --------------------------------------------------------------------------------------------- |
| `--bui-bg-surface-0` | This is used to define the background color of your app. It will only be used once. |
| `--bui-bg-surface-1` | We ar using this color to sit on top of `--bui-bg-surface-0` mostly for `Card`, `Dialog`, ... |
| `--bui-bg-surface-2` | This is for content inside elevated components. This colour is less common. |
| `--bui-bg-solid` | This is used for main actions like primary buttons. |
| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. |
| `--bui-fg-primary` | Your primary text or icon colours. |
| `--bui-fg-secondary` | Your secondary text or icon colours. |
| `--bui-fg-link` | Used for links. |
| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... |
| `--bui-font-regular` | The main font of your app. |
| Token Name | Description |
| -------------------- | ---------------------------------------------------------------------------------------- |
| `--bui-bg-app` | This is used to define the background color of your app. It will only be used once. |
| `--bui-bg-neutral-1` | We are using this color to sit on top of `--bui-bg-app` mostly for `Card`, `Dialog`, ... |
| `--bui-bg-neutral-2` | This is for content inside elevated components. This colour is less common. |
| `--bui-bg-solid` | This is used for main actions like primary buttons. |
| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. |
| `--bui-fg-primary` | Your primary text or icon colours. |
| `--bui-fg-secondary` | Your secondary text or icon colours. |
| `--bui-fg-danger` | Used for error states and destructive actions. |
| `--bui-fg-warning` | Used for warning states and cautionary information. |
| `--bui-fg-success` | Used for success states and positive feedback. |
| `--bui-fg-info` | Used for informational content and neutral status. |
| `--bui-border-1` | Subtle borders for low-contrast separators. |
| `--bui-border-2` | Main borders around surfaces like `Card`, `Dialog`, ... |
| `--bui-font-regular` | The main font of your app. |
<details>
<summary>All available CSS variables</summary>
#### Base colors
These colors are used for special purposes like ring, scrollbar, ...
| Token Name | Description |
| ------------- | ----------------------------------------------------------------------- |
| `--bui-black` | Pure black color. This one should be the same in light and dark themes. |
| `--bui-white` | Pure white color. This one should be the same in light and dark themes. |
| Token Name | Description |
| -------------- | ----------------------------------------------------------------------- |
| `--bui-black` | Pure black color. This one should be the same in light and dark themes. |
| `--bui-white` | Pure white color. This one should be the same in light and dark themes. |
| `--bui-gray-1` | You can use these mostly for backgrounds colors. |
| `--bui-gray-2` | You can use these mostly for backgrounds colors. |
| `--bui-gray-3` | You can use these mostly for backgrounds colors. |
| `--bui-gray-4` | You can use these mostly for backgrounds colors. |
| `--bui-gray-5` | You can use these mostly for backgrounds colors. |
| `--bui-gray-6` | You can use these mostly for backgrounds colors. |
| `--bui-gray-7` | You can use these mostly for backgrounds colors. |
| `--bui-gray-8` | You can use these mostly for backgrounds colors. |
#### Neutral background colors
#### Core background colors
These colors form a layered neutral scale for your application backgrounds. `--bui-bg-app` is the base background color. Each subsequent level (1 through 4) represents an elevated layer, with hover, pressed, and disabled variants for interactive states.
These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. `--bui-bg-surface-0` should mostly use as the main background color of your app.
| Token Name | Description |
| ----------------------------- | ------------------------------------------------------------ |
| `--bui-bg-app` | The base background color of your Backstage instance. |
| `--bui-bg-popover` | The background color used for popovers, tooltips, and menus. |
| `--bui-bg-neutral-1` | First elevated layer. Use for cards, dialogs, and panels. |
| `--bui-bg-neutral-1-hover` | Hover state for elements on neutral-1. |
| `--bui-bg-neutral-1-pressed` | Pressed state for elements on neutral-1. |
| `--bui-bg-neutral-1-disabled` | Disabled state for elements on neutral-1. |
| `--bui-bg-neutral-2` | Second elevated layer. Use for elements on top of neutral-1. |
| `--bui-bg-neutral-2-hover` | Hover state for elements on neutral-2. |
| `--bui-bg-neutral-2-pressed` | Pressed state for elements on neutral-2. |
| `--bui-bg-neutral-2-disabled` | Disabled state for elements on neutral-2. |
| `--bui-bg-neutral-3` | Third elevated layer. Use for elements on top of neutral-2. |
| `--bui-bg-neutral-3-hover` | Hover state for elements on neutral-3. |
| `--bui-bg-neutral-3-pressed` | Pressed state for elements on neutral-3. |
| `--bui-bg-neutral-3-disabled` | Disabled state for elements on neutral-3. |
| `--bui-bg-neutral-4` | Fourth elevated layer. Use for elements on top of neutral-3. |
| `--bui-bg-neutral-4-hover` | Hover state for elements on neutral-4. |
| `--bui-bg-neutral-4-pressed` | Pressed state for elements on neutral-4. |
| `--bui-bg-neutral-4-disabled` | Disabled state for elements on neutral-4. |
| Token Name | Description |
| ------------------------- | ------------------------------------------------ |
| `--bui-bg-surface-0` | The background color of your Backstage instance. |
| `--bui-bg-surface-1` | Use for any panels or elevated surfaces. |
| `--bui-bg-surface-2` | Use for any panels or elevated surfaces. |
| `--bui-bg-surface-3` | Use for any panels or elevated surfaces. |
| `--bui-bg-solid` | Used for solid background colors. |
| `--bui-bg-solid-hover` | Used for solid background colors when hovered. |
| `--bui-bg-solid-pressed` | Used for solid background colors when pressed. |
| `--bui-bg-solid-disabled` | Used for solid background colors when disabled. |
| `--bui-bg-tint` | Used for tint background colors. |
| `--bui-bg-tint-hover` | Used for tint background colors when hovered. |
| `--bui-bg-tint-focus` | Used for tint background colors when active. |
| `--bui-bg-tint-disabled` | Used for tint background colors when disabled. |
| `--bui-bg-danger` | Used to show errors information. |
| `--bui-bg-warning` | Used to show warnings information. |
| `--bui-bg-success` | Used to show success information. |
#### Solid background colors
| Token Name | Description |
| ------------------------- | ----------------------------------------------- |
| `--bui-bg-solid` | Used for solid background colors. |
| `--bui-bg-solid-hover` | Used for solid background colors when hovered. |
| `--bui-bg-solid-pressed` | Used for solid background colors when pressed. |
| `--bui-bg-solid-disabled` | Used for solid background colors when disabled. |
#### Status background colors
| Token Name | Description |
| ------------------ | ----------------------------------- |
| `--bui-bg-danger` | Used to show errors information. |
| `--bui-bg-warning` | Used to show warnings information. |
| `--bui-bg-success` | Used to show success information. |
| `--bui-bg-info` | Used to show informational content. |
#### Foreground colors
Foreground colours are meant to work in pair with a background colours. Typically this would work for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors are prefixed with `fg` to make it easier to identify.
| Token Name | Description |
| ------------------------ | ----------------------------------------------------------------- |
| `--bui-fg-primary` | It should be used on top of main background surfaces. |
| `--bui-fg-secondary` | It should be used on top of main background surfaces. |
| `--bui-fg-link` | It should be used on top of main background surfaces. |
| `--bui-fg-link-hover` | It should be used on top of main background surfaces. |
| `--bui-fg-disabled` | It should be used on top of main background surfaces. |
| `--bui-fg-solid` | It should be used on top of solid background colors. |
| `--bui-fg-tint` | It should be used on top of tint background colors. |
| `--bui-fg-tint-disabled` | It should be used on top of tint background colors when disabled. |
| `--bui-fg-danger` | It should be used on top of danger background colors. |
| `--bui-fg-warning` | It should be used on top of warning background colors. |
| `--bui-fg-success` | It should be used on top of success background colors. |
| Token Name | Description |
| ------------------------ | ------------------------------------------------------ |
| `--bui-fg-primary` | It should be used on top of main background surfaces. |
| `--bui-fg-secondary` | It should be used on top of main background surfaces. |
| `--bui-fg-disabled` | It should be used on top of main background surfaces. |
| `--bui-fg-solid` | It should be used on top of solid background colors. |
| `--bui-fg-danger` | Used for error states and destructive actions. |
| `--bui-fg-warning` | Used for warning states and cautionary information. |
| `--bui-fg-success` | Used for success states and positive feedback. |
| `--bui-fg-info` | Used for informational content and neutral status. |
| `--bui-fg-danger-on-bg` | It should be used on top of danger background colors. |
| `--bui-fg-warning-on-bg` | It should be used on top of warning background colors. |
| `--bui-fg-success-on-bg` | It should be used on top of success background colors. |
| `--bui-fg-info-on-bg` | It should be used on top of info background colors. |
#### Border colors
These border colors are mostly meant to be used as borders on top of any components with low contrast to help as a separator with the different background colors.
| Token Name | Description |
| ----------------------- | --------------------------------------------------- |
| `--bui-border` | It should be used on top of `--bui-bg-surface-1`. |
| `--bui-border-hover` | Used when the component is interactive and hovered. |
| `--bui-border-pressed` | Used when the component is interactive and hovered. |
| `--bui-border-disabled` | Used when the component is disabled. |
| `--bui-border-danger` | It should be used on top of `--bui-bg-danger`. |
| `--bui-border-warning` | It should be used on top of `--bui-bg-warning`. |
| `--bui-border-success` | It should be used on top of `--bui-bg-success`. |
| Token Name | Description |
| ---------------------- | ------------------------------------------------- |
| `--bui-border-1` | Subtle border for low-contrast separators. |
| `--bui-border-2` | It should be used on top of `--bui-bg-neutral-1`. |
| `--bui-border-danger` | It should be used on top of `--bui-bg-danger`. |
| `--bui-border-warning` | It should be used on top of `--bui-bg-warning`. |
| `--bui-border-success` | It should be used on top of `--bui-bg-success`. |
| `--bui-border-info` | It should be used on top of `--bui-bg-info`. |
#### Special colors
+1 -1
View File
@@ -40,7 +40,7 @@ Docs are published to [backstage.io/docs](https://backstage.io/docs). If you
contribute to the documentation, you might want to preview your changes before
submitting them. You'll find the website sources under [/microsite](https://github.com/backstage/backstage/tree/master/microsite)
with instructions for building and locally serving the website in the
[README](/microsite#readme).
[README](https://github.com/backstage/backstage/blob/master/microsite/README.md).
For additional information and helpful guidelines on how to contribute to the documentation, check out these [Documentation Guidelines](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#documentation-guidelines)!
+5 -2
View File
@@ -55,8 +55,11 @@ core
└── Progress.stories.tsx
```
> _Note: make sure your component story file has the following format
> componentName.stories.tsx_
:::note Note
Make sure your component story file has the following format
componentName.stories.tsx
:::
## Running locally
+2 -2
View File
@@ -195,11 +195,11 @@ strategy.
This method can be quite helpful when used in combination with an ingestion
procedure like the
[`GkeEntityProvider`](https://backstage.io/docs/reference/plugin-catalog-backend-module-gcp.gkeentityprovider/)
[`GkeEntityProvider`](https://backstage.io/api/stable/classes/_backstage_plugin-catalog-backend-module-gcp.index.GkeEntityProvider.html)
(installation documented
[here](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-gcp#installation))
or the
[`AwsEKSClusterProcessor`](https://backstage.io/docs/reference/plugin-catalog-backend-module-aws.awseksclusterprocessor/)
[`AwsEKSClusterProcessor`](https://backstage.io/api/stable/classes/_backstage_plugin-catalog-backend-module-aws.index.AwsEKSClusterProcessor.html)
to automatically update the set of clusters tracked by Backstage.
For this method to work any entity that would be using this `Resource` to help drive the Kubernetes details in the Catalog's Entity pages needs to have a `dependsOn` relationship setup. Here's a quick example:
+2 -2
View File
@@ -75,7 +75,7 @@ Backstage app.
If either existing
[cluster locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods)
don't work for your use-case, it is possible to implement a custom
[KubernetesClustersSupplier](https://backstage.io/docs/reference/plugin-kubernetes-backend.kubernetesclusterssupplier).
[KubernetesClustersSupplier](https://backstage.io/api/stable/interfaces/_backstage_plugin-kubernetes-node.KubernetesClustersSupplier.html).
Here's a very simplified example:
@@ -130,7 +130,7 @@ export const kubernetesModuleCustomClusterDiscovery = createBackendModule({
);
// there's also the ability to get access to some of the default implementations of the extension points where
// neccessary:
// necessary:
serviceLocator.addServiceLocator(
async ({ getDefault, clusterSupplier }) => {
// get access to the default service locator:
+1 -1
View File
@@ -86,7 +86,7 @@ Now with this example it will collate all entities that are `kind` equal to `api
:::tip
The filter configuration is implemented using the `EntityFilterQuery` syntax. The [reference documentation on `EntityFilterQuery`](https://backstage.io/docs/reference/catalog-client.entityfilterquery/) provides more details.
The filter configuration is implemented using the `EntityFilterQuery` syntax. The [reference documentation on `EntityFilterQuery`](https://backstage.io/api/stable/types/_backstage_catalog-client.index.EntityFilterQuery.html) provides more details.
:::
@@ -25,7 +25,7 @@ The search plugin is a collection of extensions that implement the search featur
### Installation
Only one step is required to start using the `Search` plugin within declarative integration, so all you have to do is to install the `@backstage/plugin-catalog` and `@backstage/plugin-search` packages, (e.g., [app-next](https://github.com/backstage/backstage/tree/master/packages/app-next)):
Only one step is required to start using the `Search` plugin within declarative integration, so all you have to do is to install the `@backstage/plugin-catalog` and `@backstage/plugin-search` packages, (e.g., [app](https://github.com/backstage/backstage/tree/master/packages/app)):
```sh
yarn add @backstage/plugin-catalog @backstage/plugin-search
+1 -1
View File
@@ -232,7 +232,7 @@ Remember to export your new extension via your plugin's `index.ts` so that it is
export { YourSearchResultListItem } from './plugin.ts';
```
For more details, see the [createSearchResultListItemExtension](https://backstage.io/docs/reference/plugin-search-react.createsearchresultlistitemextension) API reference.
For more details, see the [createSearchResultListItemExtension](https://backstage.io/api/stable/functions/_backstage_plugin-search-react.index.createSearchResultListItemExtension.html) API reference.
### 2. Custom search result extension in the SearchPage
+62
View File
@@ -291,3 +291,65 @@ search:
fuzziness: AUTO
prefixLength: 3;
```
### Custom Authentication Extension Point
For enterprise environments that require dynamic authentication mechanisms such as bearer tokens with automatic rotation, the Elasticsearch module provides an authentication extension point. This is useful when:
- Using OAuth2/OIDC identity providers for service authentication
- Tokens need to be refreshed automatically (e.g., tokens that expire hourly)
- Integrating with internal identity services
- Running Elasticsearch/OpenSearch clusters secured by token-based authentication
To use custom authentication, create a backend module that provides an auth provider:
```ts title="packages/backend/src/modules/elasticsearchAuth.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch';
export default createBackendModule({
pluginId: 'search',
moduleId: 'elasticsearch-custom-auth',
register(env) {
env.registerInit({
deps: {
elasticsearchAuth: elasticsearchAuthExtensionPoint,
},
async init({ elasticsearchAuth }) {
elasticsearchAuth.setAuthProvider({
async getAuthHeaders() {
// Fetch token from your identity service
const token = await myTokenService.getToken();
return { Authorization: `Bearer ${token}` };
},
});
},
});
},
});
```
Then register this module in your backend:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
// Other plugins...
backend.add(import('@backstage/plugin-search-backend'));
backend.add(import('@backstage/plugin-search-backend-module-elasticsearch'));
/* highlight-add-start */
backend.add(import('./modules/elasticsearchAuth'));
/* highlight-add-end */
backend.start();
```
The `getAuthHeaders` method is called before each request, allowing for just-in-time token retrieval and automatic rotation. When an auth provider is configured, it takes precedence over any static authentication in `app-config.yaml`.
:::note Note
Custom authentication is supported for the `elastic`, `opensearch`, and default providers. The `aws` provider uses AWS SigV4 request signing and does not support custom auth providers.
:::
+1 -1
View File
@@ -27,7 +27,7 @@ one organization to the other, especially in production, but is commonly your
Some or all of the endpoints may accept or require an `Authorization` header
with a `Bearer` token, which should then be the Backstage token returned by the
[`identity API`](https://backstage.io/docs/reference/core-plugin-api.identityapiref).
[`identity API`](https://backstage.io/api/stable/variables/_backstage_core-plugin-api.index.identityApiRef.html).
## Entities
@@ -0,0 +1,49 @@
---
id: audit-events
title: Audit Events
description: Tracking access to your Software Catalog.
---
The Catalog backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction within an operation group.
## Entity Events
- **`entity-fetch`**: Retrieves entities.
- **Note:** By default, "low" severity audit events like `entity-fetch` aren't logged because they map to the "debug" level, while Backstage defaults to "info" level logging. To see `entity-fetch` events, update your `app-config.yaml` by setting `backend.auditor.severityLogLevelMappings.low: info`. See the [Auditor Service documentation](https://backstage.io/docs/backend-system/core-services/auditor/#severity-levels-and-default-mappings) for details on severity mappings.
Filter on `queryType`.
- **`all`**: Fetching all entities. (GET `/entities`)
- **`by-id`**: Fetching a single entity using its UID. (GET `/entities/by-uid/:uid`)
- **`by-name`**: Fetching a single entity using its kind, namespace, and name. (GET `/entities/by-name/:kind/:namespace/:name`)
- **`by-query`**: Fetching multiple entities using a filter query. (GET `/entities/by-query`)
- **`by-refs`**: Fetching a batch of entities by their entity refs. (POST `/entities/by-refs`)
- **`ancestry`**: Fetching the ancestry of an entity. (GET `/entities/by-name/:kind/:namespace/:name/ancestry`)
- **`entity-mutate`**: Modifies entities.
Filter on `actionType`.
- **`delete`**: Deleting a single entity. Note: this will not be a permanent deletion and the entity will be restored if the parent location is still present in the catalog. (DELETE `/entities/by-uid/:uid`)
- **`refresh`**: Scheduling an entity refresh. (POST `/entities/refresh`)
- **`entity-validate`**: Validates an entity. (POST `/entities/validate`)
- **`entity-facets`**: Retrieves entity facets. (GET `/entity-facets`)
## Location Events
- **`location-fetch`**: Retrieves locations.
Filter on `actionType`.
- **`all`**: Fetching all locations. (GET `/locations`)
- **`by-id`**: Fetching a single location by ID. (GET `/locations/:id`)
- **`by-entity`**: Fetching locations associated with an entity ref. (GET `/locations/by-entity`)
- **`location-mutate`**: Modifies locations.
- **`create`**: Creating a new location. (POST `/locations`)
- **`delete`**: Deleting a location and its associated entities. (DELETE `/locations/:id`)
- **`location-analyze`**: Analyzes a location. (POST `/locations/analyze`)
@@ -696,3 +696,63 @@ filter:
targetRef:
$in: [group:default/admins, group:default/viewers]
```
### Configure groups, titles, and icons
You can define and customize the tab groups that appear on the entity page, as well as enable icons for both groups and individual tabs.
```yaml
app:
extensions:
# Entity page (new frontend system)
- page:catalog/entity:
config:
# Show icons next to group and tab titles
showNavItemIcons: true
# Optionally override default groups and their icons
groups:
- overview:
title: Overview
icon: dashboard
- quality:
title: Quality
icon: verified
- documentation:
title: Docs
icon: description
```
Notes:
- Icons for groups and tabs are resolved via the app's IconsApi. When using a string icon id (for example `"dashboard"`), ensure that the corresponding icon bundles are enabled/installed in your app (see the [IconBundleBlueprint documentation](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.IconBundleBlueprint.html)).
- Group icons are only rendered if `showNavItemIcons` is set to `true`.
### Overriding or disabling a tab's group (per extension)
Each entity content extension (tabs on the entity page) can declare a default `group` in code. You can override or disable this per installation in `app-config.yaml` using the extension's config:
```yaml
app:
extensions:
# ...
# Example entity content extension instance id
- entity-content:example/my-content:
config:
# Move this tab to a custom group you defined above
group: custom
# Show an icon for this entity content page but only if `showNavItemIcons` is enabled for the `page:catalog/entity` extension
icon: my-icon
# Disassociate from any group and show as a standalone tab
- entity-content:example/another-content:
config:
group: false
```
### Tab icons for entity content
Entity content extensions can also declare an `icon` parameter. When provided as a string, the icon id is looked up via the IconsApi. For the icon to render:
- The entity page must have `showNavItemIcons: true` (see configuration above).
- The icon id must be available in the app's enabled icon bundles.
@@ -3,7 +3,7 @@
## Overview
The Software Catalog in Backstage is intended to capture human mental models using entities and their relationships rather than an exhaustive inventory of all possible things. The focus is on attaching functionality and views centered around these entities. Determining the "edge" where the catalog ends and the external world begins is crucial to ensure that the catalog's scope is appropriate.
The Backstage software catalog serves as a centralized hub for organizing and discovering software components and services. While it excels at providing a high-level overview of these concepts, it may not be the ideal solution for tracking dynamic relationships between components and services in real-time. You can achieve real time views by attaching appropriate tooling to the nodes in the graph through [annotations](https://backstage.io/docs/features/software-catalog/well-known-annotations) and developing custom front-end [plugins](http://localhost:3000/docs/plugins/) that display deployment information and other real-time data.
The Backstage software catalog serves as a centralized hub for organizing and discovering software components and services. While it excels at providing a high-level overview of these concepts, it may not be the ideal solution for tracking dynamic relationships between components and services in real-time. You can achieve real time views by attaching appropriate tooling to the nodes in the graph through [annotations](https://backstage.io/docs/features/software-catalog/well-known-annotations) and developing custom front-end [plugins](https://backstage.io/docs/plugins/create-a-plugin) that display deployment information and other real-time data.
It is worth noting that the Backstage Software Catalog should not be considered the ultimate source of truth, instead, it is advisable to use the Backstage Catalog as a caching mechanism that utilizes a REST API to convey information to the catalog UI and other Backstage plugins. Adopting a GitOps approach is recommended to modify YAML files in Backstage, treating YAML files in repositories as the primary source of truth and using Scaffolder to make changes via the UI and generate a pull request in the repository with the updated changes.
### Descriptor Components used to build the Catalog Graph
@@ -596,7 +596,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
/* highlight-add-next-line */
import { FoobarEntitiesProcessor } from './providers';
@@ -294,7 +294,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { FrobsProvider } from './path/to/class';
export const catalogModuleFrobsProvider = createBackendModule({
@@ -737,7 +737,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { SystemXReaderProcessor } from '../path/to/class';
export const catalogModuleSystemXReaderProcessor = createBackendModule({
+6
View File
@@ -136,6 +136,12 @@ Your Backstage developer portal can be customized by incorporating
[existing open source plugins](https://github.com/backstage/backstage/tree/master/plugins),
or by [building your own](../../plugins/index.md).
## Unprocessed Entities
Sometimes entities fail to process correctly. The **Unprocessed Entities** feature helps Backstage admins find and diagnose these entities to understand the state of the catalog.
To use this feature, check out the documentation for the [catalog-unprocessed-entities plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-unprocessed-entities) and its [backend module](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-unprocessed).
## Links
- [[Blog post] Backstage Service Catalog released in alpha](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)
+1 -1
View File
@@ -40,7 +40,7 @@ used, and the relevant documentation should specify which rule applies where.
Entity ref strings are frequently passed between systems as identifiers of
entities. In those cases the refs should always be complete (have all three
parts). The sender should ensure that the refs are always lowercased in an
`en-US` locale, preferably by using [the `stringifyEntityRef` function](https://backstage.io/docs/reference/catalog-model.stringifyentityref/)
`en-US` locale, preferably by using [the `stringifyEntityRef` function](https://backstage.io/api/stable/functions/_backstage_catalog-model.index.stringifyEntityRef.html)
which does this automatically. The receiver should treat incoming refs case
insensitively to avoid problems with senders who do not obey this rule.
@@ -251,6 +251,46 @@ browser when viewing that user.
This annotation can be used on a [User entity](descriptor-format.md#kind-user)
to note that it originated from that user on GitHub.
### github.com/user-id
```yaml
# Example:
metadata:
annotations:
github.com/user-id: '123456'
```
The value of this annotation is the numeric user ID that identifies a user on
[GitHub](https://github.com) (either the public one, or a private GitHub
Enterprise installation) that is related to this entity. Unlike the username,
which can be changed by the user, the user ID is immutable.
This annotation can be used on a [User entity](descriptor-format.md#kind-user)
to note that it originated from that user on GitHub. It enables the
`userIdMatchingUserEntityAnnotation` sign-in resolver to match users by their
GitHub user ID during authentication.
### gitlab.com/user-id
```yaml
# Example:
metadata:
annotations:
gitlab.com/user-id: '123456'
```
The value of this annotation is the numeric user ID that identifies a user on
[GitLab](https://gitlab.com) (either the public one, or a private GitLab
installation) that is related to this entity. For self-hosted GitLab instances,
the annotation key will be `{integration-host}/user-id` where
`{integration-host}` is the hostname of your GitLab instance. Unlike the
username, which can be changed, the user ID is immutable.
This annotation can be used on a [User entity](descriptor-format.md#kind-user)
to note that it originated from that user on GitLab. It enables the
`userIdMatchingUserEntityAnnotation` sign-in resolver to match users by their
GitLab user ID during authentication.
### gocd.org/pipelines
```yaml
@@ -422,7 +462,7 @@ migrating away from them.
### backstage.io/github-actions-id
This annotation was used for a while to enable the GitHub Actions feature. This
is now instead using the [github.com/project-slug](#github-com-project-slug)
is now instead using the [github.com/project-slug](#githubcomproject-slug)
annotation, with the same value format.
### backstage.io/definition-at-location
@@ -0,0 +1,32 @@
---
id: audit-events
title: Audit Events
description: Tracking access to your Scaffolder.
---
The Scaffolder backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction when needed.
## Template Events
- **`template-parameter-schema`**: Retrieves template parameter schemas. (GET `/v2/templates/:namespace/:kind/:name/parameter-schema`)
## Action Events
- **`action-fetch`**: Retrieves installed actions. (GET `/v2/actions`)
## Task Events
- **`task`**: Operations related to Scaffolder tasks.
Filter on `actionType`.
- **`create`**: Creates a new task. (POST `/v2/tasks`)
- **`list`**: Fetches details of all tasks. (GET `/v2/tasks`)
- **`get`**: Fetches details of a specific task. (GET `/v2/tasks/:taskId`)
- **`cancel`**: Cancels a running task. (POST `/v2/tasks/:taskId/cancel`)
- **`retry`**: Retries a failed task. (POST `/v2/tasks/:taskId/retry`)
- **`stream`**: Retrieves a stream of task logs. (GET `/v2/tasks/:taskId/eventstream`)
- **`events`**: Retrieves a snapshot of task logs. (GET `/v2/tasks/:taskId/events`)
- **`dry-run`**: Creates a dry-run task. (POST `/v2/dry-run`) All audit logs for events associated with dry runs have the `meta.isDryLog` flag set to `true`.
- **`stale-cancel`**: Automated cancellation of stale tasks.
- **`execute`**: Tracks the initiation and completion of a real scaffolder task execution. This event will not occur during dry runs.
@@ -517,8 +517,8 @@ specifying options to the scaffolder backend plugin's `createRouter` function
[nunjucks]: https://mozilla.github.io/nunjucks
[filter]: https://mozilla.github.io/nunjucks/templating.html#filters
[global-fn]: https://mozilla.github.io/nunjucks/templating.html#global-functions
[parseRepoUrl]: https://backstage.io/docs/reference/plugin-scaffolder-node.parserepourl
[CompoundEntityRef]: https://backstage.io/docs/reference/catalog-model.compoundentityref
[parseRepoUrl]: https://backstage.io/api/stable/functions/_backstage_plugin-scaffolder-node.index.parseRepoUrl.html
[CompoundEntityRef]: https://backstage.io/api/stable/types/_backstage_catalog-model.index.CompoundEntityRef.html
[Zod]: https://zod.dev/
[zod-fn]: https://zod.dev/?id=functions
[piped]: https://en.wikipedia.org/wiki/Pipeline_(Unix)#Pipelines_in_command_line_interfaces
@@ -28,13 +28,17 @@ boilerplate code, providing a smooth start:
```sh
$ yarn backstage-cli new
? What do you want to create?
plugin-common - A new isomorphic common plugin package
plugin-node - A new Node.js library plugin package
plugin-react - A new web library plugin package
> scaffolder-module - An module exporting custom actions for @backstage/plugin-scaffolder-backend
web-library - A library package, exporting shared functionality for web environments
node-library - A library package, exporting shared functionality for Node.js environments
catalog-provider-module - An Entity Provider module for the Software Catalog
> scaffolder-backend-module - A module exporting custom actions for @backstage/plugin-scaffolder-backend
frontend-plugin - A new frontend plugin
backend-plugin - A new backend plugin
backend-plugin-module - A new backend module that extends an existing backend plugin
(Move up and down to reveal more choices)
```
When prompted, select the option to generate a scaffolder module. This creates a solid foundation for your custom
When prompted, select the option to generate a `scaffolder-backend-module` using the down arrow key. This creates a solid foundation for your custom
action. Enter the name of the module you wish to create, and the CLI will generate the required files and directory
structure.
@@ -91,8 +95,8 @@ The `createTemplateAction` takes an object which specifies the following:
may ship with the `scaffolder-backend` plugin.
- `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions`
endpoint.
- `schema.input` - A `zod` or JSON schema object for input values to your function
- `schema.output` - A `zod` or JSON schema object for values which are output from the
- `schema.input` - A `zod` schema object for input values to your function
- `schema.output` - A `zod` schema object for values which are output from the
function using `ctx.output`
- `handler` - the actual code which is run as part of the action, with a context
@@ -195,7 +199,7 @@ argument. It looks like the following:
executed successfully on the previous run.
- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action
- `ctx.workspacePath` - a string of the working directory of the template run
- `ctx.input` - an object which should match the `zod` or JSON schema provided in the
- `ctx.input` - an object which should match the `zod` schema provided in the
`schema.input` part of the action definition
- `ctx.output` - a function which you can call to set outputs that match the
`zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
@@ -205,47 +209,12 @@ argument. It looks like the following:
- `ctx.metadata` - an object containing a `name` field, indicating the template
name. More metadata fields may be added later.
## Registering Custom Actions
To register your new custom action in the Backend System, you will need to create a backend module. Here is a very
simplified example of how to do that:
```ts title="packages/backend/src/index.ts"
/* highlight-add-start */
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-end */
/* highlight-add-start */
const scaffolderModuleCustomExtensions = createBackendModule({
pluginId: 'scaffolder', // name of the plugin that the module is targeting
moduleId: 'custom-extensions',
register(env) {
env.registerInit({
deps: {
scaffolder: scaffolderActionsExtensionPoint,
// ... and other dependencies as needed
},
async init({ scaffolder /* ..., other dependencies */ }) {
// Here you have the opportunity to interact with the extension
// point before the plugin itself gets instantiated
scaffolder.addActions(createNewFileAction()); // just an example
},
});
},
});
/* highlight-add-end */
const backend = createBackend();
backend.add(import('@backstage/plugin-scaffolder-backend'));
/* highlight-add-next-line */
backend.add(scaffolderModuleCustomExtensions);
```
### Using Core Services in Custom Actions
If your custom action requires core services such as `config` or `cache` they can be imported in the dependencies and
passed to the custom action function.
```ts title="packages/backend/src/index.ts"
```ts title="module.ts"
import {
coreServices,
createBackendModule,
@@ -22,7 +22,7 @@ Field extensions are a way to combine an ID, a `React` Component and a
the `Scaffolder` frontend plugin in your own `App.tsx`.
You can create your own Field Extension by using the
[`createScaffolderFieldExtension`](https://backstage.io/docs/reference/plugin-scaffolder.createscaffolderfieldextension)
[`createScaffolderFieldExtension`](https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.createScaffolderFieldExtension.html)
`API` like below.
As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern:
@@ -151,7 +151,7 @@ const routes = (
### Async Validation Function
A validation function can be asynchronous and use [Utility APIs](https://backstage.io/docs/api/utility-apis/) via the `ApiHolder` in the [field validation context](https://backstage.io/docs/reference/plugin-scaffolder-react.customfieldvalidator). The example below uses the `catalogApiRef` to check if the submitted value (in this scenario an entity ref) exists in the catalog.
A validation function can be asynchronous and use [Utility APIs](https://backstage.io/docs/api/utility-apis/) via the `ApiHolder` in the [field validation context](https://backstage.io/api/stable/types/_backstage_plugin-scaffolder-react.index.CustomFieldValidator.html). The example below uses the `catalogApiRef` to check if the submitted value (in this scenario an entity ref) exists in the catalog.
```tsx
import { FieldValidation } from '@rjsf/utils';
@@ -16,7 +16,7 @@ This is the same [field](https://rjsf-team.github.io/react-jsonschema-form/docs/
## Registering a React component as a custom step layout
The [createScaffolderLayout](https://backstage.io/docs/reference/plugin-scaffolder-react.createscaffolderlayout) function is used to mark a component as a custom step layout:
The [createScaffolderLayout](https://backstage.io/api/stable/functions/_backstage_plugin-scaffolder-react.index.createScaffolderLayout.html) function is used to mark a component as a custom step layout:
```tsx
import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
+6 -6
View File
@@ -89,12 +89,12 @@ page header, TechDocs Addons whose location is `Header` will not be rendered.
Addons can, in principle, be provided by any plugin! To make it easier to
discover available Addons, we've compiled a list of them here:
| Addon | Package/Plugin | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`techDocsExpandableNavigationAddonModule`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
| [`techDocsReportIssueAddonModule`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
| [`techDocsTextSizeAddonModule`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
| [`techDocsLightBoxAddonModule`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
| Addon | Package/Plugin | Description |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`techDocsExpandableNavigationAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsExpandableNavigationAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
| [`techDocsReportIssueAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
| [`techDocsTextSizeAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
| [`techDocsLightBoxAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsLightBoxAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
Got an Addon to contribute? Feel free to add a row above!
+6 -6
View File
@@ -126,12 +126,12 @@ page header, TechDocs Addons whose location is `Header` will not be rendered.
Addons can, in principle, be provided by any plugin! To make it easier to
discover available Addons, we've compiled a list of them here:
| Addon | Package/Plugin | Description |
| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`<ExpandableNavigation />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
| [`<ReportIssue />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
| [`<TextSize />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
| [`<LightBox />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
| Addon | Package/Plugin | Description |
| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`<ExpandableNavigation />`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ExpandableNavigation.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
| [`<ReportIssue />`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.ReportIssue.html) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
| [`<TextSize />`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.TextSize.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
| [`<LightBox />`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.index.LightBox.html) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). |
Got an Addon to contribute? Feel free to add a row above!
+1 -1
View File
@@ -271,5 +271,5 @@ upon your cloud storage provider -
You are welcome to contribute to TechDocs CLI to improve it and support new
features! See the project
[README](https://github.com/backstage/backstage/blob/main/src/packages/techdocs-cli/README.md)
[README](https://github.com/backstage/backstage/blob/master/packages/techdocs-cli/README.md)
for more information.
+465 -130
View File
@@ -4,205 +4,540 @@ title: TechDocs Configuration Options
description: Reference documentation for configuring TechDocs using app-config.yaml
---
Using the `app-config.yaml` in the Backstage app, you can configure TechDocs
using several options. This page serves as a reference to all the available
configuration options for TechDocs.
Using the `app-config.yaml` in the Backstage app, you can configure TechDocs using several options. This page serves as a reference to all the available configuration options for TechDocs.
## Generator Configuration
`techdocs.generator` is used to configure how documentation sites are generated using MkDocs.
### Run In
`techdocs.generator.runIn`
**Options:** `'docker'` or `'local'`
This determines how to run the generator - whether to spin up the techdocs-container docker image or to run mkdocs locally (assuming all the dependencies are taken care of).
You want to change this to `'local'` if you are running Backstage using your own custom Docker setup and want to avoid running into Docker in Docker situation. [Read more here](https://backstage.io/docs/features/techdocs/getting-started/#disabling-docker-in-docker-situation-optional).
**Example:**
```yaml
# File: app-config.yaml
techdocs:
# techdocs.generator is used to configure how documentation sites are generated using MkDocs.
generator:
# techdocs.generator.runIn can be either 'docker' or 'local'. This is to determine how to run the generator - whether to
# spin up the techdocs-container docker image or to run mkdocs locally (assuming all the dependencies are taken care of).
# You want to change this to 'local' if you are running Backstage using your own custom Docker setup and want to avoid running
# into Docker in Docker situation. Read more here
# https://backstage.io/docs/features/techdocs/getting-started/#disabling-docker-in-docker-situation-optional
runIn: 'docker'
```
# (Optional) techdocs.generator.dockerImage can be used to control the docker image used during documentation generation. This can be useful
# if you want to use MkDocs plugins or other packages that are not included in the default techdocs-container (spotify/techdocs).
# NOTE: This setting is only used when techdocs.generator.runIn is set to 'docker'.
### Docker Image
`techdocs.generator.dockerImage`
(Optional) This can be used to control the docker image used during documentation generation. This can be useful if you want to use MkDocs plugins or other packages that are not included in the default techdocs-container (spotify/techdocs).
**Note:** This setting is only used when `techdocs.generator.runIn` is set to `'docker'`.
**Example:**
```yaml
techdocs:
generator:
runIn: 'docker'
dockerImage: 'spotify/techdocs'
```
# (Optional) techdocs.generator.pullImage can be used to disable pulling the latest docker image by default. This can be useful when you are
# using a custom techdocs.generator.dockerImage and you have a custom docker login requirement. For example, you need to login to
# AWS ECR to pull the docker image.
# NOTE: Disabling this requires the docker image was pulled by other means before running the techdocs generator.
### Pull Image
pullImage: true
`techdocs.generator.pullImage`
(Optional) This can be used to disable pulling the latest docker image by default. This can be useful when you are using a custom `techdocs.generator.dockerImage` and you have a custom docker login requirement. For example, you need to login to AWS ECR to pull the docker image.
**Note:** Disabling this requires the docker image was pulled by other means before running the techdocs generator.
**Example:**
```yaml
techdocs:
generator:
runIn: 'docker'
dockerImage: 'custom-registry/techdocs'
pullImage: false
```
### MkDocs Configuration
#### Omit TechDocs Core Plugin
`techdocs.generator.mkdocs.omitTechdocsCorePlugin`
(Optional) This can be used to disable automatic addition of techdocs-core plugin to the mkdocs.yaml files. Defaults to `false`, which means that the techdocs-core plugin is always added to the mkdocs file.
**Example:**
```yaml
techdocs:
generator:
mkdocs:
# (Optional) techdocs.generator.omitTechdocsCoreMkdocsPlugin can be used to disable automatic addition of techdocs-core plugin to the mkdocs.yaml files.
# Defaults to false, which means that the techdocs-core plugin is always added to the mkdocs file.
omitTechdocsCorePlugin: false
```
# (Optional and not recommended) Configures the techdocs generator to
# attempt to ensure an index.md exists falling back to using <docs-dir>/README.md
# or README.md in case a default <docs-dir>/index.md is not provided.
# Note that https://www.mkdocs.org/user-guide/configuration/#edit_uri behavior
# will be broken in these scenarios.
#### Legacy Copy README to Index
`techdocs.generator.mkdocs.legacyCopyReadmeMdToIndexMd`
(Optional and not recommended) Configures the techdocs generator to attempt to ensure an index.md exists falling back to using `<docs-dir>/README.md` or `README.md` in case a default `<docs-dir>/index.md` is not provided.
**Note:** https://www.mkdocs.org/user-guide/configuration/#edit_uri behavior will be broken in these scenarios.
**Example:**
```yaml
techdocs:
generator:
mkdocs:
legacyCopyReadmeMdToIndexMd: false
```
# (Optional) Configures the default plugins which should be added
# automatically to every mkdocs.yaml file. This simplifies the usage as
# e.g. styling plugins can be added once for all.
# Make sure that the defined plugins are installed locally / in the Docker
# image.
# By default, only the techdocs-core plugin will be added (except if
# omitTechdocsCorePlugin: true).
#### Default Plugins
`techdocs.generator.mkdocs.defaultPlugins`
(Optional) Configures the default plugins which should be added automatically to every mkdocs.yaml file. This simplifies the usage as e.g. styling plugins can be added once for all.
Make sure that the defined plugins are installed locally / in the Docker image. By default, only the techdocs-core plugin will be added (except if `omitTechdocsCorePlugin: true`).
**Example:**
```yaml
techdocs:
generator:
mkdocs:
defaultPlugins: ['techdocs-core']
```
# techdocs.builder can be either 'local' or 'external'.
# Using the default build strategy, if builder is set to 'local' and you open a TechDocs page,
# techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwards.
# This is the "Basic" setup of the TechDocs Architecture.
# Using the default build strategy, if builder is set to 'external' (or anything other than 'local'), techdocs-backend
# will only fetch the docs and will NOT try to generate and publish.
# In this case, we assume that docs are being built by an external process (e.g. in the CI/CD pipeline of the repository).
# This is the "Recommended" setup of the architecture.
# Note that custom build strategies may alter this behaviour.
# Read more about the "Basic" and "Recommended" setups here https://backstage.io/docs/features/techdocs/architecture
# Read more about build strategies here: https://backstage.io/docs/features/techdocs/concepts#techdocs-build-strategy
## Builder Configuration
`techdocs.builder`
**Options:** `'local'` or `'external'`
Using the default build strategy:
- If builder is set to `'local'` and you open a TechDocs page, techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwards. This is the **"Basic"** setup of the TechDocs Architecture.
- If builder is set to `'external'` (or anything other than `'local'`), techdocs-backend will only fetch the docs and will NOT try to generate and publish. In this case, we assume that docs are being built by an external process (e.g. in the CI/CD pipeline of the repository). This is the **"Recommended"** setup of the architecture.
**Note:** Custom build strategies may alter this behaviour.
[Read more about the "Basic" and "Recommended" setups](https://backstage.io/docs/features/techdocs/architecture)
[Read more about build strategies](https://backstage.io/docs/features/techdocs/concepts#techdocs-build-strategy)
**Example:**
```yaml
techdocs:
builder: 'local'
```
# techdocs.publisher is used to configure the Storage option, whether you want to use the local filesystem to store generated docs
# or you want to use External storage providers like Google Cloud Storage, AWS S3, etc.
## Publisher Configuration
`techdocs.publisher`
This is used to configure the storage option, whether you want to use the local filesystem to store generated docs or you want to use external storage providers like Google Cloud Storage, AWS S3, etc.
### Publisher Type
`techdocs.publisher.type`
This determines where generated documentation files are stored.
**Options:**
- `'local'` - techdocs-backend will create a 'static' directory at its root to store generated documentation files
- `'googleGcs'` - techdocs-backend will use a Google Cloud Storage Bucket
- `'awsS3'` - techdocs-backend will use an Amazon Web Service (AWS) S3 bucket
- `'azureBlobStorage'` - techdocs-backend will use Azure Blob Storage
**Example:**
```yaml
techdocs:
publisher:
# techdocs.publisher.type can be - 'local' or 'googleGcs' or 'awsS3' or 'azureBlobStorage'.
# When set to 'local', techdocs-backend will create a 'static' directory at its root to store generated documentation files.
# When set to 'googleGcs', techdocs-backend will use a Google Cloud Storage Bucket to store generated documentation files.
# When set to 'awsS3', techdocs-backend will use an Amazon Web Service (AWS) S3 bucket to store generated documentation files.
type: 'local'
```
# Optional when techdocs.publisher.type is set to 'local'.
### Local Storage
`techdocs.publisher.local`
This is used to configure the local storage option.
Optional when `techdocs.publisher.type` is set to `'local'`.
#### Publish Directory
`techdocs.publisher.local.publishDirectory`
(Optional) This specifies where the generated documentation is stored.
**Example:**
```yaml
techdocs:
publisher:
type: 'local'
local:
# (Optional). Set this to specify where the generated documentation is stored.
publishDirectory: '/path/to/local/directory'
```
# Required when techdocs.publisher.type is set to 'googleGcs'. Skip otherwise.
### Google Cloud Storage
`techdocs.publisher.googleGcs`
This is used to configure the Google Cloud Storage option.
Required when `techdocs.publisher.type` is set to `'googleGcs'`. Skip otherwise.
#### Bucket Name
`techdocs.publisher.googleGcs.bucketName`
(Required) The Cloud Storage Bucket Name
**Example:**
```yaml
techdocs:
publisher:
type: 'googleGcs'
googleGcs:
# (Required) Cloud Storage Bucket Name
bucketName: 'techdocs-storage'
```
# (Optional) Location in storage bucket to save files
# If not set, the default location will be the root of the storage bucket
bucketRootPath: '/'
#### Bucket Root Path
# (Optional) An API key is required to write to a storage bucket.
# If missing, GOOGLE_APPLICATION_CREDENTIALS environment variable will be used.
# https://cloud.google.com/docs/authentication/production
`techdocs.publisher.googleGcs.bucketRootPath`
(Optional) The Location in storage bucket to save files. If not set, the default location will be the root of the storage bucket.
**Example:**
```yaml
techdocs:
publisher:
type: 'googleGcs'
googleGcs:
bucketName: 'techdocs-storage'
bucketRootPath: '/docs'
```
#### Credentials
`techdocs.publisher.googleGcs.credentials`
(Optional) An API key required to write to a storage bucket.
If missing `GOOGLE_APPLICATION_CREDENTIALS` environment variable will be used. https://cloud.google.com/docs/authentication/production
**Example:**
```yaml
techdocs:
publisher:
type: 'googleGcs'
googleGcs:
bucketName: 'techdocs-storage'
credentials:
$file: '/path/to/google_application_credentials.json'
```
# Required when techdocs.publisher.type is set to 'awsS3'. Skip otherwise.
### AWS S3
`techdocs.publisher.awsS3`
This is used to configure the AWS S3 option.
Required when `techdocs.publisher.type` is set to `'awsS3'`. Skip otherwise.
#### Bucket Name
`techdocs.publisher.awsS3.bucketName`
(Required) The AWS S3 Bucket Name
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
# (Required) AWS S3 Bucket Name
bucketName: 'techdocs-storage'
```
# (Optional) Location in storage bucket to save files
# If not set, the default location will be the root of the storage bucket
bucketRootPath: '/'
#### Bucket Root Path
# (Optional) The AWS account ID where the storage bucket is located.
# Credentials for the account ID must be configured in the 'aws' app config section.
# See the integration-aws-node package for details on how to configure credentials in
# the 'aws' app config section.
# https://www.npmjs.com/package/@backstage/integration-aws-node
# If account ID is not set and no credentials are set, environment variables or aws config file will be used to authenticate.
# https://www.npmjs.com/package/@aws-sdk/credential-provider-node
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
`techdocs.publisher.awsS3.bucketRootPath`
(Optional) The Location in storage bucket to save files. If not set, the default location will be the root of the storage bucket.
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
bucketRootPath: '/documentation'
```
#### Account ID
`techdocs.publisher.awsS3.accountId`
The AWS account ID where the storage bucket is located. Credentials for the account ID must be configured in the `aws` app config section. See the [integration-aws-node package](https://www.npmjs.com/package/@backstage/integration-aws-node) for details on how to configure credentials in the `aws` app config section.
If account ID is not set and no credentials are set, environment variables or AWS config file will be used to authenticate.
https://www.npmjs.com/package/@aws-sdk/credential-provider-node
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
accountId: ${TECHDOCS_AWSS3_ACCOUNT_ID}
```
# (Optional) AWS credentials to use to write to the storage bucket.
# This configuration section is now deprecated.
# Configuring the account ID is now preferred, with credentials in the 'aws' app config section.
# If credentials are not set and no account ID is set, environment variables or aws config file will be used to authenticate.
# https://www.npmjs.com/package/@aws-sdk/credential-provider-node
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
#### Credentials
`techdocs.publisher.awsS3.credentials`
(Optional) AWS credentials to use to write to the storage bucket. This configuration section is now **deprecated**. Configuring the account ID is now preferred, with credentials in the `aws` app config section.
If credentials are not set and no account ID is set, environment variables or AWS config file will be used to authenticate.
https://www.npmjs.com/package/@aws-sdk/credential-provider-node
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
credentials:
accessKeyId: ${TECHDOCS_AWSS3_ACCESS_KEY_ID_CREDENTIAL}
secretAccessKey: ${TECHDOCS_AWSS3_SECRET_ACCESS_KEY_CREDENTIAL}
```
# (Optional) AWS Region of the bucket.
# If not set, AWS_REGION environment variable or aws config file will be used.
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
#### Region
`techdocs.publisher.awsS3.region`
(Optional) The AWS Region of the bucket.
If not set, `AWS_REGION` environment variable or AWS config file will be used.
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
region: ${AWS_REGION}
```
# (Optional) Endpoint URI to send requests to.
# If not set, the default endpoint is built from the configured region.
# https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/s3clientconfig.html#endpoint
#### Endpoint
`techdocs.publisher.awsS3.endpoint`
(Optional) The Endpoint URI to send requests to.
If not set, the default endpoint is built from the configured region.
https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/s3clientconfig.html#endpoint
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
endpoint: ${AWS_ENDPOINT}
```
# (Optional) HTTPS proxy to use for S3 Requests
# Defaults to using no proxy
# This allows docs to be published and read from behind a proxy
#### HTTPS Proxy
`techdocs.publisher.awsS3.httpsProxy`
(Optional) The HTTPS proxy to use for S3 Requests. Defaults to using no proxy. This allows docs to be published and read from behind a proxy.
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
httpsProxy: ${HTTPS_PROXY}
```
# (Optional) Whether to use path style URLs when communicating with S3.
# Defaults to false.
# This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs.
s3ForcePathStyle: false
#### S3 Force Path Style
# (Optional) AWS Server Side Encryption
# Defaults to undefined.
# If not set, encrypted buckets will fail to publish.
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html
sse: 'aws:kms' # or AES256
`techdocs.publisher.awsS3.s3ForcePathStyle`
# Required when techdocs.publisher.type is set to 'azureBlobStorage'. Skip otherwise.
(Optional) Whether to use path style URLs when communicating with S3. Defaults to `false`. This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs.
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
s3ForcePathStyle: true
```
#### Server Side Encryption
`techdocs.publisher.awsS3.sse`
(Optional) AWS Server Side Encryption. Defaults to undefined. If not set, encrypted buckets will fail to publish.
https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html
**Options:** `'aws:kms'` or `'AES256'`
**Example:**
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'techdocs-storage'
sse: 'aws:kms'
```
### Azure Blob Storage
`techdocs.publisher.azureBlobStorage`
Required when `techdocs.publisher.type` is set to `'azureBlobStorage'`. Skip otherwise.
#### Container Name
`techdocs.publisher.azureBlobStorage.containerName`
(Required) Azure Blob Storage Container Name
**Example:**
```yaml
techdocs:
publisher:
type: 'azureBlobStorage'
azureBlobStorage:
# (Required) Azure Blob Storage Container Name
containerName: 'techdocs-storage'
```
# (Optional) Azure blob storage connection string.
# Can be useful for local testing through azurite
# Defaults to undefined
# if provided, takes higher priority, 'techdocs.publisher.azureBlobStorage.credentials' will become irrelevant
connectionString: ''
#### Connection String
# (Required) An account name is required to write to a storage blob container.
# https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
`techdocs.publisher.azureBlobStorage.connectionString`
(Optional) Azure blob storage connection string. Can be useful for local testing through azurite. Defaults to undefined. If provided, takes higher priority, and `techdocs.publisher.azureBlobStorage.credentials` will become irrelevant.
**Example:**
```yaml
techdocs:
publisher:
type: 'azureBlobStorage'
azureBlobStorage:
containerName: 'techdocs-storage'
connectionString: 'DefaultEndpointsProtocol=https;AccountName=...'
```
#### Credentials
`techdocs.publisher.azureBlobStorage.credentials`
(Required) An account name to write to a storage blob container.
https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
(Optional) An account key is required to write to a storage container. If missing, `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` environment variables will be used.
https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json
**Example:**
```yaml
techdocs:
publisher:
type: 'azureBlobStorage'
azureBlobStorage:
containerName: 'techdocs-storage'
credentials:
accountName: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME}
# (Optional) An account key is required to write to a storage container.
# If missing,AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET environment variable will be used.
# https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json
accountKey: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY}
```
# (Optional and not recommended) Prior to version [0.x.y] of TechDocs, docs
# sites could only be accessed over paths with case-sensitive entity triplets
# e.g. (namespace/Kind/name). If you are upgrading from an older version of
# TechDocs and are unable to perform the necessary migration of files in your
# external storage, you can set this value to `true` to temporarily revert to
# the old, case-sensitive entity triplet behavior.
## Legacy Case Sensitive Triplet Paths
`techdocs.legacyUseCaseSensitiveTripletPaths`
(Optional and not recommended) Prior to version [0.x.y] of TechDocs, docs sites could only be accessed over paths with case-sensitive entity triplets e.g. (namespace/Kind/name). If you are upgrading from an older version of TechDocs and are unable to perform the necessary migration of files in your external storage, you can set this value to `true` to temporarily revert to the old, case-sensitive entity triplet behavior.
**Example:**
```yaml
techdocs:
legacyUseCaseSensitiveTripletPaths: false
```
# techdocs.cache is optional, and is only recommended when you've configured
# an external techdocs.publisher.type above. Also requires backend.cache to
# be configured with a valid cache store. Configure techdocs.cache.ttl to
# enable caching of techdocs assets.
## Cache Configuration
`techdocs.cache`
(Optional) `techdocs.cache` is only recommended when you've configured an external `techdocs.publisher.type` above. Also requires `backend.cache` to be configured with a valid cache store. Configure `techdocs.cache.ttl` to enable caching of techdocs assets.
### TTL
`techdocs.cache.ttl`
Represents the number of milliseconds a statically built asset should stay cached. Cache invalidation is handled automatically by the frontend, which compares the build times in cached metadata vs. canonical storage, allowing long TTLs (e.g. 1 month/year).
**Example:**
```yaml
techdocs:
cache:
# Represents the number of milliseconds a statically built asset should
# stay cached. Cache invalidation is handled automatically by the frontend,
# which compares the build times in cached metadata vs. canonical storage,
# allowing long TTLs (e.g. 1 month/year)
ttl: 3600000
```
# (Optional) The time (in milliseconds) that the TechDocs backend will wait
# for a cache service to respond before continuing on as though the cached
# object was not found (e.g. when the cache service is unavailable). The
# default value is 1000
### Read Timeout
`techdocs.cache.readTimeout`
(Optional) The time (in milliseconds) that the TechDocs backend will wait for a cache service to respond before continuing on as though the cached object was not found (e.g. when the cache service is unavailable). The default value is 1000.
**Example:**
```yaml
techdocs:
cache:
ttl: 3600000
readTimeout: 500
```
+3 -3
View File
@@ -10,15 +10,15 @@ description: How to use the built-in TechDocs extension points
The TechDocs backend plugin provides the following extension points:
- `techdocsPreparerExtensionPoint`
- Register a custom docs [PreparerBase extension](https://backstage.io/docs/reference/plugin-techdocs-node.preparerbase/)
- Register a custom docs [PreparerBase extension](https://backstage.io/api/stable/types/_backstage_plugin-techdocs-node.PreparerBase.html)
- Ideal for when you want a custom type of docs created for a specific entity type
- `techdocsBuildsExtensionPoint`
- Allows overriding the build phase Winston log transport (by default does not log to console)
- Allows overriding the [DocsBuildStrategy](https://backstage.io/docs/reference/plugin-techdocs-node.docsbuildstrategy/)
- Allows overriding the [DocsBuildStrategy](https://backstage.io/api/stable/interfaces/_backstage_plugin-techdocs-node.DocsBuildStrategy.html)
- `techdocsPublisherExtensionPoint`
- Register a custom docs publisher
- `techdocsGeneratorExtensionPoint`
- Register a custom [TechdocsGenerator](https://backstage.io/docs/reference/plugin-techdocs-node.techdocsgenerator/)
- Register a custom [TechdocsGenerator](https://backstage.io/api/stable/classes/_backstage_plugin-techdocs-node.TechdocsGenerator.html)
Extension points are exported from `@backstage/plugin-techdocs-backend`.
@@ -26,6 +26,8 @@ const myPage = PageBlueprint.make({
export default createFrontendPlugin({
pluginId: 'my-plugin',
title: 'My Plugin',
icon: MyPluginIcon,
extensions: [myPage],
});
```
@@ -36,6 +38,30 @@ Each plugin needs an ID, which is used to uniquely identify the plugin within an
The plugin ID should generally be part of the of the package name and use kebab-case. See both the [frontend naming patterns section](./50-naming-patterns.md), as well as the [package metadata section](../../tooling/package-metadata.md#name) for more information.
### `title` option
The display title of the plugin, used in page headers and navigation. Falls back to the plugin ID if not provided.
```tsx
export default createFrontendPlugin({
pluginId: 'my-plugin',
title: 'My Plugin',
extensions: [...],
});
```
### `icon` option
The display icon of the plugin, used in page headers and navigation. The type is `IconElement` (`JSX.Element | null`) from `@backstage/frontend-plugin-api`. Icons should be exactly 24x24 pixels in size.
```tsx
export default createFrontendPlugin({
pluginId: 'my-plugin',
icon: <MyPluginIcon />,
extensions: [...],
});
```
### `extensions` option
These are the [extensions](./20-extensions.md) that the plugin provides to the app. Note that you should not export any of these extensions separately from the plugin package, as they can already by accessed via the `getExtension` method of the plugin instance using the extension ID.
@@ -174,6 +174,8 @@ const navigationExtension = createExtension({
The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can be attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly one attached extension.
Another option that can be used when creating an extension input is the `internal: true` option, which restricts the input to only accept extensions from the same plugin as the extension defining the input. Extensions from other plugins that attempt to attach to an internal input will be ignored, and a warning will be reported. This is useful when you want to limit extensibility to overrides and modules of your plugin, rather than letting it be open to any plugin.
So how can we now attach the output to the parent extension's input? If we think about a navigation component, like the Sidebar in Backstage, there might be plugins that want to attach a link to their plugin to this navigation component. In this case the plugin only needs to know the extension `id` and the name of the extension `input` to attach the extension `output` returned by the `factory` to the specified extension:
```tsx
@@ -335,23 +337,11 @@ const routableExtension = createExtension({
});
```
## Multiple attachment points
## Sharing extensions across multiple locations
For some cases it can be useful to attach extensions to multiple parents. An example of this are Scaffolder field extensions or TechDocs addons that are consumed by multiple extensions. Specifying multiple attachments is done by providing an array of attachment points to the `attachTo` property of the extension. Keep in mind that this increases the complexity of your extension tree and should only be done when necessary. The following example shows how to attach our example extension to multiple parents:
If you need to make extensions available in multiple locations throughout your app, use a Utility API that collects the extensions and allows multiple parent extensions to consume them. This pattern provides better separation of concerns and makes data flow more explicit.
```tsx
const extension = createExtension({
name: 'my-extension',
attachTo: [
{ id: 'my-first-parent', input: 'content' },
{ id: 'my-second-parent', input: 'children' }, // The input names do not need to match
],
output: [coreExtensionData.reactElement],
factory() {
return [coreExtensionData.reactElement(<div>Hello World</div>)];
},
});
```
See the [Sharing Extensions Across Multiple Locations](./27-sharing-extensions.md) guide for a complete explanation of this pattern with detailed examples.
## Relative attachment points
@@ -361,7 +351,7 @@ When creating an extension or an [extension blueprint](./23-extension-blueprints
// Parent extension with a fixed attachment point
const parentExtension = createExtension({
kind: 'section',
attachTo: [{ id: 'app/some-fixed-extension', input: 'children' }],
attachTo: { id: 'app/some-fixed-extension', input: 'children' },
inputs: {
content: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
@@ -383,7 +373,7 @@ const parentExtension = createExtension({
// Child extension with a relative attachment point
const childExtension = createExtension({
kind: 'section-content',
attachTo: [{ relative: { kind: 'section' }, input: 'content' }],
attachTo: { relative: { kind: 'section' }, input: 'content' },
output: [coreExtensionData.reactElement],
factory() {
return [coreExtensionData.reactElement(<p>Section Content</p>)];
@@ -409,7 +399,7 @@ const parent = createExtension({
// Create a child extension that attaches to the parent's input
const child = createExtension({
attachTo: page.inputs.children, // Direct reference to the input
attachTo: parent.inputs.children, // Direct reference to the input
output: [coreExtensionData.reactElement], // Outputs are verified against the parent input
// other options...
});
@@ -0,0 +1,173 @@
---
id: sharing-extensions
title: Sharing Extensions Across Multiple Locations
sidebar_label: Sharing Extensions
description: Using Utility APIs to share extensions across multiple locations in your app
---
Some plugins may need to provide extensibility that can be reused in multiple locations throughout the app. For example, in the pattern demonstrated on this page, a plugin can be made extensible by allowing widgets to be contributed that are then rendered on multiple pages. To achieve this, the recommended pattern is to use a Utility API that collects the extensions and makes them available throughout the plugin or the app.
## Overview
This pattern combines a Utility API with an extension blueprint to:
1. Define the extension data types and API interface
2. Provide a blueprint for creating extensions
3. Create a Utility API extension that collects extensions as input
4. Consume the extensions via the API
This approach provides a native integration with the frontend system, allowing to further rely on features like making the extensions configurable or have further extension points.
## Basic Pattern
The following example demonstrates this pattern using widgets that can be displayed on multiple pages. However, this pattern is flexible and can be adapted for many different scenarios where you need to:
- Share the same type of extension across different pages or views
- Allow third-party plugins to contribute extensions in a decoupled way
- Aggregate similar functionality from multiple sources in a consistent way
The core concepts remain the same regardless of what type of functionality you're sharing.
### 1. Define the Extension Data Types and API Interface
First, in your plugin's `-react` package (e.g., `backstage-plugin-foo-react`), define the widget types and API interface:
```tsx title="in backstage-plugin-foo-react"
import { createApiRef } from '@backstage/frontend-plugin-api';
import { ComponentType } from 'react';
export interface FooWidgetProps {
title: string;
}
// Define what data each widget provides, prefer using lazy loading for large pieces of functionality like components
export interface FooWidget {
title: string;
size: 'small' | 'medium' | 'large';
loader: () => Promise<ComponentType<FooWidgetProps>>;
}
// Define the API interface
export interface FooWidgetsApi {
getWidgets(): FooWidget[];
}
// Create the API reference
export const fooWidgetsApiRef = createApiRef<FooWidgetsApi>({
id: 'plugin.foo.widgets',
});
```
### 2. Provide a Blueprint for Creating Extensions
Next, also in your `-react` package (e.g., `backstage-plugin-foo-react`), create a blueprint that creates extensions. The blueprint creates an internal data reference and exposes it via the `dataRefs` property. This blueprint will be exported for other plugins to use:
```tsx title="in backstage-plugin-foo-react"
import {
createExtensionBlueprint,
createExtensionDataRef,
ExtensionBoundary,
} from '@backstage/frontend-plugin-api';
const fooWidgetDataRef = createExtensionDataRef<FooWidget>().with({
id: 'foo.widget',
});
export const FooWidgetBlueprint = createExtensionBlueprint({
kind: 'foo-widget',
// Attach extensions created with this blueprint to the API extension that will be created in the next step
attachTo: { id: 'api:foo/widgets', input: 'widgets' },
output: [fooWidgetDataRef],
*factory(params: FooWidget, { node }) {
yield fooWidgetDataRef({
title: params.title,
size: params.size,
loader: ExtensionBoundary.lazyComponent(node, params.loader),
});
},
dataRefs: {
widget: fooWidgetDataRef,
},
});
```
### 3. Create a Utility API Extension that Collects Extensions
In your main plugin package (e.g., `backstage-plugin-foo`), create a Utility API extension that collects widgets as input. Note that this imports the blueprint's data reference via `FooWidgetBlueprint.dataRefs.widget`:
```tsx title="in backstage-plugin-foo"
import {
ApiBlueprint,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import {
FooWidgetBlueprint,
fooWidgetsApiRef,
} from 'backstage-plugin-foo-react';
export const FooWidgetsApiExtension = ApiBlueprint.makeWithOverrides({
name: 'widgets',
inputs: {
widgets: createExtensionInput([FooWidgetBlueprint.dataRefs.widget]),
},
factory(originalFactory, { inputs }) {
// Collect all widgets from the inputs and forward them to the API implementation
const widgets = inputs.widgets.map(w =>
w.get(FooWidgetBlueprint.dataRefs.widget),
);
return originalFactory(defineParams =>
defineParams({
api: fooWidgetsApiRef,
deps: {},
factory: () => ({
getWidgets: () => widgets,
}),
}),
);
},
});
```
Other plugins can now import the blueprint from your `-react` package and create widget extensions that will be collected by the API:
```tsx title="in a consuming plugin"
import { FooWidgetBlueprint } from 'backstage-plugin-foo-react';
const barWidgetExtension = FooWidgetBlueprint.make({
name: 'bar',
params: {
title: 'Bar Widget',
size: 'small',
loader: () => import('./components/BarWidget').then(m => m.BarWidget),
},
});
const bazWidgetExtension = FooWidgetBlueprint.make({
name: 'baz',
params: {
title: 'Baz Widget',
size: 'medium',
loader: () => import('./components/BazWidget').then(m => m.BazWidget),
},
});
```
### 4. Consume the Extensions via the API
You can now consume the widgets using any of the available methods for consuming Utility APIs. For example, this is how you would access the widgets in a component:
```tsx title="in backstage-plugin-foo"
import { useApi } from '@backstage/frontend-plugin-api';
import { fooWidgetsApiRef } from 'backstage-plugin-foo-react';
import { Suspense, lazy } from 'react';
export function FooPageContent() {
const widgetsApi = useApi(fooWidgetsApiRef);
const widgets = widgetsApi.getWidgets();
return; // load and render widgets ...
}
```
For more information on consuming Utility APIs, see the [Consuming Utility APIs](../utility-apis/03-consuming.md) page.
@@ -103,7 +103,7 @@ You can also pass overrides to the features array, for more details, please read
### Using an async features loader
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-defaults.createappfeatureloader/) object and pass it to the `features` option:
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.index.createFrontendFeatureLoader.html) object and pass it to the `features` option:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-defaults';
@@ -18,7 +18,7 @@ extensions:
```
:::warning
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
## Override built-in extensions
@@ -107,7 +107,7 @@ This is the extension that creates the app root element, so it renders root leve
##### Alert Display
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/docs/reference/core-plugin-api.alertapi).
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html).
| kind | namespace | name | id |
| :--------------: | :-------: | :-----------: | :----------------------------------: |
@@ -125,12 +125,12 @@ An app root element extension that displays messages posted via the [`AlertApi`]
If you do not want to display alerts, disable this extension or if the available settings do not meet your needs, override this extension.
:::warning
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.AlertApi.html) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
##### OAuth Request Dialog
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/docs/reference/core-plugin-api.oauthrequestapi/).
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/api/stable/types/_backstage_core-plugin-api.index.OAuthRequestApi.html).
| kind | namespace | name | id |
| :--------------: | :-------: | :------------------: | :-----------------------------------------: |
@@ -146,10 +146,10 @@ Renders the app's sidebar and content in a specific layout.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Nav` extension. |
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Routes` extension. |
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.coreExtensionData.html) | false | - | Override the `App/Nav` extension. |
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.index.coreExtensionData.html) | false | - | Override the `App/Routes` extension. |
### App nav
@@ -161,10 +161,10 @@ Extension responsible for rendering the logo and items in the app's sidebar.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- |
| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | true | - | [NavContentBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) |
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) | true | - | [NavContentBlueprint](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html) |
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
### App routes
@@ -0,0 +1,166 @@
---
id: module-federation
title: Module Federation
sidebar_label: Module Federation
description: Using Module Federation in Backstage
---
## Introduction
Module Federation is a feature that enables sharing code and dependencies between separately built JavaScript applications at runtime. In Backstage, module federation support allows you to:
- Build your frontend application as a **module federation host** that can load remote modules at runtime
- Package individual plugins or bundles of several plugins as **module federation remotes** that can be loaded dynamically
- Share dependencies efficiently between the host and remotes to avoid code duplication
This guide explains how to configure and build both module federation hosts and remotes in Backstage, and how to initialize module federation at runtime using the standard Module Federation Runtime API.
## Overview
### Module Federation Host vs Remotes
In module federation terminology:
- **Host**: The main frontend application that loads and consumes remote modules. In Backstage, this is your app package (typically `packages/app`).
- **Remote**: A separately built module that can be loaded by the host at runtime. In Backstage, these are typically plugin packages built as module federation remotes.
### Shared Dependencies
A critical aspect of module federation is **shared dependencies**. When a host loads remote modules, both need to share common dependencies (like React, React Router, Material-UI) in order to ensure singleton dependencies only have one instance.
Backstage provides a list of default shared dependencies for common packages like React, React Router, and Material-UI. At build-time the `version` field is automatically resolved from your `package.json` files.
## Building the Module Federation Host
The module federation host is your main frontend application. By default, Backstage frontend applications include a default list of module federation shared dependencies.
When building and bundling the frontend application, the CLI automatically:
1. Resolves versions of the shared dependencies based on the monorepo dependencies
2. Adds an additional entrypoint to the frontend application bundle with the list of resolved runtime shared dependencies
## Building Module Federation Remotes
Plugin packages can be built as module federation remotes, allowing them to be loaded dynamically by a host application.
### Using the CLI
To build a plugin as a module federation remote, use the `--module-federation` option with the `package build` command:
```bash
cd plugins/my-plugin
yarn build --module-federation
```
### Build Output
When building a plugin as a module federation remote, the CLI:
1. Resolves versions of the shared dependencies based on the monorepo dependencies (done automatically by the Rspack/Webpack module federation plugin)
2. Produces the bundle assets in the `dist` folder, including:
- a `mf-manifest.json` file which contains the module federation manifest
- a `remoteEntry.js` file which is the main entrypoint for the remote module
## Runtime Usage
To use module federation in your Backstage app, you need to initialize the Module Federation Runtime with the shared dependencies configuration.
### Basic Usage
Here's how to initialize module federation in your app, and load remote modules:
```typescript title="packages/app/src/moduleFederation.ts"
import {
createInstance,
ModuleFederation,
} from '@module-federation/enhanced/runtime';
import { loadModuleFederationHostShared } from '@backstage/module-federation-common';
export async function initializeModuleFederation(): Promise<ModuleFederation> {
return createInstance({
name: 'app',
remotes: [
{
name: 'my_plugin',
entry: 'http://localhost:3001/mf-manifest.json',
},
],
shared: await loadModuleFederationHostShared(),
});
}
export async function loadRemote(
instance: ModuleFederation,
name: string,
): Promise<any> {
return await instance.loadRemote<any>(name);
}
```
The `loadModuleFederationHostShared` function loads all shared dependencies in parallel and returns them in the format expected by the Module Federation Runtime. By default it will throw if any shared dependency fails to load. You can pass an `onError` callback to handle errors gracefully instead:
```typescript
const shared = await loadModuleFederationHostShared({
onError: error => console.error(error.message, error.cause),
});
```
### Integration with Feature Loaders
Standard Module Federation runtime API integrates very well with frontend feature loaders,
as shown in the example below:
```typescript title="packages/app/src/loader.tsx"
import { createInstance } from '@module-federation/enhanced/runtime';
import { loadModuleFederationHostShared } from '@backstage/module-federation-common';
import { createFrontendFeatureLoader } from '@backstage/frontend-plugin-api';
export const moduleFederationLoader = createFrontendFeatureLoader({
async loader() {
const moduleFederationInstance = createInstance({
name: 'app',
remotes: [],
shared: await loadModuleFederationHostShared(),
});
moduleFederationInstance.registerRemotes([
{
name: 'myFirstRemoteWith2ExposedModules',
entry:
'https://someCDN.org/myFirstRemoteWith2ExposedModules/mf-manifest.json',
},
{
name: 'mySecondRemote',
entry: 'https://someCDN.org/mySecondRemote/mf-manifest.json',
},
]);
const myFirstRemoteModule1 = await moduleFederationInstance.loadRemote<any>(
'myFirstRemoteWith2ExposedModules/module1',
);
const myFirstRemoteModule2 = await moduleFederationInstance.loadRemote<any>(
'myFirstRemoteWith2ExposedModules/module2',
);
const mySecondRemoteModule = await moduleFederationInstance.loadRemote<any>(
'mySecondRemote',
);
return [
myFirstRemoteModule1.default,
myFirstRemoteModule2.default,
mySecondRemoteModule.default,
];
},
});
const app = createApp({
features: [moduleFederationLoader],
});
export default app.createRoot();
```
Note that, on top of the standard API, we plan to provide a more simplified way to configure module federation remotes.
Additionally, the [`dynamicFrontendFeaturesLoader`](https://github.com/backstage/backstage/blob/master/packages/frontend-dynamic-feature-loader/src/loader.ts) provided in the [`@backstage/frontend-dynamic-feature-loader`](https://github.com/backstage/backstage/blob/master/packages/frontend-dynamic-feature-loader/README.md) package, which provides an integrated solution to load module federation remotes as dynamic frontend plugins, is a more complete example of a feature loader based on the module federation support.
## Default Shared Dependencies
Default shared dependencies are the same for both the host and remotes, and the list can be found in the [`@backstage/module-federation-common`](https://github.com/backstage/backstage/blob/master/packages/module-federation-common/src/defaults.ts) package.
@@ -686,7 +686,7 @@ createApp({
#### App Root Sidebar
New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items which are the other `NavItem` extensions provided by the system.
New apps feature a built-in sidebar extension which is created by using the `NavContentBlueprint` in `src/modules/nav/Sidebar.tsx`. The default implementation of the sidebar in this blueprint will render some items explicitly in different groups, and then render the rest of the items. Nav items are auto-discovered from page extensions registered under `app/routes` (no explicit `NavItemBlueprint` required), with metadata from page config, nav item extensions, or plugin defaults.
In order to migrate your existing sidebar, you will want to create an override for the `app/nav` extension. You can do this by copying the standard of having a `src/modules/nav/` folder, which can contain an extension which you can install into the `app` in the form of a `module`.
@@ -702,38 +702,45 @@ export const navModule = createFrontendModule({
Then in the actual implementation for the `SidebarContent` extension, you can provide something like the following, where you implement the entire `Sidebar` component.
The component receives a `navItems` prop with `take(id)` and `rest()` methods for placing specific items in custom positions. The recommended approach is to use `navItems.withComponent(...)` to define a component for rendering each nav item, and then use the returned `take(id)` and `rest()` methods to get pre-rendered elements directly. Items taken from the renderer are also taken from the main list. Keys are automatically assigned when rendering via `rest()`.
```tsx title="in packages/app/src/modules/nav/Sidebar.tsx"
import { NavContentBlueprint } from '@backstage/plugin-app-react';
export const SidebarContent = NavContentBlueprint.make({
params: {
component: ({ items }) => (
<Sidebar>
<SidebarLogo />
<SidebarGroup label="Search" icon={<SearchIcon />} to="/search">
<SidebarSearchModal />
</SidebarGroup>
<SidebarDivider />
<SidebarGroup label="Menu" icon={<MenuIcon />}>
...
</SidebarGroup>
<SidebarGroup label="Plugins">
<SidebarScrollWrapper>
{/* Items in this group will be scrollable if they run out of space */}
{items.map((item, index) => (
<SidebarItem {...item} key={index} />
))}
</SidebarScrollWrapper>
</SidebarGroup>
</Sidebar>
),
component: ({ navItems }) => {
const nav = navItems.withComponent(item => (
<SidebarItem icon={() => item.icon} to={item.href} text={item.title} />
));
return (
<Sidebar>
<SidebarLogo />
<SidebarGroup label="Search" icon={<SearchIcon />} to="/search">
<SidebarSearchModal />
</SidebarGroup>
<SidebarDivider />
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{nav.take('page:catalog')}
{nav.take('page:scaffolder')}
<SidebarDivider />
<SidebarScrollWrapper>
{nav.rest({ sortBy: 'title' })}
</SidebarScrollWrapper>
</SidebarGroup>
</Sidebar>
);
},
},
});
```
The `items` property is a list of all extensions provided by the `NavItemBlueprint` that are currently installed in the App. If you don't want to auto populate this list you can simply remove the rendering of that `SidebarGroup`, but otherwise you can see from the above example how a `SidebarItem` element is rendered for each of the items in the list.
The deprecated `items` prop (a flat list compatible with `<SidebarItem {...item} />`) remains supported for backward compatibility. If you don't want to auto-populate the list, simply remove the rendering of that `SidebarGroup`.
You might also notice that when you're rendering additional fixed icons for plugins that these might become duplicated as the plugin provides a `NavItem` extension and you're also rendering one in the `Sidebar` manually. In order to remove the item from the list of `items` which is passed through, we recommend that you disable that extension using config:
You might also notice that when you're rendering additional fixed icons for plugins (e.g. Search in a dedicated group) these might become duplicated, since that page is also included in `nav.rest()`. To exclude an item from the remaining list, call `nav.take('page:search')` before calling `nav.rest()` — you can discard the return value. Items that have been taken will not appear in `rest()`.
You can also use the old `NavItemBlueprint`-based nav item extensions to disable items from the nav bar, these can be disabled in config without affecting the page itself:
```yaml title="in app-config.yaml"
app:
@@ -742,15 +749,6 @@ app:
- nav-item:catalog: false
```
You can also determine the order of the provided auto installed `NavItems` that you get from the system in config. The below example ensures that the `catalog` navigation item will proceed the `search` navigation item when being passed through as the `item` prop.
```yaml title="in app-config.yaml"
app:
extensions:
- nav-item:catalog
- nav-item:search
```
#### App Root Routes
Your top-level routes are the routes directly under the `AppRouter` component with the `<FlatRoutes>` element. In a small app they might look something like this:
@@ -31,50 +31,50 @@ describe('Entity details component', () => {
});
```
To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API:
To mock [Utility APIs](../architecture/33-utility-apis.md) that are used by your component, pass API overrides to `renderInTestApp` using the `apis` option. Mock helpers are available from `@backstage/frontend-test-utils` and plugin-specific test utilities. For a deeper look at the available mock APIs and how to create your own, see [Testing with Utility APIs](../utility-apis/05-testing.md).
```tsx
import { screen } from '@testing-library/react';
import {
renderInTestApp,
TestApiProvider,
} from '@backstage/frontend-test-utils';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { EntityDetails } from './plugin';
import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';
import { identityApiRef } from '@backstage/frontend-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
import { MyEntitiesList } from './plugin';
describe('Entity details component', () => {
it('should render the entity name and owner', async () => {
const catalogApiMock = {
async getEntityFacets() {
return {
facets: {
'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }],
},
},
}
} satisfies Partial<typeof catalogApiRef.T>;
const entityRef = stringifyEntityRef({
kind: 'Component',
namespace: 'default',
name: 'test',
describe('MyEntitiesList', () => {
it('should render entities owned by the current user', async () => {
await renderInTestApp(<MyEntitiesList />, {
apis: [
[
identityApiRef,
mockApis.identity({ userEntityRef: 'user:default/guest' }),
],
[
catalogApiRef,
catalogApiMock({
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: { name: 'my-component' },
spec: { type: 'service', owner: 'user:default/guest' },
},
],
}),
],
],
});
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApiMock]]}>
<EntityDetails entityRef={entityRef} />
</TestApiProvider>,
);
await expect(
screen.findByText('The entity "test" is owned by "tools"'),
screen.findByText('my-component'),
).resolves.toBeInTheDocument();
});
});
```
This pattern also works for many other context providers. An important example is the `EntityProvider` from the `@backstage/plugin-catalog-react` package, which you can use to provide a mocked entity context to the component.
This approach provides the API overrides at the app level, which is useful when testing components that depend on APIs deep in the component tree.
The `TestApiProvider` component is also available for standalone rendering scenarios where you're not using `renderInTestApp` or other test utilities. Context providers like `EntityProvider` from `@backstage/plugin-catalog-react` can also be used to provide a mocked entity context to the component.
## Testing extensions
@@ -102,7 +102,35 @@ describe('Index page', () => {
});
```
This pattern also allows you to wrap the extension with context providers, such as the `TestApiProvider` that was introduced [above](#testing-react-components).
You can also provide API overrides directly to `createExtensionTester` using the `apis` option:
```tsx
import { screen } from '@testing-library/react';
import {
createExtensionTester,
mockApis,
renderInTestApp,
} from '@backstage/frontend-test-utils';
import { identityApiRef } from '@backstage/frontend-plugin-api';
import { indexPageExtension } from './plugin';
describe('Index page', () => {
it('should render with a custom identity', async () => {
await renderInTestApp(
createExtensionTester(indexPageExtension, {
apis: [
[
identityApiRef,
mockApis.identity({ userEntityRef: 'user:default/guest' }),
],
],
}).reactElement(),
);
expect(screen.getByText('Index Page')).toBeInTheDocument();
});
});
```
Note that the `.reactElement()` method will look for the `coreExtensionData.reactElement` data in the extension outputs. If that doesn't exist and the extension outputs something else that you want to test, you can access the output data using the `.get(dataRef)` method instead.
@@ -172,7 +200,82 @@ describe('Index page', () => {
});
```
That's all for testing features!
## Testing entity extensions
The `createTestEntityPage` utility from `@backstage/plugin-catalog-react/testUtils` simplifies testing entity cards and content extensions. It creates a test page that mounts at `/`, provides an `EntityProvider` context, and picks up entity extensions through input redirects.
```tsx
import { screen } from '@testing-library/react';
import { renderTestApp } from '@backstage/frontend-test-utils';
import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';
import { myEntityCard } from './plugin';
describe('MyEntityCard', () => {
it('should render for Component entities', async () => {
const entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: { name: 'my-service' },
spec: { type: 'service', owner: 'team-a' },
};
renderTestApp({
extensions: [createTestEntityPage({ entity }), myEntityCard],
});
expect(await screen.findByText('My Card Title')).toBeInTheDocument();
});
});
```
Entity content extensions can be tested the exact same way, just pass your content extension instead of a card. The test page also supports entity filters defined on the extensions, so you can test filter behavior by providing different entity kinds. If your extension depends on APIs you can pass mock implementation using the `apis` option `renderTestApp`, or you can pass the API extension directly alongside your content extension.
Extensions that use `EntityRefLinks` or `useRelatedEntities` may require additional API mocking using the `apis` option on `renderTestApp`.
## Mounting routes
If your component or extension uses `useRouteRef` to generate links to other routes, you need to mount those routes in the test environment. Both `renderInTestApp` and `renderTestApp` support the `mountedRoutes` option for this purpose.
For example, given a component that uses `useRouteRef` to create a link:
```tsx
import { useRouteRef } from '@backstage/frontend-plugin-api';
import { detailsRouteRef } from './routes';
export const MyComponent = () => {
const detailsLink = useRouteRef(detailsRouteRef);
return <a href={detailsLink()}>View details</a>;
};
```
You can test it by mounting the route ref to a path using the `mountedRoutes` option:
```tsx
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/frontend-test-utils';
import { detailsRouteRef } from './routes';
import { MyComponent } from './MyComponent';
describe('MyComponent', () => {
it('should render a link to the plugin page', async () => {
await renderInTestApp(<MyComponent />, {
mountedRoutes: {
'/my-plugin/details': detailsRouteRef,
},
});
expect(await screen.findByText('View details')).toHaveAttribute(
'href',
'/my-plugin/details',
);
});
});
```
## Extension tree snapshots
The `snapshot()` method on `ExtensionTester` returns a tree-shaped representation of the resolved extension hierarchy, which is convenient to use with Jest's `toMatchInlineSnapshot()` for verifying extension structure in tests.
## Missing something?
@@ -15,13 +15,23 @@ These are the [extension blueprints](../architecture/23-extension-blueprints.md)
An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override.
### NavItem - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavItemBlueprint.html)
### NavItem (deprecated) - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavItemBlueprint.html)
Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app.
The `NavItemBlueprint` is deprecated. The app now auto-discovers navigation items from page extensions, so explicit nav item extensions are no longer needed. To migrate, ensure your plugin and/or page extensions have a `title` and `icon` set — these are used to populate the sidebar automatically.
### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PageBlueprint.html)
Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes.
Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. Pages automatically inherit the plugin's `title` and `icon` as defaults, which can be overridden per-page via `PageBlueprint` params.
To enable sub-pages on a page, you can either omit the `loader` param to use the built-in default implementation that renders sub-pages as tabs, or provide a custom `loader` that explicitly handles the sub-page inputs.
### SubPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SubPageBlueprint.html)
Sub-page extensions create tabbed content within a parent page. They are attached to a page extension's `pages` input and rendered as tabs in the page header. Each sub-page has a `path` (relative to the parent page), a `title` for the tab, and an optional `icon`. Content is lazy-loaded via a `loader` function.
### PluginHeaderAction - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PluginHeaderActionBlueprint.html)
Plugin header action extensions provide plugin-scoped actions that appear in the page header. They are automatically scoped to the plugin that provides them and will appear in the header of all pages belonging to that plugin. Actions are lazy-loaded via a `loader` function that returns a React element.
## Extension blueprints in `@backstage/frontend-plugin-api/alpha`
@@ -51,10 +61,12 @@ Icon bundle extensions provide the ability to replace or provide new icons to th
Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages.
### NavContent - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavContentBlueprint.html)
### NavContent - [Reference](https://backstage.io/api/stable/variables/_backstage_plugin-app-react.NavContentBlueprint.html)
Nav content extensions allow you to replace the entire navbar with your own component. They are always attached to the app nav extension.
Your custom component receives a `navItems` prop—a collection with `take(id)` and `rest()` methods for placing specific items in custom positions. Nav items are auto-discovered from page extensions, and metadata (title, icon) comes from page config, nav item extensions, or plugin defaults. Use `navItems.take('page:home')` to take a specific item by extension ID, and `navItems.rest()` to get all remaining items. The deprecated `items` prop (a flat list) remains supported for backward compatibility.
### Router - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.RouterBlueprint.html)
Router extensions allow you to replace the router component used by the app. They are always attached to the app root extension.
@@ -73,6 +85,31 @@ Avoid using `convertLegacyEntityCardExtension` from `@backstage/core-compat-api`
Creates entity content to be displayed on the entity pages of the catalog plugin. Exported as `EntityContentBlueprint`.
Supports optional params such as `group` and `icon` to:
- group: string | false — associates the content with a tab group on the entity page (for example "overview", "quality", "deployment", or any custom id). You can override or disable this per-installation via app-config using `app.extensions[...].config.group`, where `false` removes the grouping.
- icon: string — sets the tab icon. Note: when providing a string, the icon is looked up via the app's IconsApi; make sure icon bundles are enabled/installed in your app (see the Icons blueprint reference above) so that the icon id you use is available.
To render icons in the entity page tabs, the page must also have icons enabled via app configuration. Set `showNavItemIcons: true` on the catalog entity page config (created via `page:catalog/entity`). Example:
```yaml
app:
extensions:
# Entity page
- page:catalog/entity:
config:
# Enable tab- and group-icons
showNavItemIcons: true
# Optionally override default groups and their icons
groups:
- overview:
title: Overview
icon: dashboard
- documentation:
title: Docs
icon: description
```
Avoid using `convertLegacyEntityContentExtension` from `@backstage/core-compat-api` to convert legacy entity content extensions to the new system. Instead, use the `EntityContentBlueprint` directly. The legacy converter is only intended to help adapt 3rd party plugins that you don't control, and doesn't produce as good results as using the blueprint directly.
## Extension blueprints in `@backstage/plugin-search-react/alpha`
@@ -42,6 +42,14 @@ const examplePage = createExtension({
The `title` data reference can be used for defining the extension input/output of string titles.
### `icon`
| id | type |
| :---------: | :-----------: |
| `core.icon` | `IconElement` |
The `icon` data reference can be used for defining the extension input/output of icon elements. The type is `IconElement` (`JSX.Element | null`) from `@backstage/frontend-plugin-api`. Icons should be exactly 24x24 pixels in size.
### `routePath`
| id | type |
+1 -1
View File
@@ -9,4 +9,4 @@ description: The Frontend System
We recommend migrating your frontend plugins to the new frontend system. If you do please do so under an `/alpha` sub-path export.
You can find an example app setup in the [`app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next).
You can find an example app setup in the [`app` package](https://github.com/backstage/backstage/tree/master/packages/app).
@@ -35,6 +35,14 @@ Most utility APIs are usable directly without any configuration. But they are pr
These cases are all described in [the main article](./04-configuring.md).
## Testing with utility APIs
> For details, [see the main article](./05-testing.md).
When testing frontend components and extensions, you often need to provide mock implementations of the utility APIs they depend on. The `@backstage/frontend-test-utils` package provides the `mockApis` namespace with ready-made mocks for all core utility APIs, which can be passed to test utilities like `renderInTestApp` and `TestApiProvider`.
These are described in detail in [the main article](./05-testing.md).
## Migrating from the old frontend system
If you want to learn how to migrate your own utility APIs from the old frontend system to the new one, that's described in the [Migrating APIs guide](../building-plugins/05-migrating.md#migrating-apis).
@@ -0,0 +1,194 @@
---
id: testing
title: Testing with Utility APIs
sidebar_label: Testing
description: Mocking and testing Utility APIs
---
When testing frontend components and extensions, you often need to provide mock implementations of the utility APIs they depend on. The `@backstage/frontend-test-utils` package provides the `mockApis` namespace with ready-made mocks for all core utility APIs.
## The `mockApis` namespace
The `mockApis` namespace is the main entry point for creating mock utility API instances in tests. It provides two usage patterns for each API:
### Fake instances
Call the API function directly to create a fake instance with simplified but functional behavior. These are useful when your test needs the API to actually work, not just be stubbed.
```ts
import { mockApis } from '@backstage/frontend-test-utils';
const configApi = mockApis.config({ data: { app: { title: 'Test' } } });
configApi.getString('app.title'); // 'Test'
const alertApi = mockApis.alert();
alertApi.post({ message: 'hello' });
expect(alertApi.getAlerts()).toHaveLength(1);
```
### Jest mocks
Call `.mock()` to get an instance where every method is a `jest.fn()`. You can optionally provide partial implementations. This is useful when you want to assert that specific methods were called.
```ts
import { mockApis } from '@backstage/frontend-test-utils';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
const permissionApi = mockApis.permission.mock({
authorize: async () => ({ result: AuthorizeResult.ALLOW }),
});
// ... exercise the component ...
expect(permissionApi.authorize).toHaveBeenCalledTimes(1);
```
## Providing mock APIs in tests
### With `renderInTestApp`
```tsx
import { screen } from '@testing-library/react';
import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';
await renderInTestApp(<MyComponent />, {
apis: [
mockApis.identity({ userEntityRef: 'user:default/guest' }),
mockApis.config({ data: { app: { title: 'Test App' } } }),
],
});
```
You can also use the `[apiRef, implementation]` tuple syntax to provide any API implementation, including ones that aren't from `mockApis`:
```tsx
import { myCustomApiRef } from '../apis';
const myCustomApiInstance = {
// ...
};
await renderInTestApp(<MyComponent />, {
apis: [
mockApis.identity({ userEntityRef: 'user:default/guest' }),
[myCustomApiRef, myCustomApiInstance],
],
});
```
### With `renderTestApp`
The same `apis` option is available on `renderTestApp`, which is commonly used when testing extensions or entity pages:
```tsx
import { renderTestApp, mockApis } from '@backstage/frontend-test-utils';
import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';
renderTestApp({
extensions: [createTestEntityPage({ entity }), myEntityCard],
apis: [mockApis.permission()],
});
```
### With `TestApiProvider`
For standalone rendering scenarios where you're not using `renderInTestApp`, the `TestApiProvider` component accepts the same `apis` format:
```tsx
import { render } from '@testing-library/react';
import { TestApiProvider, mockApis } from '@backstage/frontend-test-utils';
render(
<TestApiProvider
apis={[
mockApis.identity({ userEntityRef: 'user:default/guest' }),
mockApis.alert(),
]}
>
<MyComponent />
</TestApiProvider>,
);
```
## Plugin-specific test mocks
Plugins can provide their own mock APIs that follow the same pattern. For example, `@backstage/plugin-catalog-react` provides `catalogApiMock` in its `/testUtils` entry point:
```tsx
import { renderTestApp } from '@backstage/frontend-test-utils';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
renderTestApp({
extensions: [myExtension],
apis: [catalogApiMock({ entities: [entity] })],
});
```
### Creating your own mock APIs
If you maintain a plugin that exposes a utility API, you can provide mock utilities that follow the same function + `.mock()` pattern as the built-in `mockApis`.
Use `attachMockApiFactory` for fake instances with real behavior, and `createApiMock` for the jest-mocked `.mock()` variant where all methods are `jest.fn()`. The full pattern looks like this:
```ts
import {
attachMockApiFactory,
createApiMock,
type ApiMock,
} from '@backstage/frontend-test-utils';
import { myApiRef, type MyApi } from '@internal/plugin-example-react';
// Fake instance with real behavior
export function myApiMock(options?: { greeting?: string }) {
return attachMockApiFactory(myApiRef, {
greet: async () => options?.greeting ?? 'Hello!',
});
}
// Jest mock variant where all methods are jest.fn()
export namespace myApiMock {
export const mock = createApiMock(myApiRef, () => ({
greet: jest.fn(),
}));
}
```
Consumers can then use it just like the core mocks:
```tsx
// Fake with real behavior
await renderInTestApp(<MyComponent />, {
apis: [myApiMock({ greeting: 'Hi there!' })],
});
// Jest mock for assertions
const api = myApiMock.mock({
greet: async () => 'mocked',
});
await renderInTestApp(<MyComponent />, {
apis: [api],
});
expect(api.greet).toHaveBeenCalledTimes(1);
```
## Available mock APIs
The table below lists all core APIs available through the `mockApis` namespace.
| API | Fake instance | Notes |
| --------------------------------- | --------------------- | ---------------------------------------------------------------------- |
| `mockApis.alert()` | `MockAlertApi` | Collects alerts; has `getAlerts()`, `clearAlerts()`, `waitForAlert()` |
| `mockApis.analytics()` | `MockAnalyticsApi` | Collects events; has `getEvents()` |
| `mockApis.config({ data })` | `MockConfigApi` | Reads from a plain JSON object |
| `mockApis.discovery({ baseUrl })` | Inline | Returns `${baseUrl}/api/${pluginId}`, defaults to `http://example.com` |
| `mockApis.error(options?)` | `MockErrorApi` | Collects errors; has `getErrors()`, `waitForError()` |
| `mockApis.featureFlags(options?)` | `MockFeatureFlagsApi` | In-memory flag state; has `getState()`, `setState()`, `clearState()` |
| `mockApis.fetch(options?)` | `MockFetchApi` | Wraps native `fetch`; supports identity injection and plugin protocol |
| `mockApis.identity(options?)` | Inline | Configurable user ref, ownership, token, profile |
| `mockApis.permission(options?)` | `MockPermissionApi` | Defaults to `ALLOW`; accepts a handler function |
| `mockApis.storage({ data })` | `MockStorageApi` | In-memory storage with bucket support |
| `mockApis.translation()` | `MockTranslationApi` | Passthrough returning default messages from translation refs |
Each of these also has a `.mock()` variant that returns jest mocks, as described above.
+86 -6
View File
@@ -106,6 +106,88 @@ When filling these out, you have 2 choices,
If you opt for the second option of replacing the entire string, take care to not commit your `app-config.yaml` to source control. It may contain passwords that you don't want leaked.
## Passwordless PostgreSQL in the Cloud
If you want to host your PostgreSQL server in the cloud with passwordless authentication, you can use Azure Database for PostgreSQL with Microsoft Entra authentication or Google Cloud SQL for PostgreSQL with Cloud IAM.
### Azure with Entra authentication
Remove `password` from the connection configuration and set `type` to `azure`.
Optionally set `tokenCredential` with the following properties. If no credential information is provided, it will default to using Default Azure Credential and a tokenRenewalOffsetTime of 5 minutes.
#### Credential Selection
The credential type is automatically inferred based on the fields you provide:
- Client Secret Credential is used when all three are provided:
- `tenantId`
- `clientId`
- `clientSecret`
- Managed Identity Credential is used when only `clientId` is provided. This enables user-assigned managed identity.
- Default Azure Credential is used when no credential fields are provided. Default Azure Credential supports [many credential types](https://learn.microsoft.com/azure/developer/javascript/sdk/authentication/credential-chains#use-defaultazurecredential-for-flexibility), choosing one based on the runtime environment.
#### Token Renewal
Set `tokenRenewalOffsetTime` to control how early OAuth tokens should be refreshed.
The value may be:
- A human-readable string such as '1d', '2 hours', '30 seconds'
- A duration object, e.g. { minutes: 3, seconds: 30 }
Azure PostgreSQL uses short-lived Entra ID access tokens.
By default, the database connector refreshes tokens 5 minutes before they expire.
#### User Configuration
Set `user` to the display name of your Entra ID group, service principal, or managed identity. Set it to the user principal name if you're authenticating with a user's credentials.
#### Example
```yaml title="app-config.yaml"
backend:
database:
client: pg
connection:
# highlight-add-start
type: azure
tokenCredential:
tokenRenewalOffsetTime: 5 minutes
# highlight-add-end
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
# highlight-remove-start
password: ${POSTGRES_PASSWORD}
# highlight-remove-end
```
### Google with Cloud IAM
Remove `password` from the connection configuration and set `type` to `cloudsql`.
Under the hood, this implements [Automatic IAM Database Authentication](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector?tab=readme-ov-file#automatic-iam-database-authentication).
For an IAM user account, set `user` to the user's email address. For a service account, set `user` to the service account's email without the .gserviceaccount.com domain suffix.
```yaml title="app-config.yaml"
backend:
database:
client: pg
connection:
# highlight-add-start
type: cloudsql
instance: my-project:region:my-instance
# highlight-add-end
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
# highlight-remove-start
password: ${POSTGRES_PASSWORD}
# highlight-remove-end
```
:::
[Start the Backstage app](../index.md#2-run-the-backstage-app):
@@ -126,16 +208,16 @@ You may not want to install Postgres locally, the following sections outline alt
You can run Postgres in a Docker container, this is great for local development or getting a Backstage POC up and running quickly, here's how:
First we need to pull down the container image, we'll use Postgres 17, check out the [Postgres Version Policy](../../overview/versioning-policy.md#postgresql-releases) to learn which versions are supported.
First we need to pull down the container image, we'll use Postgres 18, check out the [Postgres Version Policy](../../overview/versioning-policy.md#postgresql-releases) to learn which versions are supported.
```shell
docker pull postgres:17.0-trixie
docker pull postgres:18-trixie
```
Then we just need to start up the container.
```shell
docker run -d --name postgres --restart=always -p 5432:5432 -e POSTGRES_PASSWORD=<secret> postgres:17.0-trixie
docker run -d --name postgres --restart=always -p 5432:5432 -e POSTGRES_PASSWORD=<secret> postgres:18-trixie
```
This will run Postgres in the background for you, but remember to start it up again when you reboot your system.
@@ -145,11 +227,9 @@ This will run Postgres in the background for you, but remember to start it up ag
Another way to run Postgres is to use Docker Compose, here's what that would look like:
```yaml title="docker-compose.local.yaml"
version: '4'
services:
postgres:
image: postgres:17.0-trixie
image: postgres:18-trixie
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: <secret>
+78 -2
View File
@@ -24,7 +24,83 @@ Before we begin, make sure
Now, let's get started by installing the home plugin and creating a simple homepage for your Backstage app.
### Setup homepage
## Setup Methods
There are two ways to set up the home plugin, depending on which frontend system your Backstage app uses:
1. **New Frontend System (Recommended)** - For apps using the new plugin system with extensions and blueprints
2. **Legacy Frontend System** - For existing apps using the legacy plugin architecture
### New Frontend System Setup
If your Backstage app uses the [new frontend system](../frontend-system/index.md), follow these steps:
#### 1. Install the plugin
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-home
```
#### 2. Add the plugin to your app configuration
Update your `packages/app/src/app.tsx` to include the home plugin:
```tsx title="packages/app/src/app.tsx"
import homePlugin from '@backstage/plugin-home/alpha';
const app = createApp({
features: [
// ... other plugins
homePlugin,
],
});
```
#### 3. Configure the homepage as your root route
By default, the homepage will be available at `/home`. To make it your app's landing page at `/`, add this configuration to your `app-config.yaml`:
```yaml title="app-config.yaml"
app:
extensions:
- page:home:
config:
path: /
```
The plugin will automatically add a "Home" navigation item to your sidebar and provide a basic homepage layout.
#### 4. Optional: Enable visit tracking
Visit tracking is an optional feature that allows users to see their recently visited and most visited pages on the homepage. This feature is **disabled by default** to give you control over what data is collected and stored.
Visit tracking requires a storage implementation to persist user data:
- **With UserSettings storage** (recommended): If you have the [UserSettings plugin](https://backstage.io/docs/features/software-catalog/external-integrations/#user-settings) configured with persistent storage, visit data will be stored there and synchronized across devices.
- **Fallback to local storage**: If no persistent storage is available, the plugin will automatically fall back to browser local storage, which stores data locally per device.
To enable visit tracking, add this configuration to your `app-config.yaml`:
```yaml title="app-config.yaml"
app:
extensions:
- api:home/visits: true
- app-root-element:home/visit-listener: true
```
#### 5. Customizing your homepage
The New Frontend System provides powerful customization options:
**Custom Homepage Layouts**: Use the `HomePageLayoutBlueprint` from `@backstage/plugin-home-react/alpha` to create custom homepage layouts with your own design and widget arrangements. A layout receives the installed widgets and is responsible for rendering them. If no custom layout is installed, the plugin provides a built-in default.
**Adding Homepage Widgets**: Register custom widgets using the `HomePageWidgetBlueprint` from the `@backstage/plugin-home-react/alpha` package.
For detailed instructions on creating custom layouts, registering widgets, and advanced configuration options, see the [Home plugin documentation](https://github.com/backstage/backstage/tree/master/plugins/home#readme).
### Legacy Frontend System Setup
If your Backstage app uses the legacy frontend system, follow these steps:
#### 1. Install the plugin
@@ -88,7 +164,7 @@ Let's update the route for "Home" in the Backstage sidebar to point to the new h
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| ![Sidebar without Catalog](../assets/getting-started/sidebar-without-catalog.png) | ![Sidebar with Catalog](../assets/getting-started/sidebar-with-catalog.png) |
The code for the Backstage sidebar is most likely inside your [`packages/app/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/Root/Root.tsx).
The code for the Backstage sidebar is most likely inside your [`packages/app-legacy/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/components/Root/Root.tsx).
Let's make the following changes
+1 -1
View File
@@ -81,7 +81,7 @@ This guide also assumes a basic understanding of working on a Linux based operat
- Using `nvm` (recommended)
- [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script)
- [Install and change Node version with nvm](https://nodejs.org/en/download/package-manager/#nvm)
- Node 24 is a good starting point, this can be installed using `nvm install lts/krypton`
- Node 22 or 24 are recommended, these can be installed using `nvm install 22` or `nvm install 24`
- [Binary Download](https://nodejs.org/en/download/)
- [Package manager](https://nodejs.org/en/download/package-manager/)
- [Using NodeSource packages](https://github.com/nodesource/distributions/blob/master/README.md)
@@ -43,6 +43,16 @@ By default the bump command will upgrade `@backstage` packages to the latest `ma
yarn backstage-cli versions:bump --release next
```
You can also use the `--release` option to target a specific version. This is useful if you need to pin your app to a specific release or if you need to downgrade to a previous version (e.g. moving from `1.45.0` back to `1.43.0`).
:::warning
Note that downgrading across significant version gaps (e.g. 2-3 releases) may result in package mismatches or errors due to the way Backstage manages dependencies. This method is best suited for small adjustments.
:::
```bash
yarn backstage-cli versions:bump --release 1.43.0
```
If you are using other plugins you can pass in the `--pattern` option to update
more than just the `@backstage/*` dependencies.
@@ -0,0 +1,72 @@
---
id: getting-started
sidebar_label: 001 - Getting started
title: Getting started with Backstage
---
The adoption journey is a bit different than the other Golden Paths. The goal of this guide is to prepare you for a successful implementation of Backstage in your organization. A technical understanding of Backstage is not needed for this Golden Path, just a desire to help the technical team that will be owning your Backstage instance.
:::info
I'd highly recommend poking around https://demo.backstage.io/ before continuing with this guide. It's a test instance of Backstage that provides a good foundation for what to expect from the tool as a user.
:::
## What is Backstage?
At a high level, Backstage is a framework for building developer portals. When implemented successfully, it can reduce toil for your developers by centralizing information like docs and ownership, reducing cognitive overhead due to tool fragmentation and simplify setting up new codebases or integrating with existing ones.
A few examples,
> My company tracks everything with spreadsheets. We have a list of all Github repos and who owns them, but it's becoming more and more of an issue to keep up to date. Teams aren't proactively updating it when new projects are created and it quickly falls out of date with reorgs and team charter changes.
Backstage can help! We provide a core plugin called Software Catalog that automates this process. Teams are asked to maintain a file in their repo with this ownership information and it gets automatically ingested into Backstage where you can view all projects in a single location.
> My developers have been complaining recently about having to use a growing number of different websites and tools in their day to day. It's getting hard to keep track of all of the tools and for those that we don't use frequently, we lose X minutes trying to remember how to access them.
Tool fragmentation is a real issue and Backstage can also help here! You can create plugins tailored for your company that talk to these external services. These plugins can be standalone or integrated with the Software Catalog for better context. Imagine all of your [CI/CD workflows visible directly](https://backstage.io/plugins/) on the page for your team's projects.
It's important to note that Backstage shouldn't be fully replacing these tools, we don't want to reinvent the wheel. The goal is to have all of the really important information in one place. The tool should still be where teams go to do more advanced or in depth work.
> We have been struggling recently with getting teams to use a standard template for new services. There's no standard set of libraries these services are using or standard infra management. It's increasingly difficult as a platform team to manage everything.
Backstage can help here too! The Scaffolder provides a templating framework that you can plug a Golden Path implementation to. Similar to Github template repos, this can provide a standard base for teams to create based off of.
> Our platform teams have been getting more and more support requests to help debug onboarding steps. We've documented these areas really well and there are plenty of examples in Git, but teams keep running into the same issues. It's always either a bad copy paste or they forget to update a template variable. We've started looking into a custom templating solution for this.
Backstage can help! With the Scaffolder, you can create a template that lets users fill in data through a form and uses that data to create a customized template output. This output is usually in the form of PRs to your various source control systems. Imagine you have a repo for traffic configuration, another for infrastructure management and a third for k8s manifests - with the Scaffolder, you can hide all of this complexity. You may still need to get reviews on the output PRs, but no more copy paste issues!
## What does adopting Backstage look like?
a.k.a "what am I signing myself up for?"
Successfully adopting Backstage usually looks something like this,
1. Setting up a PoC.
2. Getting leadership buy-in.
3. Identify a group of key stakeholders for the project and iterate with them aggressively.
4. Launch to the larger organization.
5. Drive Catalog adoption to 100%.
6. Your Backstage implementation starts to receive plugins from developers outside of your team.
A truly successful Backstage implementation bridges delivering value to customers (developers), demonstrating returns to leadership, and fostering an inner source model. It's a long process but has huge dividends for those that achieve it!
## Getting started
Now that you know what to expect, let's walk through how to get started.
:::note
If you're non-technical, it is highly recommended to find a technical partner for help setting up a proof-of-concept for feedback.
:::
### Software Catalog
Let's go to https://demo.backstage.io/ together. When you first navigate to the page, you will be brought to the Software Catalog page. This is a view of all projects currently registered with the (Demo) Backstage instance. There are a series of filters that you can play around with. If you're _really_ interested, we recommend reading through [the software catalog system model](../../features/software-catalog/system-model.md).
Let's click into a Component, say "artist-lookup". This will bring you to a specialized view for that Component. Across the top, you can see tabs for "CI/CD", "API", "Dependencies", "Docs" and "TODOs". For your company, you can change this as you see fit. The important takeaway is that all of these tabs are automatically filtered for this Component which makes it easy to see how this could start to replace many navigation to other tools.
### Scaffolder
Let's go to https://demo.backstage.io/create now. This is the Scaffolder, a place to store reusable templates. Click the "Choose" button in the "Demo template". This will bring you to a form with some information to input. You don't need to fill this out. The main takeaway here is that this form is generated from YAML and doesn't require a frontend team to implement a custom form for each template you want to create.
@@ -0,0 +1,30 @@
---
id: leadership-buy-in
sidebar_label: 002 - Leadership buy-in
title: Getting leadership buy-in
---
## Summary
In this section, we'll be going over what leadership needs to hear to buy in to your pitch for a developer portal. We expect that you have a good idea of the problem that you want Backstage to solve at your company. If not, we recommend you start small. Look for something that is consistently frustrating developers you work with (this can include you). User interviews are a great way to better understand what needs to improve. It may be IT blocking the creation of new Github repos or databases. It might be 5 hours per week of manual toil that your whole organization has to do. It might be a slow time to production for new services or slow provisioning of test environments. Every company will be different. There is no one size fits all answer we can give you - and if we could, it wouldn't be well-tailored for _your_ leadership team.
## Milestones
Every Backstage adoption journey has well-known milestones.
1. You set up a PoC.
2. You get some users.
3. A group of users _really_ gets the value in the portal and jumps on it. They may even create their own plugins - great!
4. You start to plateau with catalog adoption or daily active users.
5. Leadership starts to get nosy about continued value.
6. You hit a crossroads. Your team either starts to think about building something themselves or going for another off the shelf option or they sit down and do the work to get out of the plateau.
7. If your organization made it this far, you likely now have blocking checks for catalog entries and Backstage is a weekly if not daily portal for your developers - congrats!
Step 4 and 5 are painful moments. Successful Backstage adoptions can lose steam quickly. That's the nature of these things, the excitement will eventually run out and people will go back to their day jobs. Another YAML file or cataloging tool is just overhead and extra toil, regardless of the problem you're solving. Getting leadership on the same page about the value of Backstage is the first step to a very successful adoption story.
### Recommendations
1. Bring something real to your leadership team. This can either be a true proof of concept or [the demo site](https://demo.backstage.io).
2. Define metrics around what you're looking to drive up/down. That may be time to onboarding a new engineer, time to production for a new service, time to mitigate incidents, etc. As we say above, this is the meaty problem that is unique to your company that solving will really move the needle.
3. Lower the barrier to adoption. Many people see yet another YAML file as overhead. If you have an existing cataloging solution, use that to simplify the onboarding process. If you don't, this might be a good opportunity to do that work.
4. Knowledge silos. Every team has preferences on how to do things. Centralizing that data into a single interface while letting teams continue to do things how they want to is a powerful goal and something that Backstage can make happen.
@@ -0,0 +1,13 @@
---
id: setting-up-a-poc
sidebar_label: 003 - Setting up a PoC
title: Setting up a PoC
---
If you're non-technical, this section should be completed by your technical partner.
Follow [our golden path for creating an app](../create-app/index.md). Once you have that set up, we recommend adding a few `catalog-info.yaml` files to a few repos/projects you own and setting up [the GitHub catalog provider](../../integrations/github/discovery.md).
At this stage, you likely want to just get the instance running on your local machine. We'll go over preparing your instance for production at the end of chapter 2.
You may be tempted to update the theme or add that one plugin your organization _needs_, but hold strong. We'll get there in chapter 3.
@@ -0,0 +1,17 @@
---
id: first-stakeholder-feedback
sidebar_label: 004 - Stakeholder Feedback
title: First round of stakeholder feedback
---
Now that you have a PoC running, let's walk through how to get good feedback. You likely aren't the first person to hear about Backstage or maybe not even the first person to set up a PoC. There may be common pitfalls unique to your company that are worth knowing about - political, organizational or otherwise.
## Who to look for?
This depends pretty significantly on your organizational structure. If you have a dedicated platform organization or platform team, start with them. They will either be the technical owners of this application from the go, or will eventually take over ownership. Be kind to them. If you aren't from that organization, we recommend finding your technical partner from somewhere in that organization.
## What to listen for
1. IT slowness. Does your organization run on tickets? Are there specific tasks that feel like they should be automated but aren't?
2. User toil. Your developers are super aware of what's slowing them down, they'll tell you the annoying manual parts of their job that they're hoping you can fix.
3. Data sprawl. What services are your users struggling to remember? What vendors are critical but most users only touch once a month?
@@ -0,0 +1,15 @@
---
id: customize-your-instance
sidebar_label: 005 - Customizing your instance
title: Customizing your instance
---
You now have the knowledge of what your users want and the go from leadership to continue investing in Backstage. Your job now is to customize your instance for your users to really get the value from. Let's dive in!
## Open Source or Build it yourself?
There's a huge community of plugins available for easy installation at https://backstage.io/plugins. We would recommend that you start here for any needs you may be trying to solve. Building a plugin yourself requires significant effort and can be hard to justify early on in your adoption story. If there is a clear gap in the existing offerings for your company, you should build something yourself - otherwise, save yourself the maintenance overhead.
## Customizing the theme
Many organizations are tempted to spend a long time making sure the portal resembles their other offerings. This is important but shouldn't be a months long ordeal. Get it looking close enough and iterate.
@@ -0,0 +1,19 @@
---
id: preparing-for-ga
sidebar_label: 006 - Preparing for GA
title: Preparing for GA
---
We hope at this point that the developers you're working with have read the [golden path on deploying Backstage](../deployment/index.md). Your Backstage instance should be ready for the scale that comes with a full company launch.
## Launch Announcements
<!--TODO-->
## What to expect in the coming months
<!--TODO-->
## How to keep iterating
<!--TODO-->
@@ -0,0 +1,17 @@
---
id: plugin-ownership
sidebar_label: 007 - Plugin Ownership
title: Plugin Ownership
---
You're now well on your way to a healthy Backstage instance! It's been launched to the whole company and you're loving the feedback developers are giving you. Some developers have even started broaching writing their own plugins.
## Inner source
Accepting internal contributions from other teams is a good sign that you are on the road to a developer portal tailored for your developers. This is a well paved path with many upsides, but a few downsides as well. As your Backstage instance grows in size and age, those same developers may be difficult to find. Your team may start to experience more and more struggle updating Backstage.
<!--TODO-->
## Registering Plugins in Your Catalog
<!--TODO-->
@@ -0,0 +1,15 @@
---
id: full-catalog
sidebar_label: 008 - A Full Catalog
title: Ensuring your catalog stays complete
---
Along your Backstage journey (and any workflow migration journey), you will hit a point where incremental adoption is no longer easy. The new developers are no longer flowing into your tool like they once did. More and more projects are not being listed in your catalog. Something has to change.
## Enforcing Catalog Files in CI
<!--TODO-->
## Leadership Initiatives
<!--TODO-->
@@ -19,6 +19,8 @@ Users should already have read through the summary section of the docs, "What is
We recommend you poke around the demo site to get a feel for what Backstage can provide. If you're technical, or working with someone technical, you can run through the steps in `golden-path/create-app` and `golden-path/deploying-backstage` to get something running for just your company.
## Getting leadership buy-in
## First round of stakeholder feedback
If you think Backstage is a good fit for your company, it's likely there are others that do or will think the same. You may have already identified them. For this initial round of feedback, share recommendations for what that group should look like, is there any required number of technical or non-technical members, do you need leadership involved at this point, etc.
@@ -29,9 +31,7 @@ For non-technical users, it's recommended to find a technical partner to help st
At this point, we're assuming you already have an instance created through `golden-path/create-app` and `golden-path/deploying-backstage`. You should now start customizing it to your company's needs. We recommend you start small, write some catalog-info YAML files and start to build a personalized catalog.
## Second round of stakeholder feedback
## Getting leadership buy-in
## Preparing for GA
## Plugin ownership and inner source mentality
+15
View File
@@ -0,0 +1,15 @@
<!-- THIS FILE IS NOT INTENDED TO BE DISPLAYED ON THE DOCSITE -->
## Why build plugins?
This section should clearly explain why you should build a new plugin. The Backstage framework is deeply empowered by plugins and plugins are core to the project's success. Users should walk away from reading this section with a conviction that plugins are the right path for new functionality.
## Sustainable plugin development
Plugins are not developed in a vacuum. Users should reach for them to solve specific business problems facing their developers, for example, you may be tasked to create
- a new vendor integration like PagerDuty,
- a new plugin backend that talks to an internal service,
- etc.
This section should contain learnings from successful Backstage deployments about how to engage with stakeholders, how/when to iterate on your plugin, and setting yourself up for future success.
@@ -1,5 +1,5 @@
---
id: 001-first-steps
id: first-steps
sidebar_label: 001 - Scaffolding the plugin
title: How to scaffold a new plugin?
---
@@ -1,5 +1,5 @@
---
id: 002-poking-around
id: poking-around
sidebar_label: 002 - Poking around
title: 002 - Poking around
---
@@ -16,7 +16,7 @@ To make this plugin production ready, we'll need to adjust a few things,
## Testing locally
Before we jump in to making this plugin ready to ship, let's walk through how to run it locally. If you open your backend plugin's manifest (`plugins/todo-backend/package.json`), and look at the `scripts` section, you'll notice a few important commands. The ones relevant to use right now are
Before we jump in to making this plugin ready to ship, let's walk through how to run it locally. If you open your backend plugin's manifest (`plugins/todo-backend/package.json`), and look at the `scripts` section, you'll notice a few important commands. The ones relevant to us right now are
1. `yarn start` - Starts a local development server using the content in `dev/index.ts` as the backend.
2. `yarn test` - Runs all of the tests for your backend plugin.
@@ -27,8 +27,43 @@ If you run `yarn start`, you should see a custom backend for just your plugin st
2025-06-08T16:14:53.229Z rootHttpRouter info Listening on :7007
```
This indicates that your HTTP server is up and running and we can start sending test HTTP requests. Grab your favorite HTTP client and let's get testing! If you aren't sure what to use, I'd recommend the `humao.rest-client` VSCode extension which can easily be run in VSCode itself with very little extra set up.
This indicates that your HTTP server is up and running and we can start sending test HTTP requests. Grab your favorite HTTP client and let's get testing!
To start, let's make sure we're starting from a clean slate. You can list all existing TODOs by running the command below. You should get back an empty list:
```sh
curl http://localhost:7007/api/todo/todos
```
To create a new TODO, you can send a POST request to the same endpoint. However, you will get a 401 error right now.
```sh
curl -X POST http://localhost:7007/api/todo/todos \
-H 'Content-Type: application/json; charset=utf-8' \
--data-binary @- << EOF
{
"title": "My Todo"
}
EOF
```
The 401 error is because we track the ID of the user that created each TODO. If you send a request to create a TODO but don't provide an `Authorization` header, you will see this failure. For plugins that have a frontend as well, this credential management should happen automatically. Let's try this:
```sh
curl -v -X POST http://localhost:7007/api/todo/todos \
-H 'Content-Type: application/json; charset=utf-8' \
-H "Authorization: Bearer $(curl -s http://localhost:7007/api/auth/guest/refresh | jq -r '.backstageIdentity.token')" \
--data-binary @- << EOF
{
"title": "My Todo"
}
EOF
```
We can then list all TODOs to see our new TODO!
```sh
curl http://localhost:7007/api/todo/todos
```
You'll notice that `createdBy` is `user:development/guest` which is the token we used to create the TODO. That's the `-H "Authorization: Bearer $(curl -s http://localhost:7007/api/auth/guest/refresh | jq -r '.backstageIdentity.token')"` part of the request.
@@ -0,0 +1,19 @@
---
id: persistence
sidebar_label: 003 - Persisting your TODOs
title: 003 - Persisting your TODOs
---
## Saving Plugin State Indefinitely
You may have noticed that your list of TODOs disappears after you restart your Backstage backend. The general flow to restart your backend without having to rerun `yarn start` is to press ENTER on the terminal running `yarn start`. This will force the Backstage backend to restart completely, wiping out any in memory data and starting everything from scratch -- everything except your database.
### Quick intro to SQLite
SQLite is the default database for local development. It runs in memory (and can also run from a file on disk). It supports quick iteration cycles and can be easily deleted if anything goes wrong.
## Adding the `databaseService` to your plugin
<!--TODO-->
## Testing your changes
@@ -0,0 +1,19 @@
---
id: source-tracked
sidebar_label: 004 - Integrating with SCMs
title: 004 - Git-tracked TODOs
---
Problem: You have TODOs in your source code that you want to ingest with your plugin.
## Authenticating
<!--TODO-->
## Querying
<!--TODO-->
## Fetching
<!--TODO-->
@@ -0,0 +1,29 @@
---
id: testing
sidebar_label: 005 - Unit testing your plugin
title: 005 - Testing
---
## Testing is important
We've done a lot of manual testing up to this point of functionality. Let's start putting those assumptions into code that we can run on every change to ensure things are working correctly.
## Router-level testing
<!--TODO-->
## Plugin-level testing
<!--TODO-->
## OpenAPI testing
<!--TODO-->
### Integration with Jest tests
<!--TODO-->
### Fuzzing
<!--TODO-->
+4 -35
View File
@@ -1,36 +1,7 @@
<!-- THIS FILE IS NOT INTENDED TO BE DISPLAYED ON THE DOCSITE -->
# Glossary
- Page: A single `md` file.
- Guide: A number of pages grouped under the same folder.
# Overall Writing Guidelines
The goal of these docs is to provide a comprehensive set of guides that developers + admins can use to quickly get up to speed with plugin development, and then refer to as they're developing their own plugins.
A user that finishes all of these guides will feel comfortable implementing plugins on their own. If additional assistance is required, they should be referred to other sources of information such as Discord, GitHub, source code, or documentation for further support. The user will also understand why/when to build their own plugins, inner-sourcing their developer portal and contributing internal plugins back to the open-source project.
At the same time, not all users will finish the docs or they may come back to them as required. Individual guides should have strong "abstracts" (what will I learn by reading this guide), table of contents, and "next steps" (what do I need to do next) to guide users to read the most important pieces for their work.
When writing guide pages, keep it light! These should be instructional docs, and at the same time conversational and a joy to read. Guides should build on each other, when reading through a progression, the reader should feel more comfortable and confident with concepts as they pop up across progression levels. Guides should be standalone, when finishing one level (for example 101), you should be able to immediately jump into the next (201) without additional research or background. Referencing previous progression levels is ok.
# Sections
## Why build plugins?
This section should answer definitely why you should build a new plugin. The Backstage framework is deeply empowered by plugins and plugins are core to the project's success. Users should walk away from reading this section with a conviction that plugins are the right path for new functionality.
## Sustainable plugin development
Plugins are not developed in a vacuum. Users should reach for them to solve specific business problems facing their developers, for example, you may be tasked to create
- a new vendor integration like PagerDuty,
- a new plugin backend that talks to an internal service,
- etc.
This section should contain learnings from successful Backstage deployments about how to engage with stakeholders, how/when to iterate on your plugin, and setting yourself up for future success.
## Creating your first plugin
This section should be extremely deliberate in showing readers every step of the way to create a plugin using Backstage's best practices. A reader that finishes this section should feel extremely comfortable creating new plugins and how to install and use plugins regardless of their experience with JS/TS and Backstage.
@@ -78,12 +49,6 @@ app.get('/list', async (req, res) => {
});
```
### Testing
Let's write a unit test using `supertest` to make sure that everything is working as expected.
After verifying everything, introduce the problem of persistence - the todos aren't saved across reloads.
### Persistence
Saving values to the database. Writing a migrations file. Plumbing through the database service.
@@ -91,3 +56,7 @@ Saving values to the database. Writing a migrations file. Plumbing through the d
## SCM Integrations
Our users love the new plugin, and now they want it to automatically fetch todos from their source code.
### Testing
Let's write a unit test using `supertest` to make sure that everything is working as expected.
@@ -1,12 +0,0 @@
POST http://localhost:7007/api/todo/todos
Content-Type: application/json
{
"title": "My First TODO"
}
###
GET http://localhost:7007/api/todo/todos
###
@@ -0,0 +1,15 @@
---
id: first-steps
sidebar_label: 001 - Scaffolding the plugin
title: How to scaffold a new plugin?
---
Running `yarn new` -> `frontend-plugin`.
## What did we create?
<!--TODO-->
## Common issues
<!--TODO-->
@@ -0,0 +1,17 @@
---
id: poking-around
sidebar_label: 002 - Poking around
title: 002 - Poking around
---
Our frontend TODO plugin is a bit more simplistic than the backend one. We need to implement a new UI to replace the example components we have.
Let's use this React component to start. Copy this to `plugins/todo/src/components/TodoList.tsx`.
```tsx
// todo
```
### Data Mocking
You already have a backend with dynamic data. Let's start a little smaller. Using hard coded data can be a great way to iterate quickly.
@@ -0,0 +1,29 @@
---
id: dynamic-config
sidebar_label: 003 - Dynamic Config
title: 003 - Dynamic Config
---
Your plugin should have been generated by default for the New Frontend System which is config-first. That means you can easily control your frontend components through your `app-config.yaml`.
Let's try this quickly by disabling our entire TODO page,
```yaml
# TODO
```
We can also do really cool things like provide React props directly through config. Let's try moving our hard coded list of TODOs to config instead,
```tsx
// todo
```
and the config,
```yaml
# TODO
```
### Why does this work?
<!--TODO-->
@@ -0,0 +1,17 @@
---
id: http-client
sidebar_label: 004 - HTTP Client
title: 004 - HTTP Client
---
Now, let's really make our page dynamic. We'll start by writing an HTTP client by hand.
```tsx
class TodoClient {
// TODO
}
```
## OpenAPI Generated Clients
You can also skip a step and ensure your frontend and backend stay in sync by generating the client from an OpenAPI schema.
@@ -0,0 +1,15 @@
---
id: testing
sidebar_label: 005 - Testing
title: 005 - Testing
---
Everyone's favorite part! Let's make sure our components continue to work even when we're not able to validate the changes.
## Unit Tests
Use Jest + RTL + MSW v2.
## Integration Tests
Use Playwright.
@@ -0,0 +1,34 @@
<!-- THIS FILE IS NOT INTENDED TO BE DISPLAYED ON THE DOCSITE -->
# Sections
## Creating your first plugin
### Scaffolding a new plugin
Talk through how to run the `backstage-cli create` command as well as what the output it creates is. This should touch on why we install this into `packages/app`.
### Debugging
How to handle common errors.
## First steps with the new plugin
### Creating a todo plugin
We're going to be creating the frontend for a todo list plugin. We want the user to be able to create todos for themselves and show the user their current list of todos.
To start, we'll use a list of mocked data.
### Controlling your component dynamically
Update the mocked data to be controlled by config.
### HTTP API
We want our todo plugin to reach the backend that we implemented in [the backend plugin Golden Path](../backend/001-first-steps.md). Let's write a client to do this (or use OpenAPI to generate a client for us).
### Testing
Unit tests - Let's write a unit test using React Testing Library to make sure that everything is working as expected.
Integration tests - Let's write an integration test using Playwright to _really_ make sure everything is working.
@@ -0,0 +1,23 @@
---
id: catalog
sidebar_label: 001 - Catalog
title: Integrating with Catalog
---
## Software Catalog
### What is the Software Catalog?
<!--TODO-->
### Integration Points
<!--TODO-->
## Adding a new `backstage.io/todo` annotation
<!--TODO-->
## Custom TODO Entity Kind
<!--TODO-->
@@ -0,0 +1,19 @@
---
id: search
sidebar_label: 002 - Search
title: Integrating with Search
---
## Search
### What is Backstage Search?
<!--TODO-->
### Common integration points
<!--TODO-->
## Creating a custom TODO collator
<!--TODO-->
@@ -0,0 +1,23 @@
---
id: permissions
sidebar_label: 003 - Permissions
title: Integrating with the Permission framework
---
## Permissions
### What is the Permissions framework?
<!--TODO-->
### Common integration points
<!--TODO-->
## Creating private TODOs
<!--TODO-->
## Restricting who can create TODOs
<!--TODO-->
@@ -0,0 +1,23 @@
---
id: notifications
sidebar_label: 004 - Notifications
title: Integrating with Notifications
---
## Notifications
### What are Backstage Notifications?
<!--TODO-->
### Common integration points
<!--TODO-->
## TODO with an alarm
<!--TODO-->
## Create TODOs for other people and notify them
<!--TODO-->
+3 -9
View File
@@ -9,11 +9,7 @@ description: Automatically discovering catalog entities from repositories in an
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Azure DevOps integration has a special entity provider for discovering
catalog entities within an Azure DevOps. The provider will crawl your Azure
DevOps organization and register entities matching the configured path. This can
be useful as an alternative to static locations or manually adding things to the
catalog.
The Azure DevOps integration has a special entity provider for discovering catalog entities within an Azure DevOps. The provider will crawl your Azure DevOps organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the catalog.
This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor.
@@ -21,9 +17,7 @@ This guide explains how to install and configure the Azure DevOps Entity Provide
### Code Search Feature
Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure
DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure
DevOps Server you'll find this information in your Collection Settings.
Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure DevOps Server you'll find this information in your Collection Settings.
If the Code Search extension is not listed then you can install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms.vss-code-search&targetId=f9352dac-ba6e-434e-9241-a848a510ce3f&utm_source=vstsproduct&utm_medium=SearchExtStatus).
@@ -68,7 +62,7 @@ catalog:
The parameters available are:
- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host.
- **`host:`** _(optional)_ The default value is `dev.azure.com`, it is required for legacy `{org}.visualstudio.com` domains or for on-premise installations.
- **`organization:`** Your Organization slug (or Collection for on-premise users). Required.
- **`project:`** _(required)_ Your project slug. Wildcards are supported as shown on the examples above. Using '\*' will search all projects. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`.
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
+41
View File
@@ -200,6 +200,47 @@ However a system-assigned managed identity is the most secure option because:
:::
### Legacy `{org}.visualstudio.com` Domains
Backstage supports the legacy `{org}.visualstudio.com` domains along with all the previously mentioned authentication options, the caveat is that each Azure DevOps Organization will need to be defined in your configuration along with a single credential.
For example, this will work:
```yaml
integrations:
azure:
- host: my-org.visualstudio.com
credentials:
- clientId: ${AZURE_CLIENT_ID}
clientSecret: ${AZURE_CLIENT_SECRET}
tenantId: ${AZURE_TENANT_ID}
```
As will this:
```yaml
integrations:
azure:
- host: my-other-org.visualstudio.com
credentials:
- personalAccessToken: ${PERSONAL_ACCESS_TOKEN}
```
But this will NOT work:
```yaml
integrations:
azure:
- host: my-org.visualstudio.com
credentials:
- organizations:
- my-org
- my-other-org
clientId: ${AZURE_CLIENT_ID}
clientSecret: ${AZURE_CLIENT_SECRET}
tenantId: ${AZURE_TENANT_ID}
```
## Configuration schema
The configuration is a structure with these elements:
+1 -1
View File
@@ -375,7 +375,7 @@ schedule:
timeout: { minutes: 3 }
```
More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page.
More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/api/stable/interfaces/_backstage_backend-plugin-api.index.SchedulerServiceTaskScheduleDefinition.html) page.
Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication
which carries a much higher rate limit at GitHub.
+1 -1
View File
@@ -98,7 +98,7 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru
- `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts.
- `githubUrl`: The target that this provider should consume
- `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only).
- `schedule`: The refresh schedule to use, matches the structure of [`SchedulerServiceTaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinitionconfig/)
- `schedule`: The refresh schedule to use, matches the structure of [`SchedulerServiceTaskScheduleDefinitionConfig`](https://backstage.io/api/stable/interfaces/_backstage_backend-plugin-api.index.SchedulerServiceTaskScheduleDefinition.html)
- `pageSizes` (optional): Configure page sizes for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors. You can configure the following page sizes:
- `teams`: Number of teams to fetch per page when querying organization teams (default: 25)
+2 -1
View File
@@ -154,11 +154,12 @@ catalog:
fallbackBranch: master # Optional. Fallback to be used if there is no default branch configured at the Gitlab repository. It is only used, if `branch` is undefined. Uses `master` as default
skipForkedRepos: false # Optional. If the project is a fork, skip repository
includeArchivedRepos: false # Optional. If project is archived, include repository
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
group: example-group # Optional (unless useSearch is true). Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
groupPattern: # Optional. Filters for groups based on a list of RegEx. Default, no filters.
- '^somegroup$'
- 'anothergroup'
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
useSearch: false # Optional. Whether to use the GitLab group search API to find files. Requires Gitlab 'Premium' or 'Ultimate' licenses. Defaults to `false`
projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything
excludeRepos: [] # Optional. A list of project paths that should be excluded from discovery, e.g. group/subgroup/repo. Should not start or end with a slash.
schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System
+1 -1
View File
@@ -285,7 +285,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import {
GitlabOrgDiscoveryEntityProvider,
+1 -1
View File
@@ -107,7 +107,7 @@ description: Documentation landing page.
<li><a href='https://backstage.io/docs/architecture-decisions/'>Architecture Decision Records (ADRs)</a></li>
<li><a href='https://backstage.io/docs/api/deprecations'>Deprecations</a></li>
<li><a href='https://backstage.io/docs/api/utility-apis'>Utility APIs</a></li>
<li><a href='https://backstage.io/docs/reference/'>Package Index</a></li>
<li><a href='https://backstage.io/api/stable/'>API References</a></li>
<li><a href='https://backstage.io/docs/faq/'>FAQ</a></li>
</ul>
</td>

Some files were not shown because too many files have changed in this diff Show More