Merge branch 'master' into topic/nbs-integration-bitbucket-server

Signed-off-by: Kevin L. <kevin.lecouvey@gmail.com>
This commit is contained in:
Kevin L.
2024-07-26 07:30:38 -04:00
committed by GitHub
1092 changed files with 34769 additions and 7421 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+18 -1
View File
@@ -79,8 +79,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-atlassian-provider
```
Then we will need to this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-atlassian-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `atlassianAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+1 -1
View File
@@ -64,4 +64,4 @@ Auth0 requires a session, so you need to give the session a secret key.
To add the provider to the frontend, add the `auth0AuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+36 -31
View File
@@ -8,37 +8,6 @@ description: Adding AWS ALB as an authentication provider in Backstage
Backstage can de deployed behind [AWS Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html)
and get the user seamlessly authenticated.
## Installation
### Backend
:::note
These instructions are written for the [new backend system](../../backend-system/index.md).
:::
Add the `@backstage/plugin-auth-backend-module-aws-alb-provider` to your backend installation.
```sh
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-aws-alb-provider
```
Then, add it to your backend's source,
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
backend.add(import('@backstage/plugin-auth-backend'));
// highlight-add-next-line
backend.add(import('@backstage/plugin-auth-backend-module-aws-alb-provider'));
await backend.start();
```
### Frontend
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development.
## Configuration
The provider configuration can be added to your `app-config.yaml` under the root
@@ -57,4 +26,40 @@ auth:
- resolver: emailLocalPartMatchingUserEntityName
```
### Resolvers
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`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-aws-alb-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-aws-alb-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `awsalb` as the provider name.
If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely.
+42 -37
View File
@@ -37,6 +37,13 @@ auth:
development:
clientId: ${AUTH_BITBUCKET_CLIENT_ID}
clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET}
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: userIdMatchingUserEntityAnnotation
- resolver: usernameMatchingUserEntityAnnotation
```
The Bitbucket provider is a structure with two configuration keys:
@@ -45,44 +52,42 @@ The Bitbucket provider is a structure with two configuration keys:
`b59241722e3c3b4816e2`
- `clientSecret`: The Secret tied to the generated Key.
### Resolvers
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`.
- `userIdMatchingUserEntityAnnotation`: Matches the `userId` from the auth provider with the User entity that has a matching `bitbucket.org/user-id` annotation. If no match is found it will throw a `NotFoundError`.
- `usernameMatchingUserEntityAnnotation`: Matches the `username` from the auth provider with the User entity that has a matching `bitbucket.org/username` annotation. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-bitbucket-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-bitbucket-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `bitbucketAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
## Using Bitbucket for sign-in
In order to use the Bitbucket provider for sign-in, you must configure it with a
`signIn.resolver`. See the
[Sign-In Resolver documentation](../identity-resolver.md) for more details on
how this is done. Note that for the Bitbucket provider, you'll want to use
`bitbucket` as the provider ID, and `providers.bitbucket.create` for the provider
factory.
The `@backstage/plugin-auth-backend` plugin also comes with two built-in
resolvers that can be used if desired. The first one is the
`bitbucketUsernameSignInResolver`, which identifies users by matching their
Bitbucket username to `bitbucket.org/username` annotations of `User` entities in
the catalog. Note that you must populate your catalog with matching entities or
users will not be able to sign in.
The second resolver is the `bitbucketUserIdSignInResolver`, which works the
same way, but uses the Bitbucket user ID instead, and matches on the
`bitbucket.org/user-id` annotation.
The following is an example of how to use one of the built-in resolvers:
```ts
import { providers } from '@backstage/plugin-auth-backend';
// ...
providerFactories: {
bitbucket: providers.bitbucket.create({
signIn: {
resolver:
providers.bitbucket.resolvers.usernameMatchingUserEntityAnnotation(),
},
}),
},
```
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+3 -3
View File
@@ -10,8 +10,8 @@ users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud.
## Create an Application Link in Bitbucket Server
To add Bitbucket Server authentication, you must create an outgoing application link. Follow the steps described in
the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-outgoing-link-1108483656.html)
To add Bitbucket Server authentication, you must create an incoming application link. Follow the steps described in
the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html)
to create one.
## Configuration
@@ -37,7 +37,7 @@ The Bitbucket Server provider is a structure with two configuration keys:
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown
in [Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
in [Adding the provider to the sign-in page](../index.md#sign-in-configuration).
## Using Bitbucket Server for sign-in
-174
View File
@@ -1,174 +0,0 @@
---
id: provider
title: Cloudflare Access Provider
sidebar_label: Cloudflare Access
description: Adding Cloudflare Access as an authentication provider in Backstage
---
Similar to GCP IAP Proxy Provider or AWS ALB provider, developers can offload authentication
support to Cloudflare Access.
This tutorial shows how to use authentication on Cloudflare Access sitting in
front of Backstage.
It is assumed a Cloudflare tunnel is already serving traffic in front of a
Backstage instance configured to serve the frontend app from the backend and is
already gated using Cloudflare Access.
## Configuration
Let's start by adding the following `auth` configuration in your
`app-config.yaml` or `app-config.production.yaml` or similar:
```yaml
auth:
providers:
cfaccess:
# You can find the team name in the Cloudflare Zero Trust dashboard.
teamName: <Team Name>
# This service tokens section is optional -- you only need it if you have
# some Cloudflare Service Tokens that you want to be able to log in to your
# Backstage instance.
serviceTokens:
- token: '1uh2fh19efvfh129f1f919u21f2f19jf2.access'
subject: 'bot-user@your-company.com'
# This picks what sign in resolver(s) you want to use.
signIn:
resolvers:
- resolver: emailMatchingUserEntityProfileEmail
```
This config section must be in place for the provider to load at all.
The `signIn` section picks what sign-in resolver(s) to use for sign-in attempts.
It is responsible for matching the upstream provider's sign-in result to a
corresponding Backstage identity, or to throw an error if the attempt should be
rejected for any reason. The `emailMatchingUserEntityProfileEmail` is a common
choice: it tries to match the email of the signed-in user to a `User` kind
entity in the catalog whose profile email matches that.
If the builtin sign in resolvers do not match your needs, you can skip the
`signIn` section and instead [provide a custom resolver](#advanced-custom-sign-in-resolver).
## Backend Changes
We need to add the provider package as a dependency to our backend:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-cloudflare-access-provider
```
And to tell the backend to load it:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-cloudflare-access-provider'),
);
/* highlight-add-end */
```
Now the backend is ready to serve auth requests on the
`/api/auth/cfaccess/refresh` endpoint. All that's left is to update the frontend
sign-in mechanism to poll that endpoint through Cloudflare Access, on the user's
behalf.
## Frontend Changes
It is recommended to use the `ProxiedSignInPage` for this provider, which is
installed in your app like this:
```tsx title="in packages/app/src/App.tsx"
/* highlight-add-next-line */
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="cfaccess" />,
},
/* highlight-add-end */
// ...
});
```
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for
pointers on how to set up the sign-in page to also work smoothly for local
development.
## Advanced: Custom Sign-in Resolver
If none of the built-in sign in resolvers fit your needs, you need to provide a
customized version of the module. Now you should _not_
`backend.add(import(...))`, instead you will do the following.
```ts title="in packages/backend/plugin/auth.ts"
/* highlight-add-start */
import { createCloudflareAccessAuthenticator } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider';
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import {
authProvidersExtensionPoint,
createProxyAuthProviderFactory,
} from '@backstage/plugin-auth-node';
const customAuth = createBackendModule({
// This ID must be exactly "auth" because that's the plugin it targets
pluginId: 'auth',
// This ID must be unique, but can be anything
moduleId: 'custom-auth-provider',
register(reg) {
reg.registerInit({
deps: {
providers: authProvidersExtensionPoint,
cache: coreServices.cache,
},
async init({ providers, cache }) {
providers.registerProvider({
// This ID must match the actual provider config, e.g. addressing
// auth.providers.github means that this must be "github".
providerId: 'cfaccess',
// Use createProxyAuthProviderFactory instead if it's one of the proxy
// based providers rather than an OAuth based one
factory: createProxyAuthProviderFactory({
authenticator: createCloudflareAccessAuthenticator({ cache }),
async signInResolver(info, ctx) {
// This is where the body of the sign-in resolver goes!
const { profile } = info;
if (!profile.email) {
throw new Error(
'Login failed, user profile does not contain an email',
);
}
return ctx.signInWithCatalogUser({
filter: {
'spec.profile.email': profile.email,
},
});
},
}),
});
},
});
},
});
/* highlight-add-end */
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-remove-start */
backend.add(
import('@backstage/plugin-auth-backend-module-cloudflare-access-provider'),
);
/* highlight-remove-end */
/* highlight-add-next-line */
backend.add(customAuth);
```
The body of the sign-in resolver is up to you to write! The example code above
is just a copy of what `emailMatchingUserEntityProfileEmail` does. The `info`
parameter contains all of the results of the sign-in attempt so far. The `ctx`
context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/)
for issuing tokens in various ways.
+82
View File
@@ -0,0 +1,82 @@
---
id: provider
title: Cloudflare Access Provider
sidebar_label: Cloudflare Access
description: Adding Cloudflare Access as an authentication provider in Backstage
---
Similar to GCP IAP Proxy Provider or AWS ALB provider, developers can offload authentication
support to Cloudflare Access.
This tutorial shows how to use authentication on Cloudflare Access sitting in
front of Backstage.
It is assumed a Cloudflare tunnel is already serving traffic in front of a
Backstage instance configured to serve the frontend app from the backend and is
already gated using Cloudflare Access.
## Configuration
Let's start by adding the following `auth` configuration in your
`app-config.yaml` or `app-config.production.yaml` or similar:
```yaml
auth:
providers:
cfaccess:
# You can find the team name in the Cloudflare Zero Trust dashboard.
teamName: <Team Name>
# This service tokens section is optional -- you only need it if you have
# some Cloudflare Service Tokens that you want to be able to log in to your
# Backstage instance.
serviceTokens:
- token: '1uh2fh19efvfh129f1f919u21f2f19jf2.access'
subject: 'bot-user@your-company.com'
# This picks what sign in resolver(s) you want to use.
signIn:
resolvers:
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
```
This config section must be in place for the provider to load at all.
### Resolvers
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`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-cloudflare-access-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-cloudflare-access-provider'),
);
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `cfaccess` as the provider name.
If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely.
+26 -9
View File
@@ -26,6 +26,14 @@ Settings for local development:
- Homepage URL: `http://localhost:3000`
- Authorization callback URL: `http://localhost:7007/api/auth/github/handler/frame`
### Difference between GitHub Apps and GitHub OAuth Apps
GitHub Apps handle OAuth scope at the app installation level, meaning that the
`scope` parameter for the call to `getAccessToken` in the frontend has no
effect. When calling `getAccessToken` in open source plugins, one should still
include the appropriate scope, but also document in the plugin README what
scopes are required for GitHub Apps.
## Configuration
The provider configuration can then be added to your `app-config.yaml` under the
@@ -79,16 +87,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `githubAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
## Difference between GitHub Apps and GitHub OAuth Apps
GitHub Apps handle OAuth scope at the app installation level, meaning that the
`scope` parameter for the call to `getAccessToken` in the frontend has no
effect. When calling `getAccessToken` in open source plugins, one should still
include the appropriate scope, but also document in the plugin README what
scopes are required for GitHub Apps.
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+18 -1
View File
@@ -78,8 +78,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gitlab-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-gitlab-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `gitlabAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+9 -86
View File
@@ -58,102 +58,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Changes
## Backend Installation
There is a module for this provider that you will need to add to your backend.
To add the provider to the backend we will first need to install the package by running this command:
First you'll want to run this command to add the module:
```sh
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider
```
Then you will need to add this to your backend:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider'));
/* highlight-add-end */
```
### Legacy Backend Changes
## Adding the provider to the Backstage frontend
If you are still using the legacy backend you will need to make the changes outlined here.
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `gcp-iap` as the provider name.
This provider is not enabled by default in the auth backend code, because besides the config section above, it also needs to be given one or more callbacks in actual code as well as described below.
Add a `providerFactories` entry to the router in
`packages/backend/src/plugins/auth.ts`.
```ts title="packages/backend/src/plugins/auth.ts"
import { providers } from '@backstage/plugin-auth-backend';
import { stringifyEntityRef } from '@backstage/catalog-model';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
discovery: env.discovery,
providerFactories: {
'gcp-iap': providers.gcpIap.create({
// Replace the auth handler if you want to customize the returned user
// profile info (can be left out; the default implementation is shown
// below which only returns the email). You may want to amend this code
// with something that loads additional user profile data out of e.g.
// GSuite or LDAP or similar.
async authHandler({ iapToken }) {
return { profile: { email: iapToken.email } };
},
signIn: {
// You need to supply an identity resolver, that takes the profile
// and the IAP token and produces the Backstage token with the
// relevant user info.
async resolver({ profile, result: { iapToken } }, ctx) {
// Somehow compute the Backstage token claims. Just some sample code
// shown here, but you may want to query your LDAP server, or
// GSuite or similar, based on the IAP token sub/email claims
const id = iapToken.email.split('@')[0];
const sub = stringifyEntityRef({ kind: 'User', name: id });
const ent = [
sub,
stringifyEntityRef({ kind: 'Group', name: 'team-name' }),
];
return ctx.issueToken({ claims: { sub, ent } });
},
},
}),
},
});
}
```
Now the backend is ready to serve auth requests on the
`/api/auth/gcp-iap/refresh` endpoint. All that's left is to update the frontend
sign-in mechanism to poll that endpoint through the IAP, on the user's behalf.
## Frontend Changes
It is recommended to use the `ProxiedSignInPage` for this provider, which is
installed in `packages/app/src/App.tsx` like this:
```tsx title="packages/app/src/App.tsx"
/* highlight-add-next-line */
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="gcp-iap" />,
},
/* highlight-add-end */
// ..
});
```
See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information.
If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely.
+20 -3
View File
@@ -24,9 +24,9 @@ To support Google authentication, you must create OAuth credentials:
- Add yourself as a test user, if using External user type
6. Set **Application Type** to `Web Application` with these settings:
- `Name`: Backstage (or your custom app name)
- `Authorized JavaScript origins`: http://localhost:3000
- `Authorized JavaScript origins`: <http://localhost:3000>
- `Authorized Redirect URIs`:
http://localhost:7007/api/auth/google/handler/frame
<http://localhost:7007/api/auth/google/handler/frame>
7. Click Create
## Configuration
@@ -72,8 +72,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-google-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-google-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `googleAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+1 -2
View File
@@ -25,8 +25,7 @@ This will only work with the new backend system. There is no support for this in
Add the `@backstage/plugin-auth-backend-module-guest-provider` to your backend installation.
```sh
# From your Backstage root directory
```sh title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-guest-provider
```
+2
View File
@@ -124,6 +124,8 @@ The list of available resolvers is different for each provider, since they often
depend on the information model returned from the upstream provider service.
Consult the documentation of the respective provider to find the list.
In the example above `emailMatchingUserEntityProfileEmail` and `emailLocalPartMatchingUserEntityName` are common to all auth providers and `usernameMatchingUserEntityName` is specific to GitHub.
### Building Custom Resolvers
If the builtins don't work for you, you can also provide a completely custom
+3 -8
View File
@@ -12,12 +12,7 @@ access to external resources.
:::note Note
Identity management and the Sign-In page in Backstage is NOT a method for blocking
access for unauthorized users. The identity system only serves to provide a personalized
experience and access to a Backstage Identity Token, which can be passed to backend plugins.
This also means that your Backstage backend APIs are by default unauthenticated.
Thus, if your Backstage instance is exposed to the Internet, anyone can access
information in the Backstage. You can learn more [here](../overview/threat-model.md#integrator-responsibilities).
Identity management and the Sign-In page in Backstage will only block external access when using the new backend system, without setting `backend.auth.dangerouslyDisableDefaultAuthPolicy` in configuration. Even so, the frontend bundle is not protected from external access, protecting it requires the use of the [experimental public entry point](https://backstage.io/docs/tutorials/enable-public-entry/). You can learn more about this in the [Threat Model](../overview/threat-model.md#operator-responsibilities).
:::
@@ -31,7 +26,7 @@ Backstage comes with many common authentication providers in the core library:
- [Azure Easy Auth](microsoft/azure-easyauth.md)
- [Bitbucket](bitbucket/provider.md)
- [Bitbucket Server](bitbucketServer/provider.md)
- [Cloudflare Access](cloudflare/access.md)
- [Cloudflare Access](cloudflare/provider.md)
- [GitHub](github/provider.md)
- [GitLab](gitlab/provider.md)
- [Google](google/provider.md)
@@ -158,7 +153,7 @@ Some auth providers are so-called "proxy" providers, meaning they're meant to be
behind an authentication proxy. Examples of these are
[Amazon Application Load Balancer](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md),
[Azure EasyAuth](./microsoft/azure-easyauth.md),
[Cloudflare Access](./cloudflare/access.md),
[Cloudflare Access](./cloudflare/provider.md),
[Google Identity-Aware Proxy](./google/gcp-iap-auth.md)
and [OAuth2 Proxy](./oauth2-proxy/provider.md).
+57 -81
View File
@@ -7,87 +7,6 @@ description: Adding Azure's EasyAuth Proxy as an authentication provider in Back
The Backstage `core-plugin-api` package comes with a Microsoft authentication provider that can authenticate users using Microsoft Entra ID (formerly Azure Active Directory) for PaaS service hosted in Azure that support Easy Auth, such as Azure App Services.
## Backend Changes
Add the following into your `app-config.yaml` under the root `auth` configuration:
```yaml title="app-config.yaml"
auth:
providers:
azureEasyAuth:
signIn:
resolvers:
- resolver: idMatchingUserEntityAnnotation
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
```
The `idMatchingUserEntityAnnotation` is
[a builtin sign-in resolver](../identity-resolver.md#using-builtin-resolvers) from `azureEasyAuth` provider.
It tries to find a user entity with [a `graph.microsoft.com/user-id` annotation](../../features/software-catalog/well-known-annotations.md#graphmicrosoftcomtenant-id-graphmicrosoftcomgroup-id-graphmicrosoftcomuser-id)
which matches the object ID of the user attempting to sign in.
If you want to provide your own sign-in resolver,
see [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers).
Add the `@backstage/plugin-auth-backend-module-azure-easyauth-provider` to your backend installation.
```sh
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-azure-easyauth-provider
```
Then, add it to your backend's source,
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
backend.add(import('@backstage/plugin-auth-backend'));
// highlight-add-next-line
backend.add(
import('@backstage/plugin-auth-backend-module-azure-easyauth-provider'),
);
await backend.start();
```
Now the backend is ready to serve auth requests on the
`/api/auth/azureEasyAuth/refresh` endpoint. All that's left is to update the frontend
sign-in mechanism to poll that endpoint through the Easy Auth proxy, on the user's behalf.
## Frontend Changes
To use this component, you'll need to configure the app's `SignInPage`.
It is recommended to use the `ProxiedSignInPage` for this provider when running in Azure, However for local development (or any other scenario running outside of Azure), you'll want to set up something different.
For the closest experience to Easy Auth, you could set up the `microsoft` provider locally, but that will requires setting up App Registrations & secrets which may be locked down by your organisation, in which case it may be easier to use guest login locally.
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for more details.
```tsx title="packages/app/src/App.tsx"
/* highlight-add-next-line */
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => {
const configApi = useApi(configApiRef);
if (configApi.getString('auth.environment') !== 'development') {
return <ProxiedSignInPage {...props} provider="azureEasyAuth" />;
}
return (
<SignInPage
{...props}
providers={['guest', 'custom']}
title="Select a sign-in method"
align="center"
/>
);
},
},
/* highlight-add-end */
// ..
});
```
## Azure Configuration
How to configure azure depends on the Azure service you're using to host Backstage.
@@ -136,3 +55,60 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' existing = {
}
}
```
## Configuration
Add the following into your `app-config.yaml` under the root `auth` configuration:
```yaml title="app-config.yaml"
auth:
providers:
azureEasyAuth:
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: idMatchingUserEntityAnnotation
```
### Resolvers
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`.
- `idMatchingUserEntityAnnotation`: Matches the user Id from the auth provider with the User entity that has a matching `graph.microsoft.com/user-id` annotation. If no match is found it will throw a `NotFoundError`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-azure-easyauth-provider
```
Then we will need to this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-azure-easyauth-provider'),
);
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `azureEasyAuth` as the provider name.
If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely.
+31 -16
View File
@@ -40,6 +40,18 @@ If you're using an existing app registration, and backstage already has a client
If not, go to the **Certificates & Secrets** page, then the **Client secrets** tab and create a new client secret.
Make a note of this value as you'll need it in the next section.
## Outbound Network Access
If your environment has restrictions on outgoing access (e.g. through
firewall rules), make sure your Backstage backend has access to the following
hosts:
- `login.microsoftonline.com`, to get and exchange authorization codes and access
tokens
- `graph.microsoft.com`, to fetch user profile information (as seen
in [this source code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)).
If this host is unreachable, users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in.
## Configuration
The provider configuration can then be added to your `app-config.yaml` under the
@@ -55,8 +67,6 @@ auth:
clientSecret: ${AZURE_CLIENT_SECRET}
tenantId: ${AZURE_TENANT_ID}
domainHint: ${AZURE_TENANT_ID}
additionalScopes:
- Mail.Send
signIn:
resolvers:
# typically you would pick one of these
@@ -74,7 +84,7 @@ The Microsoft provider is a structure with three mandatory configuration keys:
Leave blank if your app registration is multi tenant.
When specified, this reduces login friction for users with accounts in multiple tenants by automatically filtering away accounts from other tenants.
For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy)
- `additionalScopes` (optional): List of scopes for the App Registration. The default and mandatory value is ['user.read'].
- `additionalScopes` (optional): List of scopes for the App Registration, to be requested in addition to the required ones.
### Resolvers
@@ -92,20 +102,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-microsoft-provider
```
Then we will need to this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `microsoftAuthApiRef` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
## Outbound Network Access
If your environment has restrictions on outgoing access (e.g. through
firewall rules), make sure your Backstage backend has access to the following
hosts:
- `login.microsoftonline.com`, to get and exchange authorization codes and access
tokens
- `graph.microsoft.com`, to fetch user profile information (as seen
in [this source code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)).
If this host is unreachable, users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in.
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+20 -18
View File
@@ -47,25 +47,27 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Adding the provider to the Backstage frontend
## Backend Installation
It is recommended to use the `ProxiedSignInPage` for this provider, which is
installed in `packages/app/src/App.tsx` like this:
To add the provider to the backend we will first need to install the package by running this command:
```tsx title="packages/app/src/App.tsx"
/* highlight-add-next-line */
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => (
<ProxiedSignInPage {...props} provider="oauth2Proxy" />
),
},
/* highlight-add-end */
// ..
});
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oauth2-proxy-provider
```
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page to also work smoothly for local development.
Then we will need to this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-oauth2-proxy-provider'),
);
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page, and to also make it work smoothly for local development. You'll use `oauth2Proxy` as the provider name.
If you [provide a custom sign in resolver](https://backstage.io/docs/auth/identity-resolver#building-custom-resolvers), you can skip the `signIn` block entirely.
+18 -1
View File
@@ -83,8 +83,25 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-okta-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-okta-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `oktaAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+40 -1
View File
@@ -38,6 +38,12 @@ auth:
clientId: ${AUTH_ONELOGIN_CLIENT_ID}
clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
issuer: https://<company>.onelogin.com/oidc/2
signIn:
resolvers:
# typically you would pick one of these
- resolver: emailMatchingUserEntityProfileEmail
- resolver: emailLocalPartMatchingUserEntityName
- resolver: usernameMatchingUserEntityName
```
The OneLogin provider is a structure with three configuration keys; **these are
@@ -47,8 +53,41 @@ found on the SSO tab** for the OneLogin Application:
- `clientSecret`: The client secret
- `issuer`: The issuer URL
### Resolvers
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`.
:::note Note
The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`.
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-onelogin-provider
```
Then we will need to add this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-auth-backend-module-onelogin-provider'));
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `oneloginAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+30 -103
View File
@@ -33,108 +33,6 @@ Cloud Console and within a Backstage app required to enable this capability.
1. Take note of the `App ID` in the resulting modal; this is the client ID to be
used by Backstage.
## Install the provider in the backend
### New backend system
Apps using the [new backend system](../../backend-system/index.md),
can enable the VMware Cloud provider with a small modification like:
```ts title="packages/backend/src/index.ts"
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-vmware-cloud-provider'),
);
/* highlight-add-end */
backend.start();
```
### Old backend system
This provider was added after the migration of the auth-backend plugin to the
new backend system, so no default provider factory was added. Because of this,
the installation procedure for old-style backends is slightly more involved:
```ts title="packages/backend/src/plugins/auth.ts"
import {
DEFAULT_NAMESPACE,
stringifyEntityRef,
} from '@backstage/catalog-model';
import {
createRouter,
providers,
defaultAuthProviderFactories,
} from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
/* highlight-add-start */
import {
commonSignInResolvers,
createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import {
vmwareCloudAuthenticator,
} from '@backstage/plugin-auth-backend-module-vmware-cloud-provider';
/* highlight-add-end */
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
discovery: env.discovery,
tokenManager: env.tokenManager,
providerFactories: {
...defaultAuthProviderFactories,
/* highlight-add-start */
vmwareCloudServices: createOAuthProviderFactory({
authenticator: vmwareCloudAuthenticator,
signInResolver:
commonSignInResolvers.emailLocalPartMatchingUserEntityName(),
}),
/* highlight-add-end */
```
In the above, `commonSignInResolvers.emailLocalPartMatchingUserEntityName()`
can be replaced with a more suitable resolver for the app in question.
## Add to Sign-in Page
See the [Sign-In Configuration](../index.md#sign-in-configuration) docs for
general guidance, but as an example:
```tsx title="packages/app/src/App.tsx"
/* highlight-add-start */
import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api';
import { SignInPage } from '@backstage/core-components';
/* highlight-add-end */
const app = createApp({
/* highlight-add-start */
components: {
SignInPage: props => (
<SignInPage
{...props}
provider={{
id: 'vmware-cloud-auth-provider',
title: 'VMware Cloud',
message: 'Sign in using VMware Cloud',
apiRef: vmwareCloudAuthApiRef,
}}
/>
),
},
/* highlight-add-end */
// ..
});
```
## Configuration
Add the following to your `app-config.yaml` under the root `auth` configuration:
@@ -161,13 +59,17 @@ Where `APP_ID` refers to the ID retrieved when creating the OAuth App, and
`ORG_ID` is the [long ID of the Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1)
in VMware Cloud for which you wish to enable sign-in.
Note that VMware Cloud requires OAuth Apps to use
:::note Note
VMware Cloud requires OAuth Apps to use
[PKCE](https://oauth.net/2/pkce/) when performing authorization code flows; the
library used by this provider requires the use of Express session middleware to
do this. Therefore the value `your session secret` under `auth.session.secret`
should be replaced with a long, complex and unique string which will act as a
key for signing session cookies set by Backstage.
:::
### Resolvers
This provider includes several resolvers out of the box that you can use:
@@ -183,3 +85,28 @@ The resolvers will be tried in order, but will only be skipped if they throw a `
:::
If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation.
## Backend Installation
To add the provider to the backend we will first need to install the package by running this command:
```bash title="from your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-vmware-cloud-provider
```
Then we will need to this line:
```ts title="in packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-auth-backend'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-auth-backend-module-vmware-cloud-provider'),
);
/* highlight-add-end */
```
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `vmwareCloudAuthApiRef` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+55 -22
View File
@@ -190,43 +190,76 @@ If a service defines a default factory, that factory will be used if there is no
When defining a default factory for a service, it is possible for it to end up with duplicate implementations at runtime. This applies both to any shared root context in your factory, as well as plugin specific instances of your service. This is because package dependency version ranges may not line up perfectly, causing duplicate installations of the same package. This can happen both for two different plugins using the same service, but also across a plugin and its modules. If your service would break in this scenario, you should not define a default factory for it, but instead require that users of your service explicitly install a factory in their backend instance.
## Service Factory Options
## Service Factory Customization
When declaring a service factory you may also want to make the export the building blocks of the implementation itself. This is to allow for further customization of the service implementation through code, beyond what is possible with static configuration, without the need to re-implement the entire service from scratch. For example, we might export our example `DefaultFooService` class, while moving construction to a static `create` factory method to make it easier to evolve:
```ts
export class DefaultFooService {
static create(options: { transform: (foo: string) => string }) {
return new DefaultFooService(options.transform ?? (foo) => foo);
}
private constructor(private readonly transform: (foo: string) => string) {}
foo(foo: string): string {
return this.transform(foo);
}
}
```
By exporting `DefaultFooService` we now make it relatively simple for advanced users of our service to customize the implementation. To do so, they can define their own service factory that uses our provided implementation:
```ts
export const customFooServiceFactory = createServiceFactory({
service: fooServiceRef,
deps: {},
factory() {
return DefaultFooService.create({
transform: foo => foo.toUpperCase(),
});
},
});
```
This allows you to provide more advanced options for the service implementation that couldn't be expressed through static configuration. It also gives users of the service implementation access to other services through dependency injection, which can be useful for their customizations.
## Service Factory Options Pattern
:::note Note
This pattern is discouraged, only use it when necessary. If possible you should prefer to make services configurable via static configuration instead.
This pattern is discouraged, only use it when necessary. If possible you should prefer to make services configurable via static configuration or re-implementation instead.
:::
When declaring a service factory it's possible to include an options callback. This allows you to customize the factory through code when installing it in the backend. For example, this is how you install an explicit factory instance in the backend without any options:
In some cases it might be beneficial to allow users of your service factory to pass options to the factory itself, rather than to the service implementation. This can be enabled by also defining the service factory as a function that returns a reconfigured factory. For example:
```ts
const backend = createBackend();
backend.add(fooServiceFactory());
```
Note that we call `fooServiceFactory` to create the service factory instance. This is because `createServiceFactory` always returns a factory function that creates the actual service factory. To add options to your service factory, you wrap the object passed to `createServiceFactory` in a callback that accepts the desired options. Note that the options must always be optional. For example:
```ts
export interface FooFactoryOptions {
const fooServiceFactoryWithOptions = (options?: {
transform: (foo: string) => string;
}
export const fooServiceFactory = createServiceFactory(
(options?: FooFactoryOptions) => ({
}) =>
createServiceFactory<FooService>({
service: fooServiceRef,
deps: {},
factory() {
return new DefaultFooService(options?.transform);
return DefaultFooService.create({
transform: options?.transform,
});
},
}),
});
export const fooServiceFactory = Object.assign(
fooServiceFactoryWithOptions,
fooServiceFactoryWithOptions(),
);
```
This lets us use the options to customize the factory implementation in any way we want. From the outside the service factory looks just like before, except that we're now also able to pass options when installing the factory:
This makes it possible to use the `fooServiceFactory` directly, as well passing additional options to create a customized factory:
```ts
const backend = createBackend();
backend.add(fooServiceFactory({ transform: foo => foo.toUpperCase() }));
backend.add(fooServiceFactory);
// OR
backend.add(fooServiceFactory({ transform: foo => foo.toLowerCase() }));
```
This pattern is discouraged due to the inability to access other services through dependency injection. It is however used in a few places in the Backstage framework where the ability to directly pass options without re-implementing the service is very convenient, such as the `mockServices` from `@backstage/backend-test-utils`.
@@ -1254,7 +1254,6 @@ In order to add your own permission policy you'll need to do the following:
```ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
PolicyDecision,
AuthorizeResult,
@@ -1262,13 +1261,14 @@ import {
import {
PermissionPolicy,
PolicyQuery,
PolicyQueryUser,
} from '@backstage/plugin-permission-node';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
class CustomPermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
// TODO: Add code here that inspects the incoming request and user, and returns AuthorizeResult.ALLOW, AuthorizeResult.DENY, or AuthorizeResult.CONDITIONAL as needed. See the docs at https://backstage.io/docs/permissions/writing-a-policy for more information
@@ -61,7 +61,7 @@ requests and return mock responses. This lets you stub out remote services
rather than the local clients, leading to more thorough and robust tests. You
can read more about how it works [in their documentation](https://mswjs.io/).
The `@backstage/backend-test-utils` package exports a `setupRequestMockHandlers`
The `@backstage/backend-test-utils` package exports a `registerMswTestHooks`
function which ensures that the correct `jest` lifecycle hooks are invoked to
set up and tear down your `msw` instance, and enables the option that completely
rejects requests that don't match one of your mock rules. This ensures that your
@@ -70,13 +70,13 @@ tests cannot accidentally leak traffic into production from tests.
Example:
```ts
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import { registerMswTestHooks } from '@backstage/backend-test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
describe('read from remote', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
registerMswTestHooks(worker);
it('should auth and read successfully', async () => {
expect.assertions(1);
@@ -31,47 +31,3 @@ createBackendPlugin({
},
});
```
## Root Logger
The root logger is the logger that is used by other root services. It's where the implementation lies for creating child loggers around the backstage ecosystem including child loggers for plugins with the correct metadata and annotations.
If you want to override the implementation for logging across all of the backend, this is the service that you should override.
## Configuring the service
The following example is how you can override the root logger service to add additional metadata to all log lines.
```ts
import { coreServices } from '@backstage/backend-plugin-api';
import { WinstonLogger } from '@backstage/backend-app-api';
const backend = createBackend();
backend.add(
createServiceFactory({
service: coreServices.rootLogger,
deps: {
config: coreServices.rootConfig,
},
async factory({ config }) {
const logger = WinstonLogger.create({
meta: {
service: 'backstage',
// here's some additional information that is not part of the
// original implementation
podName: 'myk8spod',
},
level: process.env.LOG_LEVEL || 'info',
format:
process.env.NODE_ENV === 'production'
? format.json()
: WinstonLogger.colorFormat(),
transports: [new transports.Console()],
});
return logger;
},
}),
);
```
@@ -60,3 +60,34 @@ backend.add(
}),
);
```
For more advanced customization, there are several APIs from the `@backstage/config-loader` package that allow you to customize the implementation of the config service. The default implementation uses the `ConfigSources.default` method, which has the same options as the `rootConfigServiceFactory` function. You can use these to create your own config service implementation:
```ts
import { ConfigSources } from '@backstage/config-loader';
import { createServiceFactory } from '@backstage/backend-plugin-api';
const backend = createBackend();
backend.add(
createServiceFactory({
service: coreServices.rootConfig,
deps: {},
async factory() {
const source = ConfigSources.default({
argv: [
'--config',
'/backstage/app-config.development.yaml',
'--config',
'/backstage/app-config.yaml',
],
remote: { reloadIntervalSeconds: 60 },
});
console.log(`Loading config from ${source}`);
return await ConfigSources.toConfig(source);
},
}),
);
```
You can also use other config source such as `StaticConfigSource` and combine them with other sources using `ConfigSources.merge(...)`. You can also create your own config source by implementing the `ConfigSource` interface.
@@ -1,7 +1,7 @@
---
id: root-health
title: Root Health Service
sidebar_label: Health
sidebar_label: Root Health
description: Documentation for the Health service
---
@@ -5,4 +5,46 @@ sidebar_label: Root Logger
description: Documentation for the Root Logger service
---
TODO
## Root Logger
The root logger is the logger that is used by other root services. It's where the implementation lies for creating child loggers around the backstage ecosystem including child loggers for plugins with the correct metadata and annotations.
If you want to override the implementation for logging across all of the backend, this is the service that you should override.
## Configuring the service
The following example is how you can override the root logger service to add additional metadata to all log lines.
```ts
import { coreServices } from '@backstage/backend-plugin-api';
import { WinstonLogger } from '@backstage/backend-app-api';
const backend = createBackend();
backend.add(
createServiceFactory({
service: coreServices.rootLogger,
deps: {
config: coreServices.rootConfig,
},
async factory({ config }) {
const logger = WinstonLogger.create({
meta: {
service: 'backstage',
// here's some additional information that is not part of the
// original implementation
podName: 'myk8spod',
},
level: process.env.LOG_LEVEL || 'info',
format:
process.env.NODE_ENV === 'production'
? format.json()
: WinstonLogger.colorFormat(),
transports: [new transports.Console()],
});
return logger;
},
}),
);
```
+101 -34
View File
@@ -5,40 +5,37 @@ sidebar_label: Heroku
description: How to deploy Backstage to Heroku
---
Heroku is a Platform as a Service (PaaS) designed to handle application
deployment in a hands-off way. Heroku supports container deployment of Docker
images, a natural fit for Backstage.
Heroku is a Platform as a Service (PaaS) designed to simplify application deployment.
## Configuring the CLI
## Create App
First, install the
[heroku-cli](https://devcenter.heroku.com/articles/heroku-cli) and login:
Starting with an existing Backstage app or follow the [getting started guide](https://backstage.io/docs/getting-started/) to create a new one.
Install the
[Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) and create a new Heroku app:
```shell
$ heroku login
cd your-app/
heroku apps:create <your-app>
```
If you have not yet created a project through the Heroku interface, you can create it through the CLI.
## Domain
Get Heroku app URL:
```shell
$ heroku create <your-app>
heroku domains -a <your-app>
<your-app-123>.herokuapp.com
```
You _might_ also need to set your Heroku app's stack to `container`:
```bash
$ heroku stack:set container -a <your-app>
```
Configuring your `app-config.yaml`:
The core [app-backend plugin](https://www.npmjs.com/package/@backstage/plugin-app-backend) allows a single Heroku app to serve the frontend and backend. To make this work you need to update the `baseUrl` and `port` in `app-config.production.yaml`:
```yaml
app:
# Should be the same as backend.baseUrl when using the `app-backend` plugin
baseUrl: https://<your-app>.herokuapp.com
baseUrl: https://<your-app-123>.herokuapp.com
backend:
baseUrl: https://<your-app>.herokuapp.com
baseUrl: https://<your-app-123>.herokuapp.com
listen:
port:
$env: PORT
@@ -46,28 +43,98 @@ backend:
# https://devcenter.heroku.com/articles/dynos#web-dynos
```
> Make sure your file is being copied into your container in the `Dockerfile`.
## Build Script
Before building the Docker image, run the [backstage host build commands](https://backstage.io/docs/deployment/docker#host-build). They must be run whenever you are going to publish a new image.
Add a build script in `package.json` to compile frontend during deployment:
Heroku runs a container registry on `registry.heroku.com`. To push Backstage
Docker images, log in to the container registry also:
```json
"scripts": {
"build": "yarn build:backend --config ../../app-config.yaml --config ../../app-config.production.yaml"
```
## Start Command
Create a [Procfile](https://devcenter.heroku.com/articles/procfile) in the app's root:
```shell
$ heroku container:login
echo "web: yarn workspace backend start --config ../../app-config.yaml --config ../../app-config.production.yaml" > Procfile
```
## Push and deploy a Docker image
## Database
Now we can push a Backstage [Docker image](docker.md) to Heroku's container
registry and release it to the `web` worker:
Provision a [Heroku Postgres](https://elements.heroku.com/addons/heroku-postgresql) database:
```bash
$ docker image build . -f packages/backend/Dockerfile --tag registry.heroku.com/<your-app>/web
$ docker push registry.heroku.com/<your-app>/web
$ heroku container:release web -a <your-app>
```shell
heroku addons:create heroku-postgresql -a <your-app>
```
Now you should have Backstage up and running! 🎉
Update `database` in `app-config.production.yaml`:
```yaml
backend:
database:
client: pg
pluginDivisionMode: schema
ensureExists: false
ensureSchemaExists: true
connection: ${DATABASE_URL}
```
Allow postgres self-signed certificates:
```shell
heroku config:set PGSSLMODE=no-verify -a <your-app>
```
## Deployment
Commit changes and push to Heroku to build and deploy:
```shell
git add Procfile && git commit -am "configure heroku"
git push heroku main
```
View the app in the browser:
```shell
heroku open -a <your-app>
```
View logs:
```shell
heroku logs -a <your-app>
```
## Docker
As an alternative to git deploys, Heroku also [supports container images](https://devcenter.heroku.com/articles/container-registry-and-runtime).
Login to Heroku's container registry:
```shell
heroku container:login
```
Configure the Heroku app to run a container image:
```shell
heroku stack:set container -a <your-app>
```
Locally run the [host build commands](https://backstage.io/docs/deployment/docker/#host-build), they must be run whenever you are going to publish a new image:
```shell
yarn install --frozen-lockfile
yarn tsc
yarn build:backend --config ../../app-config.yaml --config ../../app-config.production.yaml
```
Build, push, and release the container image to the `web` dyno:
```shell
docker image build . -f packages/backend/Dockerfile --tag registry.heroku.com/<your-app>/web
docker push registry.heroku.com/<your-app>/web
heroku container:release web -a <your-app>
```
+1 -1
View File
@@ -116,7 +116,7 @@ This makes it easier to create, find, and update documentation.
[TechDocs is now open source.](https://backstage.io/docs/features/techdocs/)
(See also:
"[Will Spotify's internal plugins be open sourced, too?](https://backstage.io/docs/faq/product#will-spotifys-internal-plugins-be-open-sourced-too)"
above)
above).
### Are you planning to have plugins baked into the repo? Or should they be developed in separate repos?
@@ -173,8 +173,7 @@ To create the Backend module, run `yarn new`, select `backend-module`. Then fill
This will create a new package at `plugins/kubernetes-backend-module-pinniped`. We are going to need also the `@backstage/plugin-kubernetes-node` and `@backstage/plugin-kubernetes-common` dependencies, the `@backstage/plugin-kubernetes-node` houses the [kubernetesAuthStrategyExtensionPoint](https://github.com/backstage/backstage/blob/ebe7afad9d19f279469168ca0d4feceb92c1ad36/plugins/kubernetes-node/src/extensions.ts#L77) and a [Pinniped Helper](https://github.com/backstage/backstage/blob/ebe7afad9d19f279469168ca0d4feceb92c1ad36/plugins/kubernetes-node/src/auth/PinnipedHelper.ts#L53) class.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd plugins/kubernetes-backend-module-pinniped add @backstage/plugin-kubernetes-node
yarn --cwd plugins/kubernetes-backend-module-pinniped add @backstage/plugin-kubernetes-common
```
+3 -6
View File
@@ -15,8 +15,7 @@ If you haven't setup Backstage already, read the
The first step is to add the Kubernetes frontend plugin to your Backstage
application.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-kubernetes
```
@@ -53,8 +52,7 @@ work.
Navigate to `packages/backend` of your Backstage app, and install the
`@backstage/plugin-kubernetes-backend` package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend
```
@@ -109,8 +107,7 @@ To get the Kubernetes plugin install using the New Backend System you will need
Run this command to add the package:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend
```
+2 -2
View File
@@ -60,7 +60,6 @@ This feature assumes your backstage instance has enabled the [permissions framew
A sample policy like:
```typescript
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
AuthorizeResult,
PolicyDecision,
@@ -68,12 +67,13 @@ import {
import {
PermissionPolicy,
PolicyQuery,
PolicyQueryUser,
} from '@backstage/plugin-permission-node';
class KubernetesDenyAllProxyEndpointPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
if (request.permission.name === 'kubernetes.proxy') {
return {
+2 -4
View File
@@ -16,8 +16,7 @@ If you haven't setup Backstage already, start
## Adding Search to the Frontend
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react
```
@@ -133,8 +132,7 @@ For more information about using `Root.tsx`, please see
Add the following plugins into your backend app:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-node
```
+7 -11
View File
@@ -396,21 +396,17 @@ Recently, the Backstage maintainers [announced the new Backend System](https://b
In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
```ts
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';
import { searchModuleElasticsearchEngine } from '@backstage/plugin-search-backend-module-elasticsearch/alpha';
import { searchModuleCatalogCollator } from '@backstage/plugin-search-backend-module-catalog/alpha';
import { searchModuleTechDocsCollator } from '@backstage/plugin-search-backend-module-techdocs/alpha';
import { searchModuleExploreCollator } from '@backstage/plugin-search-backend-module-explore/alpha';
const backend = createBackend();
// [1] adding the search plugin to the backend
backend.add(searchPlugin());
backend.add(import('@backstage/plugin-search-backend/alpha'));
// [2] (optional) the default search engine is Lunr, if you want to extend the search backend with another search engine.
backend.add(searchModuleElasticsearchEngine());
backend.add(
import('@backstage/plugin-search-backend-module-elasticsearch/alpha'),
);
// [3] extending search with collator modules to start index documents, take in optional schedule parameters.
backend.add(searchModuleCatalogCollator());
backend.add(searchModuleTechDocsCollator());
backend.add(searchModuleExploreCollator());
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-explore/alpha'));
backend.start();
```
@@ -47,7 +47,7 @@ action is logged for further investigation.
### Local File (`type: file`) Configurations
In addition to url locations, you can use the `file` location type to bring in content from the local file system. You should only use this for local development, test setups, and example data, not for production data.
You are also not able to use placeholders in them like `$text`. You can however reference other files relative to the current file. See the full [catalog example data set here](https://github.com/backstage/backstage/tree/master/packages/catalog-model/examples) for an extensive example.
You are also not able to use placeholders in them like `$text`, `$json` or `$yaml`. You can however reference other files relative to the current file. See the full [catalog example data set here](https://github.com/backstage/backstage/tree/master/packages/catalog-model/examples) for an extensive example.
Here is an example pulling in the `all.yaml` file from the examples folder. Note the use of `../../` to go up two levels from the current execution path of the backend. This is typically `packages/backend/`.
@@ -184,9 +184,8 @@ Catalog errors are published to the [events plugin](https://github.com/backstage
The first step is to add the events backend plugin to your Backstage application. Navigate to your Backstage application directory and add the plugin package.
```ts
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-events-node
```ts title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-events-backend
```
Now you can install the events backend plugin in your backend.
@@ -201,8 +200,7 @@ If you want to log catalog errors you can install the `@backstage/plugin-catalog
Install the catalog logs module.
```ts
# From your Backstage root directory
```ts title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-logs
```
@@ -84,6 +84,7 @@ import {
EntityProvider,
EntityProviderConnection,
} from '@backstage/plugin-catalog-node';
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
/**
* Provides entities from fictional frobs service.
@@ -92,11 +93,17 @@ export class FrobsProvider implements EntityProvider {
private readonly env: string;
private readonly reader: UrlReader;
private connection?: EntityProviderConnection;
private taskRunner: SchedulerServiceTaskRunner;
/** [1] */
constructor(env: string, reader: UrlReader) {
constructor(
env: string,
reader: UrlReader,
taskRunner: SchedulerServiceTaskRunner,
) {
this.env = env;
this.reader = reader;
this.taskRunner = taskRunner;
}
/** [2] */
@@ -107,6 +114,12 @@ export class FrobsProvider implements EntityProvider {
/** [3] */
async connect(connection: EntityProviderConnection): Promise<void> {
this.connection = connection;
this.taskRunner.run({
id: this.getProviderName(),
fn: async () => {
await this.run();
},
});
}
/** [4] */
@@ -248,24 +261,17 @@ export default async function createPlugin(
): Promise<Router> {
const builder = CatalogBuilder.create(env);
/* highlight-add-start */
const frobs = new FrobsProvider('production', env.reader);
const taskRunner = env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 10 },
});
const frobs = new FrobsProvider('production', env.reader, taskRunner);
builder.addEntityProvider(frobs);
/* highlight-add-end */
const { processingEngine, router } = await builder.build();
await processingEngine.start();
/* highlight-add-start */
await env.scheduler.scheduleTask({
id: 'run_frobs_refresh',
fn: async () => {
await frobs.run();
},
frequency: { minutes: 30 },
timeout: { minutes: 10 },
});
/* highlight-add-end */
// ..
}
```
@@ -300,9 +306,17 @@ export const catalogModuleFrobsProvider = createBackendModule({
deps: {
catalog: catalogProcessingExtensionPoint,
reader: coreServices.urlReader,
/* highlight-add-start */
scheduler: coreServices.scheduler,
/* highlight-add-end */
},
async init({ catalog, reader }) {
catalog.addEntityProvider(new FrobsProvider('dev', reader));
async init({ catalog, reader, scheduler }) {
const taskRunner = scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 10 },
});
const frobs = new FrobsProvider('dev', reader, taskRunner);
catalog.addEntityProvider(frobs);
},
});
},
@@ -318,6 +332,98 @@ backend.add(catalogModuleFrobsProvider);
backend.start();
```
#### Follow-up: Config Defined Schedule
If you want to go a step further and increase the configurability of your new `FrobsProvider`, you can define the schedule that the task runs at in `app-config.yaml` instead of requiring code changes to adjust.
```yaml title="app-config.yaml"
catalog:
providers:
frobs-provider:
schedule:
initialDelay: { seconds: 30 }
frequency: { hours: 1 }
timeout: { minutes: 50 }
```
This approach will also allow you to customize the schedule per environment. You can also [add a schema to your config](../../conf/defining.md).
#### New Backend
```ts title="packages/backend/src/index.ts"
import {
SchedulerServiceTaskScheduleDefinition,
/* highlight-add-start */
readSchedulerServiceTaskScheduleDefinitionFromConfig,
/* highlight-add-end */
} from '@backstage/backend-plugin-api';
export const catalogModuleFrobsProvider = createBackendModule({
pluginId: 'catalog',
moduleId: 'frobs-provider',
register(env) {
env.registerInit({
deps: {
// ... other deps
/* highlight-add-start */
rootConfig: coreServices.rootConfig,
/* highlight-add-end */
},
async init({ catalog, reader, scheduler, rootConfig }) {
/* highlight-add-start */
const config = rootConfig.getConfig('catalog.providers.frobs-provider'); // Generally, catalog config goes under catalog.providers.pluginId
// Add a default schedule if you don't define one in config.
const schedule = config.has('schedule')
? readSchedulerServiceTaskScheduleDefinitionFromConfig(
config.getConfig('schedule'),
)
: {
frequency: { minutes: 30 },
timeout: { minutes: 10 },
};
const taskRunner: SchedulerServiceTaskRunner =
scheduler.createScheduledTaskRunner(schedule);
/* highlight-add-end */
// rest of your code
},
});
},
});
```
#### Old Backend
```ts title="packages/backend/src/plugins/catalog.ts"
/* highlight-add-next-line */
import { FrobsProvider } from '../path/to/class';
import {
/* highlight-add-start */
readSchedulerServiceTaskScheduleDefinitionFromConfig,
/* highlight-add-end */
} from '@backstage/backend-plugin-api';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
/* highlight-add-start */
const config = env.config.getConfig('catalog.providers.frobs-provider'); // Generally, catalog config goes under catalog.providers.pluginId
// Add a default schedule if you don't define one in config.
const schedule = config.has('schedule')
? readSchedulerServiceTaskScheduleDefinitionFromConfig(
config.getConfig('schedule'),
)
: {
frequency: { minutes: 30 },
timeout: { minutes: 10 },
};
const taskRunner = env.scheduler.createScheduledTaskRunner(schedule);
/* highlight-add-end */
// ..
}
```
### Example User Entity Provider
If you have a 3rd party entity provider such as an internal HR system that you wish to use you are not limited to using our entity providers, (or simply wish to add to existing entity providers with your own data).
@@ -66,14 +66,14 @@ import {
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (
isPermission(request.permission, templateParameterReadPermission) ||
isPermission(request.permission, templateStepReadPermission)
) {
if (user?.identity.userEntityRef === 'user:default/spiderman')
if (user?.info.userEntityRef === 'user:default/spiderman')
return createScaffolderTemplateConditionalDecision(request.permission, {
not: scaffolderTemplateConditions.hasTag({ tag: 'secret' }),
});
@@ -109,11 +109,11 @@ import {
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, actionExecutePermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return createScaffolderActionConditionalDecision(request.permission, {
not: scaffolderActionConditions.hasActionId({
actionId: 'debug:log',
@@ -147,11 +147,11 @@ import {
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, actionExecutePermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return createScaffolderActionConditionalDecision(request.permission, {
not: {
allOf: [
@@ -190,25 +190,25 @@ import {
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-start */
if (isPermission(request.permission, taskCreatePermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
if (isPermission(request.permission, taskCancelPermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
}
}
if (isPermission(request.permission, taskReadPermission)) {
if (user?.identity.userEntityRef === 'user:default/spiderman') {
if (user?.info.userEntityRef === 'user:default/spiderman') {
return {
result: AuthorizeResult.ALLOW,
};
@@ -239,7 +239,6 @@ Instead of the changes in `permission.ts` noted in the above example you will ma
```ts title="packages/backend/src/index.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
PolicyDecision,
AuthorizeResult,
@@ -247,13 +246,14 @@ import {
import {
PermissionPolicy,
PolicyQuery,
PolicyQueryUser,
} from '@backstage/plugin-permission-node';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
class ExamplePermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
// Various scaffolder permission checks ...
@@ -24,8 +24,7 @@ There are also several modules available for various SCM tools:
Here's how to add an action module, first you need to run this command:
```sh
# From your Backstage root directory
```sh title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-github
```
@@ -26,6 +26,25 @@ parameters:
ui:help: 'Hint: additional description...'
```
#### Custom validation error message
```yaml
parameters:
- title: Fill in some steps
properties:
name:
title: Simple text input
type: string
description: Description about input
maxLength: 8
pattern: '^([a-zA-Z][a-zA-Z0-9]*)(-[a-zA-Z0-9]+)*$'
ui:autofocus: true
ui:help: 'Hint: additional description...'
errorMessage:
properties:
name: '1-8 alphanumeric tokens (first starts with letter) delimited by -'
```
### Multi line text input
```yaml
@@ -259,7 +278,11 @@ spec:
## Use placeholders to reference remote files
#### Note: testing of this functionality is not yet supported using _create/edit_
:::note
Testing of this functionality is not yet supported using _create/edit_. In addition, this functionality only works for remote files and not local files. You also cannot nest files.
:::
### template.yaml
@@ -145,6 +145,10 @@ When the action `handler` is called, we provide you a `context` as the only
argument. It looks like the following:
- `ctx.baseUrl` - a string where the template is located
- `ctx.checkpoint` - _Experimental_ allows to
implement [idempotency of the actions](https://github.com/backstage/backstage/tree/master/beps/0004-scaffolder-task-idempotency)
by not re-running the same function again if it was
executed successfully on the previous run.
- `ctx.logger` - a Winston logger for additional logging inside your action
- `ctx.logStream` - a stream version of the logger if needed
- `ctx.workspacePath` - a string of the working directory of the template run
@@ -153,7 +157,7 @@ argument. It looks like the following:
- `ctx.output` - a function which you can call to set outputs that match the
JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
- `createTemporaryDirectory` a function to call to give you a temporary
directory somewhere on the runner so you can store some files there rather
directory somewhere on the runner, so you can store some files there rather
than polluting the `workspacePath`
- `ctx.metadata` - an object containing a `name` field, indicating the template
name. More metadata fields may be added later.
@@ -191,7 +195,7 @@ const scaffolderModuleCustomExtensions = createBackendModule({
const backend = createBackend();
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
/* highlight-add-next-line */
backend.add(scaffolderModuleCustomExtensions());
backend.add(scaffolderModuleCustomExtensions);
```
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.
@@ -217,6 +221,28 @@ import {
})
```
### Using Checkpoints in Custom Actions (Experimental)
Idempotent action could be achieved via the usage of checkpoints.
Example:
```ts title="plugins/my-company-scaffolder-actions-plugin/src/vendor/my-custom-action.ts"
const res = await ctx.checkpoint?.('create.projects', async () => {
const projectStgId = createStagingProjectId();
const projectProId = createProductionProjectId();
return {
projectStgId,
projectProId,
};
});
```
You have to define the unique key in scope of the scaffolder task for your checkpoint. During the execution task engine
will check if the checkpoint with such key was already executed or not, if yes, and the run was successful, the callback
will be skipped and instead the stored value will be returned.
### Register Custom Actions with the Legacy Backend System
Once you have your Custom Action ready for usage with the scaffolder, you'll
@@ -533,6 +533,11 @@ catalogFilter:
metadata.annotations.github.com/team-slug: { exists: true }
```
#### Custom validation messages
You may specify custom JSON Schema validation messages as supported by the
[ajv-errors](https://github.com/ajv-validator/ajv-errors) plugin library to [ajv](https://github.com/ajv-validator/ajv).
## `spec.steps` - `Action[]`
The `steps` is an array of the things that you want to happen part of this
+3 -6
View File
@@ -21,8 +21,7 @@ The first step is to add the TechDocs plugin to your Backstage application.
Navigate to your new Backstage application directory. And then to your
`packages/app` directory, and install the `@backstage/plugin-techdocs` package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-techdocs
```
@@ -106,8 +105,7 @@ That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
Navigate to `packages/backend` of your Backstage app, and install the
`@backstage/plugin-techdocs-backend` package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-techdocs-backend
```
@@ -199,8 +197,7 @@ To install TechDocs when using the New Backend system you will need to do the fo
Navigate to `packages/backend` of your Backstage app, and install the `@backstage/plugin-techdocs-backend` package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-techdocs-backend
```
@@ -91,7 +91,9 @@ To create a new extension data reference to represent a type of shared extension
```ts
export const reactElementExtensionDataRef =
createExtensionDataRef<React.JSX.Element>('my-plugin.reactElement');
createExtensionDataRef<React.JSX.Element>().with({
id: 'my-plugin.reactElement',
});
```
The `ExtensionDataRef` can then be used to describe an output property of the extension. This will enforce typing on the return value of the extension factory:
@@ -337,7 +339,7 @@ Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallbac
### Analytics
Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`.
Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`. Whether an extension is routable is inferred from its outputs, but you can also explicitly control this behavior by passing the `routable` prop to `ExtensionBoundary`.
The `ExtensionBoundary` can be used like the following in an extension creator:
@@ -359,7 +361,7 @@ export function createSomeExtension<
path: config.path,
routeRef: options.routeRef,
element: (
<ExtensionBoundary node={node} routable>
<ExtensionBoundary node={node}>
<ExtensionComponent />
</ExtensionBoundary>
),
@@ -359,7 +359,7 @@ export const detailsSubRouteRef = createSubRouteRef({
Using subroutes in a page extension is as simple as this:
```tsx title="plugins/catalog/src/components/IndexPage.ts"
```tsx title="plugins/catalog/src/components/IndexPage.tsx"
import React from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';
import { useRouteRef } from '@backstage/frontend-plugin-api';
@@ -402,7 +402,7 @@ export const IndexPage = () => {
This is how you can get the parameters of a sub route URL:
```tsx title="plugins/catalog/src/components/DetailsPage.ts"
```tsx title="plugins/catalog/src/components/DetailsPage.tsx"
import React from 'react';
import { useParams } from 'react-router-dom';
@@ -426,7 +426,7 @@ export const DetailsPage = () => {
Finally, see how a plugin can provide subroutes:
```tsx title="plugins/catalog/src/plugin.ts"
```tsx title="plugins/catalog/src/plugin.tsx"
import React from 'react';
import {
createPlugin,
@@ -38,33 +38,31 @@ Note that while we use this naming pattern for the plugin instance this is only
| Description | Pattern | Examples |
| ----------- | ------------------------------- | ------------------------------------------------------------------- |
| Creator | `create<Kind>Extension` | `createPageExtension`, `createEntityCardExtension` |
| Blueprint | `<Kind>Blueprint` | `PageBlueprint`, `EntityCardBlueprint` |
| ID | `[<kind>:]<namespace>[/<name>]` | `'core.nav'`, `'page:user-settings'`, `'entity-card:catalog/about'` |
| Symbol | `<namespace>[<Name>][<Kind>]` | `coreNav`, `userSettingsPage`, `catalogAboutEntityCard` |
When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the extension creator function used to create the extension, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted.
When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the blueprint creator, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted.
Example:
```ts
// This is an extension creator that is used to create an extension of the 'page' kind.
export function createPageExtension(options) {
return createExtension({
kind: 'page', // Kinds are kebab-case
// ...options
});
}
// This is an extension blueprint that is used to create an extension of the 'page' kind.
export const PageBlueprint = createExtensionBlueprint({
kind: 'page',
// ...
});
// The namespace is inferred from the plugin ID, in this case 'catalog'
// The final ID for this extension will be 'page:catalog/entity'
const catalogEntityPage = createPageExtension({
const catalogEntityPage = PageBlueprint.make({
name: 'entity',
// ...
});
// The name is omitted, because the catalog plugin only provides a single extension of this kind
// The final ID for this extension will be 'search-result-list-item:catalog'
const catalogSearchResultListItem = createSearchResultListItemExtension({
const catalogSearchResultListItem = SearchResultListItemBlueprint.make({
// ...
});
@@ -100,9 +98,9 @@ export interface SearchResultItemExtensionData {
}
export const searchResultItemExtensionDataRef =
createExtensionDataRef<SearchResultItemExtensionData>(
'search.search-result-item',
);
createExtensionDataRef<SearchResultItemExtensionData>().with({
id: 'search.search-result-item',
});
```
#### Grouped Extension Data
@@ -111,8 +109,12 @@ This way of defining extension data is similar to the standalone way, but it use
```ts
export const coreExtensionData = {
reactElement: createExtensionDataRef<ReactElement>('core.react-element'),
routePath: createExtensionDataRef<string>('core.route-path'),
reactElement: createExtensionDataRef<ReactElement>().with({
id: 'core.react-element',
}),
routePath: createExtensionDataRef<string>().with({
id: 'core.route-path',
}),
};
```
@@ -127,9 +129,9 @@ export function createGraphiQLEndpointExtension(options) {
// Use a TypeScript namespace to merge the extension data references with the extension creator
export namespace createGraphiQLEndpointExtension {
export const endpointDataRef = createExtensionDataRef</* ... */>(
'graphiql.graphiql-endpoint.endpoint',
);
export const endpointDataRef = createExtensionDataRef</* ... */>().with({
id: 'graphiql.graphiql-endpoint.endpoint',
});
}
```
@@ -45,6 +45,7 @@ This extension is the first extension attached to the extension tree. It is resp
| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) |
| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) |
| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) |
| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) |
#### Default theme extensions
@@ -314,6 +314,31 @@ const app = createApp({
});
```
### `icons`
Icons are now installed as extensions, using the `IconBundleBlueprint` to make new instances which can be added to the app.
```ts
import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
const exampleIconBundle = IconBundleBlueprint.make({
name: 'example-bundle',
params: {
icons: {
user: MyOwnUserIcon,
},
},
});
const app = createApp({
features: [
createExtensionOverrides({
extensions: [exampleIconBundle],
}),
],
});
```
### `bindRoutes`
Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information.
@@ -38,6 +38,10 @@ Sign-in page extension have a single purpose - to implement a custom sign-in pag
Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use.
### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md)
Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app.
### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md)
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.
+1 -1
View File
@@ -64,7 +64,7 @@ const app = createApp({
})
```
Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `themes.light` and `themes.light` from [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme).
Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `themes.light` and `themes.dark` from [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme).
## Example of a custom theme
+1 -2
View File
@@ -73,8 +73,7 @@ to install and configure the client.
Go to the root directory of your freshly installed Backstage
App. Run the following to install the PostgreSQL client into your backend:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add pg
```
@@ -21,8 +21,7 @@ to an entity in the software catalog.
1. Add the plugin's npm package to the repo:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @circleci/backstage-plugin
```
+1 -2
View File
@@ -28,8 +28,7 @@ Now, let's get started by installing the home plugin and creating a simple homep
#### 1. Install the plugin
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/app add @backstage/plugin-home
```
@@ -63,18 +63,16 @@ When a given dependency version is the _same_ between different packages, the
dependency is hoisted to the main `node_modules` folder in the monorepo root to
be shared between packages. When _different_ versions of the same dependency are
encountered, Yarn creates a `node_modules` folder within a particular package.
This can lead to multiple versions of the same package being installed and used
in the same app.
This can lead to confusing situations with type definitions, or anything with
global state. React [Context](https://reactjs.org/docs/context.html), for
example, depends on global referential equality. This can cause problems in
Backstage with API lookup, or config loading.
All Backstage core packages are implemented in such as way that package
duplication is **not** a problem. For example, duplicate installations of
packages like `@backstage/core-plugin-api`, `@backstage/core-components`,
`@backstage/plugin-catalog-react`, and `@backstage/backend-plugin-api` are all
acceptable.
To help resolve these situations, the Backstage CLI has
[versions:check](https://backstage.io/docs/tooling/cli/03-commands#versionscheck). This
will validate versions of `@backstage` packages in your app to check for
duplicate definitions:
```bash
# Add --fix to attempt automatic resolution in yarn.lock
yarn backstage-cli versions:check
```
While package duplication might be acceptable in many cases, you might want to
deduplicate packages for the purpose of optimizing bundle size and installation
speed. We recommend using deduplication utilities such as `yarn dedupe` to trim
down the number of duplicate packages.
+2
View File
@@ -20,6 +20,8 @@ Run your Backstage app with `yarn dev`. Navigate to `http://localhost:3000`.
If you're not already logged in, you should see a login screen like this,
![Screenshot of the login screen](../assets/getting-started/login-screen.png)
To login, you should choose the "Github" provider and click the "Sign in" button. This will redirect you to a Github OAuth page. Verify that the scopes mentioned on that page match the setup you did in [the authentication tutorial](./config/authentication.md). Once you click "Confirm", you will be brought back to the Backstage interface and signed in!
If you are already logged in, you will be automatically brought to your Backstage instance.
+1 -2
View File
@@ -66,8 +66,7 @@ catalog:
As this provider is not one of the default providers, you will first need to install
the AWS catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-aws
```
+1 -2
View File
@@ -66,8 +66,7 @@ catalog:
As this provider is not one of the default providers, you will first need to install
the AWS catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-aws
```
+1 -2
View File
@@ -102,8 +102,7 @@ It may take some time before the branch is indexed and searchable.
As this provider is not one of the default providers, you will first need to install
the Azure catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
+1 -2
View File
@@ -102,8 +102,7 @@ It may take some time before the branch is indexed and searchable.
As this provider is not one of the default providers, you will first need to install
the Azure catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure
```
+1 -2
View File
@@ -18,8 +18,7 @@ Microsoft Graph API.
The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph
```
+45 -3
View File
@@ -18,8 +18,7 @@ Microsoft Graph API.
The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph
```
@@ -112,6 +111,17 @@ microsoftGraphOrg:
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
If you don't want to only ingest groups matching the `search` and/or `filter` query, but also the groups which are members of the matched groups, you can use the `includeSubGroups` configuration:
```yaml
microsoftGraphOrg:
providerId:
group:
filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified')
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
includeSubGroups: true
```
In addition to these groups, one additional group will be created for your organization.
All imported groups will be a child of this group.
@@ -181,6 +191,23 @@ microsoftGraphOrg:
select: ['id', 'displayName', 'description']
```
### Using Provider Config Transformer
Dynamic configuration scaling allows the `msgraph` catalog plugin to adjust its settings at runtime without requiring a redeploy. This feature is useful for scenarios where configuration needs to be updated based on real-time events or changing conditions. For example, you can dynamically adjust synchronization schedules, filters, and search parameters to optimize performance and responsiveness.
:::note
Adjusting fields that are not used on each scheduled ingestion (e.g., `id`, `schedule`) will have no effect.
:::
:::warning
Dynamically changing configuration on the fly can introduce unintended consequences, such as system instability and configuration errors. Please review your transformer carefully to ensure that it is working as anticipated!
:::
#### Example Use Cases:
- **Filter Scaling**: Adjust filters like `userGroupMember` and `groupFilter` dynamically.
- **Search Parameter Adjustment**: Change search parameters such as `groupSearch` and `userSelect` on-the-fly.
### Using Custom Transformers
Transformers can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`. Here is an example:
@@ -192,6 +219,7 @@ import {
myUserTransformer,
myGroupTransformer,
myOrganizationTransformer,
myProviderConfigTransformer,
} from './transformers';
backend.add(
@@ -213,6 +241,9 @@ backend.add(
microsoftGraphTransformers.setOrganizationTransformer(
myOrganizationTransformer,
);
microsoftGraphTransformers.setProviderConfigTransformer(
myProviderConfigTransformer,
);
/* highlight-add-end */
},
});
@@ -221,7 +252,7 @@ backend.add(
);
```
The `myUserTransformer`, `myGroupTransformer`, and `myOrganizationTransformer` transformer functions are from the examples in the section below.
The `myUserTransformer`, `myGroupTransformer`, `myOrganizationTransformer`, and `myProviderConfigTransformer` transformer functions are from the examples in the section below.
### Transformer Examples
@@ -233,6 +264,7 @@ import {
defaultGroupTransformer,
defaultUserTransformer,
defaultOrganizationTransformer,
MicrosoftGraphProviderConfig,
} from '@backstage/plugin-catalog-backend-module-msgraph';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
@@ -275,6 +307,16 @@ export async function myOrganizationTransformer(
): Promise<GroupEntity | undefined> {
return undefined;
}
// Example config transformer that expands the group filter to also include 'azure-group-a'
export async function myProviderConfigTransformer(
provider: MicrosoftGraphProviderConfig,
): Promise<MicrosoftGraphProviderConfig> {
if (!provider.groupFilter?.includes('azure-group-a')) {
provider.groupFilter = `${provider.groupFilter} or displayName eq 'azure-group-a'`;
}
return provider;
}
```
## Troubleshooting
@@ -19,8 +19,7 @@ backend. The provider is not installed by default, therefore you have to add a
dependency to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` to your backend
package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-cloud
```
@@ -23,7 +23,6 @@ backend. The provider is not installed by default, therefore you have to add a
dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend package.
```bash title="From your Backstage root directory"
# From your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server
```
+1 -2
View File
@@ -16,8 +16,7 @@ stored in the root of the matching projects.
As this provider is not one of the default providers, you will first need to install
the Gerrit provider plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gerrit
```
+2 -4
View File
@@ -25,8 +25,7 @@ backend. They are not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
@@ -273,8 +272,7 @@ backend. They are not installed by default, therefore you have to add a
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package, plus `@backstage/integration` for the basic credentials management:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github
```
+13 -5
View File
@@ -24,8 +24,7 @@ You will have to add the GitHub Entity provider to your backend as it is not ins
dependency on `@backstage/plugin-catalog-backend-module-github` to your backend
package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
@@ -40,8 +39,9 @@ backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha'));
## Events Support
The catalog module for GitHub comes with events support enabled.
This will make it subscribe to its relevant topics (`github.push`)
and expects these events to be published via the `EventsService`.
This will make it subscribe to its relevant topics (`github.push`,
`github.repository`) and expects these events to be published
via the `EventsService`.
Additionally, you should install the
[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md)
@@ -55,7 +55,15 @@ You can decide between the following options (extensible):
- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md)
- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md)
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events.
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
The webhook(s) will need to be configured to react to `push` and
`repository` events.
Certain actions like `transferred` by the `repository` event type
will not be supported when you use repository webhooks.
Please check the GitHubs documentation for these event types and
its actions.
## Configuration
+2 -2
View File
@@ -109,8 +109,8 @@ integrations:
webhookSecret: ${AUTH_ORG_WEBHOOK_SECRET}
```
:::Note
Note that in both examples above `apps` is an array which means you can add multiple GitHub Apps using `$include` or environment variables as long as they are each for a different GitHub Org as mentioned under the [Caveats](#caveats) section
:::note
Note that in both examples above `apps` is an array which means you can add multiple GitHub Apps using `$include` or environment variables as long as they are each for a different GitHub Org as mentioned under the [Caveats](#caveats) section.
:::
## Limiting the GitHub App installations
+2 -4
View File
@@ -29,8 +29,7 @@ the Processor method (not recommended), it is described separately below.
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-github` to your backend package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
@@ -334,8 +333,7 @@ frequency with which they are refreshed, separately from other processors.
The `GithubOrgReaderProcessor` is not registered by default, so you have to
install and register it in the catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github
```
+1 -2
View File
@@ -38,8 +38,7 @@ You will have to add the GitHub Org provider to your backend as it is not instal
dependency on `@backstage/plugin-catalog-backend-module-github-org` to your backend
package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github-org
```
+1 -2
View File
@@ -20,8 +20,7 @@ This provider can also be configured to ingest GitLab data based on [GitLab Webh
As this provider is not one of the default providers, you will first need to install
the gitlab catalog plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab
```
+1 -2
View File
@@ -25,8 +25,7 @@ This provider can also be configured to ingest GitLab data based on [GitLab Syst
As this provider is not one of the default providers, you will first need to install the Gitlab provider plugin:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab @backstage/plugin-catalog-backend-module-gitlab-org
```
+1 -2
View File
@@ -24,8 +24,7 @@ the Processor method (not recommended), it is described separately below.
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
```
+9 -2
View File
@@ -21,8 +21,7 @@ Backstage in general supports OpenLDAP compatible vendors, as well as Active Dir
The provider is not installed by default, therefore you have to add a dependency
to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
```
@@ -86,6 +85,14 @@ catalog:
These config blocks have a lot of options in them, so we will describe each
"root" key within the block separately.
> NOTE:
>
> If you want to import users and groups from different LDAP servers, you can define multiple providers with different names.
> If they should come from the same server, you can define multiple users and groups blocks within the same provider using an array of users / groups.
> Entries coming from the same block will be able to detect group memberships based on the `memberOf` attribute.
>
> If you want only to import users or groups, you can omit the groups or users block.
### target
This is the URL of the targeted server, typically on the form
+2
View File
@@ -37,6 +37,8 @@ The operator is ultimately responsible for auditing usage of internal and extern
The operator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
The built-in protection against unauthorized access does not by default include protection of the frontend bundle. The frontend bundle includes all the code of your frontend plugins and code in minified form, as well as any other frontend resources like images, fonts, etc. If this is a concern, you can use the [experimental public entry point](https://backstage.io/docs/tutorials/enable-public-entry/) to create two separate frontend builds, where authenticated users only have access to the full one.
## Common Backend Configuration
There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected.
+4 -5
View File
@@ -66,9 +66,8 @@ import { catalogConditions, createCatalogConditionalDecision, createCatalogPermi
/* highlight-remove-next-line */
import { createConditionFactory } from '@backstage/plugin-permission-node';
/* highlight-add-next-line */
import { PermissionPolicy, PolicyQuery, createConditionFactory } from '@backstage/plugin-permission-node';
import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node';
/* highlight-add-start */
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common';
/* highlight-add-end */
...
@@ -102,21 +101,21 @@ const isInSystem = createConditionFactory(isInSystemRule);
class TestPermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
if (isResourcePermission(request.permission, 'catalog-entity')) {
return createCatalogConditionalDecision(
request.permission,
/* highlight-remove-start */
catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
claims: user?.info.ownershipEntityRefs ?? [],
}),
/* highlight-remove-end */
/* highlight-add-start */
{
anyOf: [
catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
claims: user?.info.ownershipEntityRefs ?? [],
}),
isInSystem({ systemRef: 'interviewing' }),
],
+1 -2
View File
@@ -50,8 +50,7 @@ The permissions framework uses a new `permission-backend` plugin to accept autho
1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @backstage/plugin-permission-backend
```
+1 -2
View File
@@ -39,8 +39,7 @@ The source code is available here:
2. Add these packages as dependencies for your Backstage app:
```sh
# From your Backstage root directory
```sh title="From your Backstage root directory"
yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common
yarn --cwd packages/app add @internal/plugin-todo-list
```
@@ -169,15 +169,12 @@ Before running this step, please make sure you followed the steps described in [
In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission:
```ts title="packages/backend/src/plugins/permission.ts"
/* highlight-add-start */
import {
BackstageIdentityResponse,
} from '@backstage/plugin-auth-node';
/* highlight-add-end */
import {
PermissionPolicy,
/* highlight-add-next-line */
/* highlight-add-start */
PolicyQuery,
PolicyQueryUser,
/* highlight-add-end */
} from '@backstage/plugin-permission-node';
/* highlight-add-start */
import { isPermission } from '@backstage/plugin-permission-common';
@@ -190,7 +187,7 @@ class TestPermissionPolicy implements PermissionPolicy {
/* highlight-add-start */
async handle(
request: PolicyQuery,
_user?: BackstageIdentityResponse,
_user?: PolicyQueryUser,
): Promise<PolicyDecision> {
if (isPermission(request.permission, todoListCreatePermission)) {
return {
@@ -237,12 +237,12 @@ Let's go back to the permission policy's handle function and try to authorize ou
```ts title="packages/backend/src/plugins/permission.ts"
import {
BackstageIdentityResponse,
IdentityClient
} from '@backstage/plugin-auth-node';
import {
PermissionPolicy,
PolicyQuery,
PolicyQueryUser,
} from '@backstage/plugin-permission-node';
import { isPermission } from '@backstage/plugin-permission-common';
/* highlight-remove-next-line */
@@ -262,9 +262,9 @@ import {
async handle(
request: PolicyQuery,
/* highlight-remove-next-line */
_user?: BackstageIdentityResponse,
_user?: PolicyQueryUser,
/* highlight-add-next-line */
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
if (isPermission(request.permission, todoListCreatePermission)) {
return {
@@ -276,7 +276,7 @@ async handle(
return createTodoListConditionalDecision(
request.permission,
todoListConditions.isOwner({
userId: user?.identity.userEntityRef ?? '',
userId: user?.info.userEntityRef ?? '',
}),
);
}
+9 -14
View File
@@ -10,7 +10,10 @@ That policy looked like this:
```typescript title="packages/backend/src/plugins/permission.ts"
class TestPermissionPolicy implements PermissionPolicy {
async handle(request: PolicyQuery): Promise<PolicyDecision> {
async handle(
request: PolicyQuery,
_user?: PolicyQueryUser,
): Promise<PolicyDecision> {
if (request.permission.name === 'catalog.entity.delete') {
return {
result: AuthorizeResult.DENY,
@@ -35,14 +38,6 @@ As we confirmed in the previous section, we know that this now prevents us from
Let's change the policy to the following:
```ts
/* highlight-remove-next-line */
import { IdentityClient } from '@backstage/plugin-auth-node';
/* highlight-add-start */
import {
BackstageIdentityResponse,
IdentityClient
} from '@backstage/plugin-auth-node';
/* highlight-add-end */
import {
AuthorizeResult,
PolicyDecision,
@@ -65,7 +60,7 @@ class TestPermissionPolicy implements PermissionPolicy {
/* highlight-add-start */
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-add-end */
/* highlight-remove-next-line */
@@ -81,7 +76,7 @@ class TestPermissionPolicy implements PermissionPolicy {
return createCatalogConditionalDecision(
request.permission,
catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
claims: user?.info.ownershipEntityRefs ?? [],
}),
);
/* highlight-add-end */
@@ -95,7 +90,7 @@ Let's walk through the new code that we just added.
Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement.
The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `BackstageIdentityResponse` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous.
The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous.
You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities!
@@ -125,7 +120,7 @@ import {
class TestPermissionPolicy implements PermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
user?: PolicyQueryUser,
): Promise<PolicyDecision> {
/* highlight-remove-next-line */
if (isPermission(request.permission, catalogEntityDeletePermission)) {
@@ -134,7 +129,7 @@ class TestPermissionPolicy implements PermissionPolicy {
return createCatalogConditionalDecision(
request.permission,
catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
claims: user?.info.ownershipEntityRefs ?? [],
}),
);
}
+1 -2
View File
@@ -71,8 +71,7 @@ Backstage application / backend exposes it.
To actually attach and run the plugin router, you will make some modifications
to your backend.
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add @internal/plugin-carmen-backend@^0.1.0 # Change this to match the plugin's package.json
```
+1 -5
View File
@@ -24,7 +24,7 @@ Running an individual test (e.g. `MyComponent.test.tsx`):
To run both `MyComponent.test.tsx` and `MyControl.test.tsx` suite of tests:
yarn test MyCo
yarn test MyComponent MyControl
:::note Note
@@ -52,10 +52,6 @@ We use the light-weight
[react-testing-library](https://github.com/kentcdodds/react-testing-library) to
render React components.
## Testing Utilities
TODO.
## Writing Unit Tests
The following principles are good guides for determining if you are writing high
+1 -1
View File
@@ -75,7 +75,7 @@ Given one or more PRs towards master that we want to create a patch release for,
./scripts/patch-release-for-pr.js <pr-number> <pr-number-2> ...
```
Wait until the script has finished executing, at the end of the output you will find a link of the format `https://github.com/backstage/backstage/compare/patch/...`. Open this link in your browser to create a PR for the patch release. Finish the sentence "This release fixes an issue where..." and create the PR.
Wait until the script has finished executing, at the end of the output you will find a link of the format `https://github.com/backstage/backstage/pull/new/patch-release-pr-...`. Open this link in your browser to create a PR for the patch release. Finish the sentence "This release fixes an issue where..." and create the PR.
Once the PR has been approved and merged, the patch release will be automatically created. The patch release is complete when a notification has been posted to Discord in the `#announcements` channel. Keep an eye on "Deploy Packages" workflow and re-trigger if it fails. It is safe to re-trigger any part of this workflow, including the release step.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+446
View File
@@ -0,0 +1,446 @@
# Release v1.29.0-next.2
Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.29.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.29.0-next.2)
## @backstage/app-defaults@1.5.8-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/core-compat-api@0.2.7-next.1
### Patch Changes
- Updated dependencies
- @backstage/frontend-plugin-api@0.6.7-next.1
## @backstage/core-components@0.14.9-next.1
### Patch Changes
- 99d672d: Modified the `Select` component to take in a `data-testid` parameter ensuring backwards compatibility with default value corresponding to previously hardcoded `data-testid` of "select".
## @backstage/create-app@0.5.17-next.2
### Patch Changes
- e90a2cd: Added the Catalog logs module to the `create-app` template
## @backstage/dev-utils@1.0.35-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/app-defaults@1.5.8-next.2
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog-react@1.12.2-next.2
## @backstage/frontend-app-api@0.7.3-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
## @backstage/frontend-plugin-api@0.6.7-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/frontend-test-utils@0.1.10-next.2
### Patch Changes
- Updated dependencies
- @backstage/frontend-app-api@0.7.3-next.2
- @backstage/frontend-plugin-api@0.6.7-next.1
## @backstage/plugin-api-docs@0.11.7-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog@1.21.1-next.2
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-app-visualizer@0.1.8-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
## @backstage/plugin-auth-react@0.1.4-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-catalog@1.21.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-catalog-graph@0.4.7-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-catalog-import@0.12.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-catalog-react@1.12.2-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
## @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-config-schema@0.1.57-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-devtools@0.1.16-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-home@0.7.7-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-home-react@0.1.15-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-home-react@0.1.15-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-kubernetes@0.11.12-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-kubernetes-react@0.4.1-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
## @backstage/plugin-kubernetes-cluster@0.0.13-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-kubernetes-react@0.4.1-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
## @backstage/plugin-kubernetes-react@0.4.1-next.1
### Patch Changes
- e3cb6ab: Add a namespace label to RolloutDrawer
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-notifications@0.2.3-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-org@0.6.27-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-org-react@0.1.26-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
## @backstage/plugin-scaffolder@1.22.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-scaffolder-react@1.10.0-next.2
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-scaffolder-backend@1.23.0-next.2
### Patch Changes
- ff1bb4c: Added a documentation how to use checkpoints
## @backstage/plugin-scaffolder-react@1.10.0-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
## @backstage/plugin-search@1.4.14-next.2
### Patch Changes
- 1117aba: Update deps in search api extension to include fetch api
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-search-react@1.7.13-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
## @backstage/plugin-signals@0.0.8-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-techdocs@1.10.7-next.2
### Patch Changes
- 6fa652c: Improve default sorting of docs table
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-auth-react@0.1.4-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/plugin-techdocs-react@1.2.6-next.1
- @backstage/core-compat-api@0.2.7-next.1
## @backstage/plugin-techdocs-addons-test-utils@1.0.35-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-techdocs@1.10.7-next.2
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog@1.21.1-next.2
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/plugin-techdocs-react@1.2.6-next.1
## @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-techdocs-react@1.2.6-next.1
## @backstage/plugin-techdocs-react@1.2.6-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
## @backstage/plugin-user-settings@0.8.9-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/core-compat-api@0.2.7-next.1
## example-app@0.2.99-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-techdocs@1.10.7-next.2
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-search@1.4.14-next.2
- @backstage/app-defaults@1.5.8-next.2
- @backstage/cli@0.26.11-next.1
- @backstage/frontend-app-api@0.7.3-next.2
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-api-docs@0.11.7-next.2
- @backstage/plugin-auth-react@0.1.4-next.1
- @backstage/plugin-catalog@1.21.1-next.2
- @backstage/plugin-catalog-graph@0.4.7-next.2
- @backstage/plugin-catalog-import@0.12.1-next.2
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1
- @backstage/plugin-devtools@0.1.16-next.1
- @backstage/plugin-home@0.7.7-next.2
- @backstage/plugin-kubernetes@0.11.12-next.2
- @backstage/plugin-kubernetes-cluster@0.0.13-next.2
- @backstage/plugin-notifications@0.2.3-next.2
- @backstage/plugin-org@0.6.27-next.2
- @backstage/plugin-scaffolder@1.22.1-next.2
- @backstage/plugin-scaffolder-react@1.10.0-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/plugin-signals@0.0.8-next.1
- @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1
- @backstage/plugin-techdocs-react@1.2.6-next.1
- @backstage/plugin-user-settings@0.8.9-next.2
## example-app-next@0.0.13-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-techdocs@1.10.7-next.2
- @backstage/core-components@0.14.9-next.1
- @backstage/plugin-search@1.4.14-next.2
- @backstage/app-defaults@1.5.8-next.2
- @backstage/cli@0.26.11-next.1
- @backstage/frontend-app-api@0.7.3-next.2
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-api-docs@0.11.7-next.2
- @backstage/plugin-app-visualizer@0.1.8-next.1
- @backstage/plugin-auth-react@0.1.4-next.1
- @backstage/plugin-catalog@1.21.1-next.2
- @backstage/plugin-catalog-graph@0.4.7-next.2
- @backstage/plugin-catalog-import@0.12.1-next.2
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1
- @backstage/plugin-home@0.7.7-next.2
- @backstage/plugin-kubernetes@0.11.12-next.2
- @backstage/plugin-kubernetes-cluster@0.0.13-next.2
- @backstage/plugin-notifications@0.2.3-next.2
- @backstage/plugin-org@0.6.27-next.2
- @backstage/plugin-scaffolder@1.22.1-next.2
- @backstage/plugin-scaffolder-react@1.10.0-next.2
- @backstage/plugin-search-react@1.7.13-next.1
- @backstage/plugin-signals@0.0.8-next.1
- @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1
- @backstage/plugin-techdocs-react@1.2.6-next.1
- @backstage/plugin-user-settings@0.8.9-next.2
- @backstage/core-compat-api@0.2.7-next.1
## app-next-example-plugin@0.0.13-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
## example-backend@0.0.28-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-scaffolder-backend@1.23.0-next.2
## example-backend-legacy@0.2.100-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-scaffolder-backend@1.23.0-next.2
- example-app@0.2.99-next.2
## e2e-test@0.2.18-next.2
### Patch Changes
- Updated dependencies
- @backstage/create-app@0.5.17-next.2
## techdocs-cli-embedded-app@0.2.98-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-techdocs@1.10.7-next.2
- @backstage/core-components@0.14.9-next.1
- @backstage/app-defaults@1.5.8-next.2
- @backstage/cli@0.26.11-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog@1.21.1-next.2
- @backstage/plugin-techdocs-react@1.2.6-next.1
## @internal/plugin-todo-list@1.0.29-next.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
+117
View File
@@ -0,0 +1,117 @@
---
id: v1.29.0
title: v1.29.0
description: Backstage Release v1.29.0
---
These are the release notes for the v1.29.0 release of [Backstage](https://backstage.io/).
A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done.
## Highlights
### BREAKING: Backend System deprecations and removals
As part of the [work towards a stable 1.0 release of the new backend system](https://github.com/backstage/backstage/issues/24493), there are several new deprecations and breaking changes in the backend system packages:
**Breaking**:
- The deprecated `token` option has been removed from `PermissionsService`, and the request options are now required and must contain a `credentials` object.
- The deprecated `getPath` option has been removed from `httpRouterServiceFactory`, the plugin paths are now always `/api/<pluginId>`.
- It is no longer possible to pass service factory callbacks to the `defaultServiceFactories` option of `createSpecializedBackend`.
**Deprecations**:
- The ability to define options for service factories through `createServiceFactory` has been deprecated. See the [service architecture documentation](https://backstage.io/docs/backend-system/architecture/services) for more information on how to define customizable services.
- The ability to install backend features in callback form, i.e. `() => BackendFeature`, has been deprecated. This also includes other usages such as `startTestBackend`, and dynamically imported backend features. No manual changes should be needed for this change, as all backend feature creators have been updated to return `BackendFeature` instances directly.
- The `ServiceFactoryTest.get` method has been renamed to `ServiceFactoryTest.getSubject`, and is now deprecated.
- The following types have been renamed to use an `*Options` suffix instead: `ServiceRefConfig`, `RootServiceFactoryConfig`, `PluginServiceFactoryConfig`.
- Deprecated all exports related to the legacy status checker in `@backstage/backend-common`.
- The `isDockerDisabledForTests` function exported by `@backstage/backend-test-utils` has been deprecated.
### Backend Health Service
A new health service as been added to the new backend system. This service provides health check endpoints for the backend, and replaces `createStatusCheckRouter` from `@backstage/backend-common`.
The service helps implement the new `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness` endpoints, which provide health checks for the entire backend instance.
You can read more about this new service and how to customize it in the [Root Health Service documentation](https://backstage.io/docs/backend-system/core-services/root-health).
### New Catalog Logs module
This new `@backstage/plugin-catalog-backend-module-logs` module is a minimal module that will log any error events that are published by the catalog. This module is useful for making sure that catalog errors are visible in the logs, but you may want to replace it with a more customized solution if the resulting logs are too verbose.
### Updates to the `@backstage/create-app` template
New backstage projects created with `@backstage/create-app` will now include the Catalog Logs module for logging catalog error events, as well as support for the Postgres Search Engine.
### Permission Policy deprecations
The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information:
- `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials.
- `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant.
Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`.
### Renaming the `setupRequestMockHandlers` test utility
The `setupRequestMockHandlers` utility function exported by `@backstage/test-utils` and `@backstage/backend-test-utils` has been renamed to `registerMswTestHooks`. This is done to better reflect the context and the purpose of the function. The old name is deprecated and will be removed in a future release.
### Catalog GitHub module support for `repository` events
The GitHub provider module and `GithubEntityProvider` for the Catalog now supports event driven ingestion of repositories by subscribing to `repository` events from GitHub. This includes the actions `archived`, `deleted`, `edited`, `renamed`, `transferred`, and `unarchived`. This is in addition to the existing support for `push` events, which you can read more about in the integration documentation for [GitHub Discovery](https://backstage.io/docs/integrations/github/discovery#events-support).
Contributed by [@pjungermann](https://github.com/pjungermann) in [#25360](https://github.com/backstage/backstage/pull/25360)
### Catalog i18n support
The Catalog plugin as well as the Catalog React library now support internationalization (i18n). This means that you can customize the messaging in the catalog, as well as add translations. You can read more about this in the [i18n documentation](https://backstage.io/docs/plugins/internationalization/).
Contributed by [@mario-mui](https://github.com/mario-mui) in [#23392](https://github.com/backstage/backstage/pull/23392)
### Route Binding configuration improvements
It is now possible to explicitly remove default route bindings, for cases where you don't want a plugin route to be bound to any target at all:
```yaml
app:
routes:
bindings:
# This has the effect of removing the button for registering new
# catalog entities in the scaffolder template list view
scaffolder.registerComponent: false
```
### Scaffolder Fields performance improvements
The `EntityPicker` and `MultiEntityPicker` fields have been updated to improve performance with large catalogs. Contributed by [@kmikko](https://github.com/kmikko) in [#25315](https://github.com/backstage/backstage/pull/25315), [#25380](https://github.com/backstage/backstage/pull/25380)
### BREAKING: Catalog LDAP Module improvements
The `@backstage/plugin-catalog-backend-module-ldap` module has been improved to support multiple or no declarations of both user and group configs.
This change is breaking for `readLdapOrg` and `LdapProviderConfig`, which now both always accept arrays of `users` and `groups` configurations.
Contributed by [@Jenson3210](https://github.com/Jenson3210) in [#25261](https://github.com/backstage/backstage/pull/25261)
## Security Fixes
This release does not contain any security fixes.
## Upgrade path
We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated).
## Links and References
Below you can find a list of links and references to help you learn about and start using this new release.
- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/)
- [GitHub repository](https://github.com/backstage/backstage)
- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy)
- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support
- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.29.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.
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -248,8 +248,7 @@
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn add --cwd packages/app @backstage/plugin-azure-devops
```
@@ -313,8 +312,7 @@
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn add --cwd packages/app @backstage/plugin-azure-devops
```
+2 -4
View File
@@ -10,8 +10,7 @@
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn add --cwd packages/app @backstage/plugin-azure-devops
```
@@ -69,8 +68,7 @@
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn add --cwd packages/app @backstage/plugin-azure-devops
```
+1 -20
View File
@@ -26,7 +26,6 @@ repo [command] Command that run across an entire
package [command] Lifecycle scripts for individual packages
migrate [command] Migration utilities
versions:bump [options] Bump Backstage packages to the latest versions
versions:check [options] Check Backstage package versioning
clean Delete cache directories [DEPRECATED]
build-workspace <workspace-dir> [packages...] Builds a temporary dist workspace from the provided
packages
@@ -327,8 +326,7 @@ Options:
## versions\:bump
Bump all `@backstage` packages to the latest versions. This checks for updates
in the package registry, and will update entries both in `yarn.lock` and
`package.json` files when necessary.
in the package registry, and will update entries `package.json` files when necessary.
```text
Usage: backstage-cli versions:bump [options]
@@ -339,23 +337,6 @@ Options:
--release <version|next|main> Bump to a specific Backstage release line or version (default: "main")
```
## versions\:check
Validate `@backstage` dependencies within the repo, making sure that there are
no duplicates of packages that might lead to breakages.
By supplying the `--fix` flag the command will attempt to fix any conflict that
can be resolved by editing `yarn.lock`, but will not attempt to search for
remote updates or modify any `package.json` files.
```text
Usage: backstage-cli versions:check [options]
Options:
--fix Fix any auto-fixable versioning problems
-h, --help display help for command
```
## build-workspace
Builds a mirror of the workspace using the packaged production version of each
@@ -36,12 +36,15 @@ Please ensure the appropriate database drivers are installed in your `backend`
package. If you intend to use both PostgreSQL and SQLite, you can install
both of them.
```bash
# From your Backstage root directory
# install pg if you need PostgreSQL
yarn --cwd packages/backend add pg
Install pg if you need PostgreSQL:
# install SQLite 3 if you intend to set it as the client
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add pg
```
Install SQLite 3 if you intend to set it as the client:
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add better-sqlite3
```
+44
View File
@@ -100,3 +100,47 @@ With that, Backstage's cli and backend will detect public entry point and serve
5. Finally, as soon as you log in, you will be redirected to the main app home page (inspect the page and see that the protected bundle was served from the app backend after the redirect).
That's it!
## New Frontend System
If your app uses the new frontend system, you can still use the public entry point feature. The `index-public-experimental.tsx` file does end up looking a bit different in this case:
```tsx title="in packages/app/src/index-public-experimental.tsx"
import React from 'react';
import ReactDOM from 'react-dom/client';
import { CookieAuthRedirect } from '@backstage/plugin-auth-react';
import { createApp } from '@backstage/frontend-app-api';
import {
coreExtensionData,
createExtension,
createExtensionOverrides,
createSignInPageExtension,
} from '@backstage/frontend-plugin-api';
const signInPage = createSignInPageExtension({
name: 'guest',
loader: async () => props => <SignInPage {...props} providers={['guest']} />,
});
const authRedirectExtension = createExtension({
namespace: 'app',
name: 'layout',
attachTo: { id: 'app/root', input: 'children' },
output: {
element: coreExtensionData.reactElement,
},
factory: () => ({
element: <CookieAuthRedirect />,
}),
});
const app = createApp({
features: [
createExtensionOverrides({
extensions: [signInPage, authRedirectExtension],
}),
],
});
ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
```
+1 -2
View File
@@ -19,8 +19,7 @@ switch between database backends.
First, add PostgreSQL to your `backend` package:
```bash
# From your Backstage root directory
```bash title="From your Backstage root directory"
yarn --cwd packages/backend add pg
```