Merge branch 'master' of https://github.com/fidelity-external-staging/backstage-backstage into issue-24719-update-feature-docs
This commit is contained in:
@@ -47,7 +47,10 @@ If you want to use the Lighthouse CLI and run the checks based on the config you
|
||||
yarn dlx @lhci/cli@0.11.x autorun
|
||||
```
|
||||
|
||||
> Note: running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed.
|
||||
:::note Note
|
||||
Running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed.
|
||||
|
||||
:::
|
||||
|
||||
### Use Lighthouse Github Action on your own repo
|
||||
|
||||
|
||||
@@ -4,10 +4,14 @@ title: Contributing New Providers
|
||||
description: Documentation on adding new authentication providers
|
||||
---
|
||||
|
||||
> NOTE: The primary audience for this documentation are contributors to the main
|
||||
> Backstage project that want to add support for new authentication providers.
|
||||
> While you can follow it to implement your own custom providers it is much
|
||||
> more advanced than using our built-in providers.
|
||||
:::note Note
|
||||
|
||||
The primary audience for this documentation are contributors to the main
|
||||
Backstage project that want to add support for new authentication providers.
|
||||
While you can follow it to implement your own custom providers it is much
|
||||
more advanced than using our built-in providers.
|
||||
|
||||
:::
|
||||
|
||||
## How Does Authentication Work?
|
||||
|
||||
|
||||
@@ -23,13 +23,12 @@ auth:
|
||||
environment: development
|
||||
providers:
|
||||
oauth2Proxy:
|
||||
development:
|
||||
signIn:
|
||||
resolvers:
|
||||
# typically you would pick one of these
|
||||
- resolver: emailMatchingUserEntityProfileEmail
|
||||
- resolver: emailLocalPartMatchingUserEntityName
|
||||
- resolver: forwardedUserMatchingUserEntityName
|
||||
signIn:
|
||||
resolvers:
|
||||
# typically you would pick one of these
|
||||
- resolver: emailMatchingUserEntityProfileEmail
|
||||
- resolver: emailLocalPartMatchingUserEntityName
|
||||
- resolver: forwardedUserMatchingUserEntityName
|
||||
```
|
||||
|
||||
### Resolvers
|
||||
|
||||
@@ -27,6 +27,45 @@ any configuration. They generate self-signed tokens automatically for making
|
||||
requests to other Backstage backend plugins, and the receivers use the caller's
|
||||
public key set endpoint to be able to perform verification.
|
||||
|
||||
A backend plugin wishing to make a request to another backend plugin acquires
|
||||
the required token as follows, where `auth` and `httpAuth` are assumed to be
|
||||
injected from `coreServices.auth` and `coreServices.httpAuth`, respectively:
|
||||
|
||||
```ts
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: credentials,
|
||||
targetPluginId: '<plugin-id>', // e.g. 'catalog'
|
||||
});
|
||||
```
|
||||
|
||||
In this example we are assuming that we are in an Express request handler, and
|
||||
we extract the caller credentials (typically a user or a service) out of its
|
||||
`req` and make the upstream request on-behalf-of that principal. Prefer to use
|
||||
this pattern wherever there's an incoming set of credentials to refer to.
|
||||
|
||||
If you want to initiate a request entirely as your own service, not on behalf of
|
||||
anybody else, you can do so as follows:
|
||||
|
||||
```ts
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: await auth.getOwnServiceCredentials(),
|
||||
targetPluginId: '<plugin-id>', // e.g. 'catalog'
|
||||
});
|
||||
```
|
||||
|
||||
Callers pass along the tokens verbatim with requests in the `Authorization`
|
||||
header:
|
||||
|
||||
```yaml
|
||||
Authorization: Bearer eyJhbG...
|
||||
```
|
||||
|
||||
You may occasionally also see some code, e.g. clients to other systems, that
|
||||
accept a `credentials` argument directly instead of a token. For those, just
|
||||
pass in the credentials as acquired above, instead of making a token. The client
|
||||
code will know what to do with those credentials internally.
|
||||
|
||||
This flow has only one configuration option to set in your app-config:
|
||||
`backend.auth.dangerouslyDisableDefaultAuthPolicy`, which can be set to `true`
|
||||
if you for some reason need to completely disable both the issuing and
|
||||
@@ -55,6 +94,9 @@ backend:
|
||||
options:
|
||||
token: ${CICD_TOKEN}
|
||||
subject: cicd-system-completion-events
|
||||
# Restrictions are optional; see below
|
||||
accessRestrictions:
|
||||
- plugin: events
|
||||
- type: static
|
||||
options:
|
||||
token: ${ADMIN_CURL_TOKEN}
|
||||
@@ -73,13 +115,65 @@ The subjects must be strings without whitespace. They are used for identifying
|
||||
each caller, and become part of the credentials object that request recipient
|
||||
plugins get.
|
||||
|
||||
Callers pass along the tokens verbatim with requests in the `Authorization`
|
||||
header:
|
||||
Callers must pass along tokens verbatim with requests in the `Authorization`
|
||||
header when calling Backstage plugins:
|
||||
|
||||
```yaml
|
||||
Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW
|
||||
```
|
||||
|
||||
## JWKS Token Auth
|
||||
|
||||
This access method allows for external caller token authentication using configured
|
||||
JSON Web Key Sets (JWKS). This is useful for callers that are authenticating to our
|
||||
instance of Backstage with third-party tools, such as Auth0.
|
||||
|
||||
You can configure this access method by adding one or more entries of type `jwks`
|
||||
to the `backend.auth.externalAccess` app-config key:
|
||||
|
||||
```yaml title="in e.g. app-config.production.yaml"
|
||||
backend:
|
||||
auth:
|
||||
externalAccess:
|
||||
- type: jwks
|
||||
options:
|
||||
url: https://example.com/.well-known/jwks.json
|
||||
issuer: https://example.com
|
||||
algorithm: RS256
|
||||
audience: example, other-example
|
||||
subjectPrefix: custom-prefix
|
||||
- type: jwks
|
||||
options:
|
||||
url: https://another-example.com/.well-known/jwks.json
|
||||
issuer: https://example.com
|
||||
```
|
||||
|
||||
The URL should point at an unauthenticated endpoint that returns the JWKS.
|
||||
|
||||
`issuer` specifies the issuer(s) of the JWT that the authenticating app will accept.
|
||||
Passed JWTs must have an `iss` claim which matches one of the specified issuers.
|
||||
|
||||
`algorithm` specifies the algorithm(s) that are used to verify the JWT. The passed JWTs
|
||||
must have been signed using one of the listed algorithms.
|
||||
|
||||
`audience` specifies the intended audience(s) of the JWT. The passed JWTs must have an "aud"
|
||||
claim that matches one of the audiences specified, or have no audience specified.
|
||||
|
||||
For additional details regarding the JWKS configuration, please consult your authentication
|
||||
provider's documentation.
|
||||
|
||||
The subject returned from the token verification will become part of the
|
||||
credentials object that the request recipient plugins get. All subjects will have the prefix
|
||||
`external:`, but you can also provide a custom subjectPrefix which will get appended before the
|
||||
subject returned from your JWKS service (ex. `external:custom-prefix:sub`).
|
||||
|
||||
Callers must pass along tokens with requests in the `Authorization` header when
|
||||
calling Backstage plugins:
|
||||
|
||||
```yaml
|
||||
Authorization: Bearer eyJhbG...
|
||||
```
|
||||
|
||||
## Legacy Tokens
|
||||
|
||||
Plugins and backends that are _not_ on the new backend system use a legacy token
|
||||
@@ -156,8 +250,12 @@ payload:
|
||||
- `sub`: the exact string "backstage-server"
|
||||
- `exp`: one hour from the time it was generated, in epoch seconds
|
||||
|
||||
> NOTE: The JWT must encode the `alg` header as a protected header, such as with
|
||||
> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
|
||||
:::note Note
|
||||
|
||||
The JWT must encode the `alg` header as a protected header, such as with
|
||||
[setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
|
||||
|
||||
:::
|
||||
|
||||
The caller then passes along the JWT token with requests in the `Authorization`
|
||||
header:
|
||||
@@ -165,3 +263,97 @@ header:
|
||||
```yaml
|
||||
Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW
|
||||
```
|
||||
|
||||
## Access Restrictions
|
||||
|
||||
Each `externalAccess` entry may optionally have an `accessRestrictions` key,
|
||||
which limits what that particular access method can do. Let's look at an
|
||||
example:
|
||||
|
||||
```yaml title="in e.g. app-config.production.yaml"
|
||||
backend:
|
||||
auth:
|
||||
externalAccess:
|
||||
- type: static
|
||||
options:
|
||||
token: ${CICD_TOKEN}
|
||||
subject: cicd-system-completion-events
|
||||
accessRestrictions:
|
||||
- plugin: events
|
||||
```
|
||||
|
||||
In this short example there's only one entry. It says that for anyone trying to
|
||||
make access with the CICD token, they will be rejected if they try to contact
|
||||
anything but the `events` backend plugin. You could add additional entries to
|
||||
the array that allow targeting more plugins if that's what you want.
|
||||
|
||||
:::note Note
|
||||
|
||||
If no `accessRestrictions` are added, the access method has unlimited access to
|
||||
all functionality of all plugins. It is recommended that you try to specify
|
||||
access restrictions whenever possible, to reduce risk.
|
||||
|
||||
:::
|
||||
|
||||
Each entry has one or more of the following fields:
|
||||
|
||||
- **`plugin`**: Required. A plugin ID as a string, for example `'catalog'`. Permits
|
||||
access to make requests to this plugin. Can be further refined by setting
|
||||
additional fields as per below.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
accessRestrictions:
|
||||
# access to any other plugin will be rejected
|
||||
- plugin: my-plugin
|
||||
```
|
||||
|
||||
- **`permission`**: Optional. A collection (comma/space separated string or
|
||||
string array) of permission names. If given, this method is limited to only
|
||||
performing actions with these named permissions in the plugin with the ID
|
||||
given above.
|
||||
|
||||
Note that this only applies where permissions checks are enabled in the first
|
||||
place. Endpoints that are not protected by the permissions system at all, are
|
||||
not affected by this setting.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
accessRestrictions:
|
||||
- plugin: my-plugin
|
||||
# Any other permission check will be rejected.
|
||||
permission:
|
||||
- my-plugin.add-item
|
||||
- my-plugin.remove-item
|
||||
# Also supports the shorthand form:
|
||||
# permission: my-plugin.add-item, my-plugin.remove-item
|
||||
```
|
||||
|
||||
- **`permissionAttribute`**: Optional. A key-value object of permission attributes
|
||||
where each value is a collection (comma/space separated string or string
|
||||
array) of allowed such values. If given, this method is limited to only
|
||||
performing actions whose permissions have these attributes.
|
||||
|
||||
Note that this only applies where permissions checks are
|
||||
enabled in the first place. Endpoints that are not protected by
|
||||
the permissions system at all, are not affected by this
|
||||
setting.
|
||||
|
||||
In practice, this is typically used to limit by the `action` attribute, for
|
||||
`'create'`, `'read'`, `'update'`, or `'delete'` values.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
accessRestrictions:
|
||||
- plugin: my-plugin
|
||||
permissionAttribute:
|
||||
# Updates and deletes will be rejected.
|
||||
action:
|
||||
- create
|
||||
- read
|
||||
# Also supports the shorthand form:
|
||||
# action: create, read
|
||||
```
|
||||
|
||||
@@ -19,7 +19,11 @@ The diagram below provides an overview of the different building blocks, and the
|
||||
|
||||

|
||||
|
||||
> NOTE: These are all concepts that existed in our old backend system in one way or another, but they have now all been lifted up to be first class concerns.
|
||||
:::note Note
|
||||
|
||||
These are all concepts that existed in our old backend system in one way or another, but they have now all been lifted up to be first class concerns.
|
||||
|
||||
:::
|
||||
|
||||
### Backend
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`.
|
||||
|
||||
## Root Scoped Services
|
||||
|
||||
If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
|
||||
If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factor for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
|
||||
|
||||
There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it.
|
||||
|
||||
@@ -157,7 +157,7 @@ export const fooServiceFactory = createServiceFactory({
|
||||
});
|
||||
```
|
||||
|
||||
Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
|
||||
Whatever value is returned by the `createRootContext` function will be shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
|
||||
|
||||
## Default Service Factories
|
||||
|
||||
@@ -192,7 +192,11 @@ When defining a default factory for a service, it is possible for it to end up w
|
||||
|
||||
## Service Factory Options
|
||||
|
||||
> NOTE: This pattern is discouraged, only use it when necessary. If possible you should prefer to make services configurable via static configuration instead.
|
||||
:::note Note
|
||||
|
||||
This pattern is discouraged, only use it when necessary. If possible you should prefer to make services configurable via static configuration 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:
|
||||
|
||||
|
||||
@@ -78,4 +78,4 @@ Plugins must always be designed to be horizontally scalable. This means that you
|
||||
|
||||
### Isolated
|
||||
|
||||
Plugins must never communicate with each other directly through code, they may only communicate over the network. Plugins that wish to expose an external interface for other plugins and modules to use are recommended to do so through a [node-library](../../local-dev/cli-build-system.md#package-roles) package. The library should export an API client service to make calls to your plugin, or similar construct.
|
||||
Plugins must never communicate with each other directly through code, they may only communicate over the network. Plugins that wish to expose an external interface for other plugins and modules to use are recommended to do so through a [node-library](../../tooling/cli/02-build-system.md#package-roles) package. The library should export an API client service to make calls to your plugin, or similar construct.
|
||||
|
||||
@@ -91,6 +91,6 @@ export const catalogClientServiceFactory = createServiceFactory({
|
||||
})
|
||||
```
|
||||
|
||||
An exception to the above service reference naming pattern has been made for the all of the core services in the core API. The `@backstage/backend-plugin-api` makes all core service references available via a single `coreServices` collection. Likewise, the `@backstage/backend-test-utils` exports all mock service implementations via a single `mockServices` collection. This means that the table above is slightly misleading, since `loggerServiceRef` and `databaseServiceRef` are instead available as `coreServices.logger` and `coreService.database`. We recommend that plugins avoid this patterns unless they have a very large number of services that they need to export.
|
||||
An exception to the above service reference naming pattern has been made for all of the core services in the core API. The `@backstage/backend-plugin-api` makes all core service references available via a single `coreServices` collection. Likewise, the `@backstage/backend-test-utils` exports all mock service implementations via a single `mockServices` collection. This means that the table above is slightly misleading, since `loggerServiceRef` and `databaseServiceRef` are instead available as `coreServices.logger` and `coreService.database`. We recommend that plugins avoid this patterns unless they have a very large number of services that they need to export.
|
||||
|
||||
While it is often preferred to prefix root scoped services with `Root`, it is not required. For example, `RootHttpRouterService` and `RootLifecycleService` follow this pattern, but `ConfigService` doesn't and it is a root scoped service.
|
||||
|
||||
@@ -46,7 +46,7 @@ Apart from installing existing plugins and modules in the backend, there are a c
|
||||
|
||||
### Configuration
|
||||
|
||||
Perhaps the most accessible way is though static configuration, which you can read more about in the documentation for how to [write configuration](../../conf/writing.md). Many different aspects of the backend can be configured, including both the behavior of the backend itself, as well as many plugins or modules. You'll need to refer to the documentation of each plugin or module to see what configuration is available. Also be sure to check out the documentation of the [core services](../core-services/01-index.md), as that also covers how to configure those.
|
||||
Perhaps the most accessible way is through static configuration, which you can read more about in the documentation for how to [write configuration](../../conf/writing.md). Many different aspects of the backend can be configured, including both the behavior of the backend itself, as well as many plugins or modules. You'll need to refer to the documentation of each plugin or module to see what configuration is available. Also be sure to check out the documentation of the [core services](../core-services/01-index.md), as that also covers how to configure those.
|
||||
|
||||
### Services
|
||||
|
||||
@@ -107,7 +107,7 @@ This example touches on the fact that services can have different scopes, being
|
||||
|
||||
A more advanced way to deploy Backstage is to split the backend plugins into multiple different backend deployments. Both the [deployment documentation](../../deployment/scaling.md) and [Threat Model](../../overview/threat-model.md#trust-model) explain the benefits of this, so here we'll focus on how to do it.
|
||||
|
||||
To create a separate backend we need to create an additional backend package. This package will be built and deployed separately from your existing backend. There is currently no template to create a backend via `yarn new`, so the quickest way is to copy the new package and modify. The naming is up to you and it depends on how you are splitting things and up. For this example we'll just use a simple suffix. You might end up with a directory structure like this:
|
||||
To create a separate backend we need to create an additional backend package. This package will be built and deployed separately from your existing backend. There is currently no template to create a backend via `yarn new`, so the quickest way is to copy the new package and modify. The naming is up to you and it depends on how you are splitting things up. For this example we'll just use a simple suffix. You might end up with a directory structure like this:
|
||||
|
||||
```text
|
||||
packages/
|
||||
|
||||
@@ -1369,7 +1369,7 @@ The vast majority of the backend plugins that currently live in the Backstage Re
|
||||
| @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) |
|
||||
|
||||
@@ -6,8 +6,12 @@ sidebar_label: Overview
|
||||
description: Building backend plugins and modules using the new backend system
|
||||
---
|
||||
|
||||
> NOTE: If you have an existing backend and/or backend plugins that are not yet
|
||||
> using the new backend system, see [migrating](./08-migrating.md).
|
||||
:::note Note
|
||||
|
||||
If you have an existing backend and/or backend plugins that are not yet
|
||||
using the new backend system, see [migrating](./08-migrating.md).
|
||||
|
||||
:::
|
||||
|
||||
This section covers how to build your own backend [plugins](../architecture/04-plugins.md) and
|
||||
[modules](../architecture/06-modules.md). They are sometimes collectively referred to as
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Router } from 'express';
|
||||
import { KubernetesBuilder } from './KubernetesBuilder';
|
||||
|
||||
@@ -214,3 +214,44 @@ The above module can then be installed by the integrator alongside the kubernete
|
||||
backend.add(import('@backstage/plugin-kubernetes-backend'));
|
||||
backend.add(import('@internal/gke-cluster-supplier'));
|
||||
```
|
||||
|
||||
### Dev Server
|
||||
|
||||
Follow the steps below to run your migrated plugin on a local development server:
|
||||
|
||||
1. First, delete the `src/run.ts` and `src/service/standaloneServer.ts` files in case they exist (the `backstage-cli` previously used these files to run legacy backend plugins locally, but they are no longer required).
|
||||
|
||||
2. Next, create a new development backend in the `dev/index.ts` file. The dev server is a lite version of a backend app that is mainly used to run your plugin locally, so a simple `kubernetes` backend local development server would look like this:
|
||||
|
||||
```ts title="in dev/index.js"
|
||||
// This package should be installed as a `dev` dependency
|
||||
import { createBackend } from '@backstage/backend-defaults';
|
||||
|
||||
const backend = createBackend();
|
||||
// Path to the file where the plugin is export as default
|
||||
backend.add(import('../src'));
|
||||
backend.start();
|
||||
```
|
||||
|
||||
The development server created above will be automatically configured with the default dependency factories, but if you need to mock some of the services your plugin relies on, such as the `rootConfig` service, you can use one of the `mockServices` factories:
|
||||
|
||||
```ts title="in dev/index.js"
|
||||
//...
|
||||
// This package should be installed as `devDependecies`
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
|
||||
const backend = createBackend();
|
||||
// ...
|
||||
backend.add(
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
// your config mocked values goes here
|
||||
},
|
||||
}),
|
||||
);
|
||||
// ...
|
||||
```
|
||||
|
||||
Checkout the [custom service implementations](https://backstage.io/docs/backend-system/building-backends/index#custom-service-implementations) documentation and also the [core service configurations](https://backstage.io/docs/backend-system/core-services/index) page in case you'd like to create your own custom mock factory for one or more services.
|
||||
|
||||
3. Now you can finally start your plugin locally by running `yarn start` from the root folder of your plugin.
|
||||
|
||||
@@ -5,4 +5,136 @@ sidebar_label: Auth
|
||||
description: Documentation for the Auth service
|
||||
---
|
||||
|
||||
TODO
|
||||
This service deals with the generation and verification of tokens and their
|
||||
associated representations as credentials objects. You can use it for validating
|
||||
incoming tokens, and generating tokens for calling other services.
|
||||
|
||||
If you want to deal with credentials specifically in the HTTP request/response
|
||||
flow, see also [the `httpAuth` service](./http-auth.md). If you want to extract
|
||||
more details about authenticated users such as their ownership entity refs, use
|
||||
[the `userInfo` service](./user-info.md).
|
||||
|
||||
## Using the Service
|
||||
|
||||
In the following code examples, the `auth` and `httpAuth` variables are assumed
|
||||
to be dependency-injected instances of the `coreServices.auth` and
|
||||
`coreServices.httpAuth` service, respectively. For a backend plugin, it might
|
||||
look like this:
|
||||
|
||||
```ts
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'my-plugin',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
},
|
||||
async init({ auth, httpAuth, httpRouter }) {
|
||||
// Your code goes here
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Creating Request Tokens
|
||||
|
||||
If you need to create a token that can be used for making a request to another
|
||||
backend plugin:
|
||||
|
||||
```ts
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: await auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
```
|
||||
|
||||
:::note Note
|
||||
|
||||
Never store and reuse tokens. Always call `getPluginRequestToken` immediately
|
||||
before making a request. Otherwise you run the risk of running into permission
|
||||
problems when expired tokens are being used for requests.
|
||||
|
||||
:::
|
||||
|
||||
This example is suitable when you need to make the request "as your own plugin",
|
||||
i.e. when your code is the original initiator of the call. An example of this
|
||||
could be periodic batch processes that index content in another service.
|
||||
|
||||
In situations where you are making a call on-behalf-of someone else, for example
|
||||
when making upstream requests inside a request handler, please always instead
|
||||
use the extracted credentials from the request.
|
||||
|
||||
```ts
|
||||
router.get('/makes-calls', async (req, res) => {
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: await httpAuth.credentials(req),
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
// make a call using the token
|
||||
```
|
||||
|
||||
This ensures that the original caller and their associated permissions are
|
||||
properly carried along with the request chain. See [the `httpAuth` service docs](./http-auth.md)
|
||||
for more details.
|
||||
|
||||
The [service to service auth docs](../../auth/service-to-service-auth.md)
|
||||
contain more details about how to properly use tokens in your HTTP request
|
||||
paths.
|
||||
|
||||
### Authorizing Tokens
|
||||
|
||||
Most plugins should not deal with incoming request tokens directly at all, but
|
||||
rather use [`httpAuth.credentials`](./http-auth.md) instead as part of their
|
||||
request handlers. But in the rare cases where you are holding an incoming token
|
||||
and want to validate it and turn it into a credentials object, you can do so:
|
||||
|
||||
```ts
|
||||
const credentials = await auth.authenticate(token);
|
||||
```
|
||||
|
||||
There is an optional second parameter that you can set to `{ allowLimitedAccess:
|
||||
true }` if you specifically built a plugin that deals with cookie based access,
|
||||
which is rare.
|
||||
|
||||
### Inspecting Credentials
|
||||
|
||||
The `auth` service also contains facilities for working with credentials
|
||||
objects. For example checking what type of principal (caller type - e.g. user or
|
||||
service) they represent. For example:
|
||||
|
||||
```ts
|
||||
if (auth.isPrincipal(credentials, 'user)) {
|
||||
// In here, the TypeScript type of the credentials object has been properly
|
||||
// narrowed to `BackstageCredentials<BackstageUserPrincipal>` so you can
|
||||
// access its specific properties such as `credentials.principal.userEntityRef`.
|
||||
}
|
||||
```
|
||||
|
||||
## Configuring the service
|
||||
|
||||
:::note Note
|
||||
|
||||
The `auth` service is not suitable for having its implementation replaced
|
||||
entirely in your private repo. If you desire additional service auth related
|
||||
features, don't hesitate to [file an issue](https://github.com/backstage/backstage/issues/new/choose)
|
||||
or [contribute](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) to the open source features.
|
||||
|
||||
:::
|
||||
|
||||
For configuring service-to-service access methods, see [the auth docs](../../auth/service-to-service-auth.md).
|
||||
|
||||
The default auth policy requires all requests to be authenticated with either
|
||||
user or service credentials. This can be disabled by setting the
|
||||
`backend.auth.dangerouslyDisableDefaultAuthPolicy` app-config flag to `true`.
|
||||
Disabling this check means that the backend will no longer block unauthenticated
|
||||
requests, but instead allow them to pass through to plugins. Do not do this in
|
||||
production unless absolutely necessary.
|
||||
|
||||
If permissions are enabled, unauthenticated requests will be treated exactly as
|
||||
such, leaving it to the permission policy to determine what permissions should
|
||||
be allowed for an unauthenticated identity. Note that this will also apply to
|
||||
service-to-service calls between plugins unless you configure credentials for
|
||||
service calls.
|
||||
|
||||
@@ -5,4 +5,102 @@ sidebar_label: Http Auth
|
||||
description: Documentation for the Http Auth service
|
||||
---
|
||||
|
||||
TODO
|
||||
The `httpAuth` service deals with submitting and receiving credentials on Express
|
||||
HTTP request/response objects. This service is frequently used in plugins that
|
||||
have REST interfaces.
|
||||
|
||||
If you want to deal with raw tokens and do low level credentials handling, see
|
||||
also [the `auth` service](./auth.md). If you want to extract more details about
|
||||
authenticated users such as their ownership entity refs, use [the `userInfo` service](./user-info.md).
|
||||
|
||||
## Using the Service
|
||||
|
||||
In the following code examples, the `auth` and `httpAuth` variables are assumed
|
||||
to be dependency-injected instances of the `coreServices.auth` and
|
||||
`coreServices.httpAuth` service, respectively. For a backend plugin, it might
|
||||
look like this:
|
||||
|
||||
```ts
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'my-plugin',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
},
|
||||
async init({ auth, httpAuth, httpRouter }) {
|
||||
// Your code goes here
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Getting Request Credentials
|
||||
|
||||
If you need to extract the validated credentials out of an incoming request, you
|
||||
can do so like this:
|
||||
|
||||
```ts
|
||||
router.get('/some-request', async (req, res) => {
|
||||
const credentials = await httpAuth.credentials(req, { allow: ['user'] });
|
||||
// Do something with the credentials here
|
||||
```
|
||||
|
||||
The second argument is optional, but in this example we specified that we only
|
||||
want to allow user based requests. The credentials returned will then have a
|
||||
narrowed TypeScript type that reflects that the principal is known to be of the
|
||||
user type. This second argument can also specify `allowLimitedAccess: true` if
|
||||
you specifically built a plugin that deals with cookie based access, which is
|
||||
rare.
|
||||
|
||||
The default is to accept both service and user credentials (excluding limited
|
||||
access), but in the example above, any attempt to call this endpoint with
|
||||
service credentials will result in an Unauthorized error being thrown.
|
||||
|
||||
:::note Note
|
||||
|
||||
You don't need to call `httpAuth.credentials` _just_ to ensure that incoming
|
||||
credentials are valid in the first place; only use this method if you actually
|
||||
need to act upon the credentials somehow. The Backstage backend framework will have
|
||||
ensured the actual validity of any incoming token before your backend code is
|
||||
reached. The policy for these upfront framework level rules is controlled using
|
||||
[the `httpRouter` service](./http-router.md) when you register your routes.
|
||||
|
||||
:::
|
||||
|
||||
If you want to further work with the credentials object, [the `auth` service](./auth.md)
|
||||
has helper methods for that.
|
||||
|
||||
### Issuing Cookies
|
||||
|
||||
For some rare use cases, plugins may want to issue cookies with _limited access_
|
||||
user credentials. This is mostly relevant when browsers need to be able to
|
||||
request static resources, such as in the TechDocs plugin.
|
||||
|
||||
Plugins should almost never interact with the cookie functionality of the
|
||||
`httpAuth` service directly. The framework has builtin handling of cookie
|
||||
creation/deletion requests on a dedicated well-known endpoint. All you normally
|
||||
have to do to accept limited user access is to inform [the `httpRouter` service](./http-router.md)
|
||||
when creating your route that you want to permit
|
||||
cookie based access for a specific route, and then setting `allowLimitedAccess`
|
||||
to `true` when extracting credentials.
|
||||
|
||||
Due to the above, we do not document the `httpAuth.issueUserCookie` method here.
|
||||
|
||||
## Configuring the service
|
||||
|
||||
:::note Note
|
||||
|
||||
The `httpAuth` service is not suitable for having its implementation replaced
|
||||
entirely in your private repo. If you desire additional service auth related
|
||||
features, don't hesitate to [file an issue](https://github.com/backstage/backstage/issues/new/choose)
|
||||
or [contribute](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) to the open source features.
|
||||
|
||||
:::
|
||||
|
||||
This service has no configuration options, but it abides by the policies you
|
||||
have set up using [the `httpRouter` service](./http-router.md) for your routes,
|
||||
if any.
|
||||
|
||||
@@ -5,7 +5,8 @@ sidebar_label: Http Router
|
||||
description: Documentation for the Http Router service
|
||||
---
|
||||
|
||||
One of the most common services is the HTTP router service which is used to expose HTTP endpoints for other plugins to consume.
|
||||
One of the most common services is the HTTP router service which is used to
|
||||
expose HTTP endpoints for other plugins to consume.
|
||||
|
||||
## Using the service
|
||||
|
||||
@@ -37,6 +38,36 @@ createBackendPlugin({
|
||||
});
|
||||
```
|
||||
|
||||
This service is also responsible for keeping track of the auth policies that
|
||||
apply to your routes. The default policy is to require that auth is present with
|
||||
every incoming request, and to accept both service and user credentials
|
||||
(excluding limited access tokens). You can override this while registering your
|
||||
routes. This dangerously allows unauthenticated access on a specific route:
|
||||
|
||||
```ts
|
||||
http.addAuthPolicy({
|
||||
path: '/static/:id',
|
||||
allow: 'unauthenticated',
|
||||
});
|
||||
```
|
||||
|
||||
Note that the path is exactly the same format as what you used in your routes,
|
||||
including placeholders.
|
||||
|
||||
If your plugin uses cookie based access (which is rare), you need to allow that
|
||||
as follows:
|
||||
|
||||
```ts
|
||||
http.addAuthPolicy({
|
||||
path: '/static/:id',
|
||||
allow: 'user-cookie',
|
||||
});
|
||||
```
|
||||
|
||||
For those routes you will also have to specify `allowLimitedAccess: true` when
|
||||
using the [`auth`](./auth.md) and [`httpAuth`](./http-auth.md) services to
|
||||
access the incoming credentials.
|
||||
|
||||
## Configuring the service
|
||||
|
||||
This service does not have any configuration options.
|
||||
|
||||
@@ -16,36 +16,42 @@ import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { Router } from 'express';
|
||||
import { NotAllowedError } from '@backstage/errors';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import Router from 'express-promise-router';
|
||||
|
||||
createBackendPlugin({
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
permissions: coreServices.permissions,
|
||||
http: coreServices.httpRouter,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
},
|
||||
async init({ permissions, http }) {
|
||||
const router = Router();
|
||||
router.get('/test-me', (request, response) => {
|
||||
// use the identity service to pull out the token from request headers
|
||||
const { token } = await identity.getIdentity({
|
||||
request,
|
||||
});
|
||||
|
||||
// ask the permissions framework what the decision is for the permission
|
||||
async init({ permissions, httpRouter, httpAuth }) {
|
||||
const endpoints = Router();
|
||||
endpoints.get('/test-me', (request, response) => {
|
||||
// Ask the permissions framework what the decision is for the given
|
||||
// permission, for the principal that made the original request. The
|
||||
// `httpAuth` service helps us extract those credentials. We authorize
|
||||
// a single permission here, so the result will be an array with one
|
||||
// element accordingly.
|
||||
const permissionResponse = await permissions.authorize(
|
||||
[
|
||||
{
|
||||
permission: myCustomPermission,
|
||||
},
|
||||
],
|
||||
{ token },
|
||||
[{ permission: myCustomPermission }],
|
||||
{ credentials: await httpAuth.credentials(request) },
|
||||
);
|
||||
|
||||
if (permissionResponse[0].result !== AuthorizeResult.ALLOW) {
|
||||
throw new NotAllowedError(
|
||||
'You are not permitted to perform this action',
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Actual code goes here
|
||||
});
|
||||
|
||||
http.use(router);
|
||||
httpRouter.use(endpoints);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -5,4 +5,28 @@ sidebar_label: Plugin Metadata
|
||||
description: Documentation for the Plugin Metadata service
|
||||
---
|
||||
|
||||
TODO
|
||||
This service allows you to query for metadata about the current plugin. In particular, this service is used by other plugin-scoped services, if they need to know what the ID is of the plugin that they are being instantiated for.
|
||||
|
||||
## Using the service
|
||||
|
||||
The following example shows a fake plugin-scoped service which wants to know what plugin it "belongs" to.
|
||||
|
||||
```ts
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
export const myServiceFactory = createServiceFactory({
|
||||
service: myServiceRef,
|
||||
deps: {
|
||||
logger: coreServices.logger,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ logger, plugin }) {
|
||||
const pluginId = plugin.getId();
|
||||
logger.info(`Creating an instance of my service for plugin '${id}'`);
|
||||
return ...; // TODO
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -5,4 +5,63 @@ sidebar_label: User Info
|
||||
description: Documentation for the User Info service
|
||||
---
|
||||
|
||||
TODO
|
||||
This service lets you extract more information about a set of user credentials.
|
||||
Specifically, it can be used to extract the ownership entity refs for a user
|
||||
principal.
|
||||
|
||||
See also the [`auth`](./auth.md) and [`httpAuth`](./http-auth.md) services for
|
||||
general credentials handling.
|
||||
|
||||
## Using the Service
|
||||
|
||||
In the following code examples, the `userInfo`, `auth`, and `httpAuth` variables are assumed
|
||||
to be dependency-injected instances of the `coreServices.userInfo` and
|
||||
`coreServices.httpAuth` service, respectively. For a backend plugin, it might
|
||||
look like this:
|
||||
|
||||
```ts
|
||||
export default createBackendPlugin({
|
||||
pluginId: 'my-plugin',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
userInfo: coreServices.userInfo,
|
||||
},
|
||||
async init({ auth, httpAuth, httpRouter, userInfo }) {
|
||||
// Your code goes here
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Getting User Info
|
||||
|
||||
This example extracts some user credentials out of a request and fetches
|
||||
additional information about that principal.
|
||||
|
||||
```ts
|
||||
router.get('/some-request', async (req, res) => {
|
||||
const credentials = await httpAuth.credentials(req, { allow: ['user'] });
|
||||
const info = await userInfo.getUserInfo(credentials);
|
||||
```
|
||||
|
||||
The `userInfo` service only deals with credentials that contain user principals,
|
||||
it won't accept requests for service principals. In our example code the initial
|
||||
credentials extraction limits it to user credentials upfront. If you have an
|
||||
endpoint that allows both user and service credentials, you may want to wrap
|
||||
your user info extraction in a principal type check:
|
||||
|
||||
```ts
|
||||
router.get('/some-request', async (req, res) => {
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
if (auth.isPrincipal(credentials, 'user')) {
|
||||
const info = await userInfo.getUserInfo(credentials);
|
||||
// ...
|
||||
```
|
||||
|
||||
The user info contains data that was extracted during sign-in for the given
|
||||
user.
|
||||
|
||||
@@ -101,7 +101,7 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
|
||||
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
|
||||
file works, see the
|
||||
[`backend:bundle` command docs](../local-dev/cli-commands.md#backendbundle).
|
||||
[`backend:bundle` command docs](../tooling/cli/03-commands.md#backendbundle).
|
||||
|
||||
The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be
|
||||
executed with the root of the repo as the build context, in order to get access
|
||||
|
||||
@@ -627,7 +627,7 @@ annotations:
|
||||
#### Adding the namespace annotation
|
||||
|
||||
Entities can have the `backstage.io/kubernetes-namespace` annotation, this will cause the entity's Kubernetes resources
|
||||
to by looked up via that namespace.
|
||||
to be looked up via that namespace.
|
||||
|
||||
```yaml
|
||||
annotations:
|
||||
|
||||
@@ -236,7 +236,11 @@ backend.add(kubernetesModuleCustomClusterDiscovery);
|
||||
backend.start();
|
||||
```
|
||||
|
||||
> Note: this example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery)
|
||||
:::note Note
|
||||
|
||||
This example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery)
|
||||
|
||||
:::
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -34,10 +34,14 @@ const searchEngine = new LunrSearchEngine({ logger: env.logger });
|
||||
const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine });
|
||||
```
|
||||
|
||||
> Note: Lunr is appropriate as a zero-config search engine when developing
|
||||
> other parts of Backstage locally, however its use is highly discouraged when
|
||||
> running Backstage in production. When deploying Backstage, use one of the
|
||||
> other search engines instead.
|
||||
:::note Note
|
||||
|
||||
Lunr is appropriate as a zero-config search engine when developing
|
||||
other parts of Backstage locally, however its use is highly discouraged when
|
||||
running Backstage in production. When deploying Backstage, use one of the
|
||||
other search engines instead.
|
||||
|
||||
:::
|
||||
|
||||
## Postgres
|
||||
|
||||
|
||||
@@ -95,7 +95,11 @@ const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => {
|
||||
<Route path="/catalog" element={<CatalogIndexPage columns={myColumnsFunc} />} />
|
||||
```
|
||||
|
||||
> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
|
||||
:::note Note
|
||||
|
||||
The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
|
||||
|
||||
:::
|
||||
|
||||
## Customize Actions
|
||||
|
||||
@@ -162,7 +166,11 @@ const customActions: TableProps<CatalogTableRow>['actions'] = [
|
||||
<Route path="/catalog" element={<CatalogIndexPage actions={customActions} />} />
|
||||
```
|
||||
|
||||
> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
|
||||
:::note Note
|
||||
|
||||
The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file.
|
||||
|
||||
:::
|
||||
|
||||
The above customization will override the existing actions. Currently the only way to keep them and add your own is to also include the existing actions in your array by copying them from the [`defaultActions`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L113-L168).
|
||||
|
||||
@@ -400,7 +408,11 @@ export const CustomCatalogPage = () => {
|
||||
|
||||
The above is a very basic version of a fully custom `CatalogIndexPage`, you'll want to explore the various props to see what you can all do with them. This was built off the building blocks seen in the [`DefaultCatalogPage`](https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx)
|
||||
|
||||
> Note: The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
|
||||
:::note Note
|
||||
|
||||
The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically.
|
||||
|
||||
:::
|
||||
|
||||
To use this custom `CatalogIndexPage` which we called `CustomCatalogPage`, you'll need to make the following change:
|
||||
|
||||
|
||||
@@ -0,0 +1,604 @@
|
||||
---
|
||||
id: extending-the-model--old
|
||||
title: Extending the model
|
||||
# prettier-ignore
|
||||
description: Documentation on extending the catalog model
|
||||
---
|
||||
|
||||
The Backstage catalog [entity data model](descriptor-format.md) is based on the
|
||||
[Kubernetes objects format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/),
|
||||
and borrows a lot of its semantics as well. This page describes those semantics
|
||||
at a higher level and how to extend them to fit your organization.
|
||||
|
||||
Backstage comes with a number of catalog concepts out of the box:
|
||||
|
||||
- There are a number of builtin versioned _kinds_, such as `Component`, `User`
|
||||
etc. These encapsulate the high level concept of an entity, and define the
|
||||
schema for its entity definition data.
|
||||
- An entity has both a _metadata_ object and a _spec_ object at the root.
|
||||
- Each kind may or may not have a _type_. For example, there are several well
|
||||
known types of component, such as `service` and `website`. These clarify the
|
||||
more detailed nature of the entity, and may affect what features are exposed
|
||||
in the interface.
|
||||
- Entities may have a number of _[annotations](well-known-annotations.md)_ on
|
||||
them. These can be added either by humans into the descriptor files, or added
|
||||
by automated processes when the entity is ingested into the catalog.
|
||||
- Entities may have a number of _labels_ on them.
|
||||
- Entities may have a number of _relations_, expressing how they relate to each
|
||||
other in different ways.
|
||||
|
||||
We'll list different possibilities for extending this below.
|
||||
|
||||
## Adding a New apiVersion of an Existing Kind
|
||||
|
||||
Example intents:
|
||||
|
||||
> "I want to evolve this core kind, tweaking the semantics a bit so I will bump
|
||||
> the apiVersion a step"
|
||||
|
||||
> "This core kind is a decent fit but we want to evolve it at will so we'll move
|
||||
> it to our own company's apiVersion space and use that instead of
|
||||
> `backstage.io`."
|
||||
|
||||
The `backstage.io` apiVersion space is reserved for use by the Backstage
|
||||
maintainers. Please do not change or add versions within that space.
|
||||
|
||||
If you add an [apiVersion](descriptor-format.md#apiversion-and-kind-required)
|
||||
space of your own, you are effectively branching out from the underlying kind
|
||||
and making your own. An entity kind is identified by the apiVersion + kind pair,
|
||||
so even though the resulting entity may be similar to the core one, there will
|
||||
be no guarantees that plugins will be able to parse or understand its data. See
|
||||
below about adding a new kind.
|
||||
|
||||
## Adding a New Kind
|
||||
|
||||
Example intents:
|
||||
|
||||
> "The kinds that come with the package are lacking. I want to model this other
|
||||
> thing that is a poor fit for either of the builtins."
|
||||
|
||||
> "This core kind is a decent fit but we want to evolve it at will so we'll move
|
||||
> it to our own company's apiVersion space and use that instead of
|
||||
> `backstage.io`."
|
||||
|
||||
A [kind](descriptor-format.md#apiversion-and-kind-required) is an overarching
|
||||
family, or an idea if you will, of entities that also share a schema. Backstage
|
||||
comes with a number of builtin ones that we believe are useful for a large
|
||||
variety of needs that one may want to model in Backstage. The primary ambition
|
||||
is to map things to these kinds, but sometimes you may want or need to extend
|
||||
beyond them.
|
||||
|
||||
Introducing a new apiVersion is basically the same as adding a new kind. Bear in
|
||||
mind that most plugins will be compiled against the builtin
|
||||
`@backstage/catalog-model` package and have expectations that kinds align with
|
||||
that.
|
||||
|
||||
The catalog backend itself, from a storage and API standpoint, does not care
|
||||
about the kind of entities it stores. Extending with new kinds is mainly a
|
||||
matter of permitting them to pass validation when building the backend catalog
|
||||
using the `CatalogBuilder`, and then to make plugins be able to understand the
|
||||
new kind.
|
||||
|
||||
For the consuming side, it's a different story. Adding a kind has a very large
|
||||
impact. The very foundation of Backstage is to attach behavior and views and
|
||||
functionality to entities that we ascribe some meaning to. There will be many
|
||||
places where code checks `if (kind === 'X')` for some hard coded `X`, and casts
|
||||
it to a concrete type that it imported from a package such as
|
||||
`@backstage/catalog-model`.
|
||||
|
||||
If you want to model something that doesn't feel like a fit for either of the
|
||||
builtin kinds, feel free to reach out to the Backstage maintainers to discuss
|
||||
how to best proceed.
|
||||
|
||||
If you end up adding that new kind, you must namespace its `apiVersion`
|
||||
accordingly with a prefix that makes sense, typically based on your organization
|
||||
name - e.g. `my-company.net/v1`. Also do pick a new `kind` identifier that does
|
||||
not collide with the builtin kinds.
|
||||
|
||||
## Adding a New Type of an Existing Kind
|
||||
|
||||
Example intents:
|
||||
|
||||
> "This is clearly a component, but it's of a type that doesn't quite fit with
|
||||
> the ones I've seen before."
|
||||
|
||||
> "We don't call our teams "team", can't we put "flock" as the group type?"
|
||||
|
||||
Some entity kinds have a `type` field in its spec. This is where an organization
|
||||
are free to express the variety of entities within a kind. This field is
|
||||
expected to follow some taxonomy that makes sense for yourself. The chosen value
|
||||
may affect what operations and views are enabled in Backstage for that entity.
|
||||
Inside Spotify our model has grown significantly over the years, and our
|
||||
component types now include ML models, apps, data pipelines and many more.
|
||||
|
||||
It might be tempting to put software that doesn't fit into any of the existing
|
||||
types into an Other catch-all type. There are a few reasons why we advise
|
||||
against this; firstly, we have found that it is preferred to match the
|
||||
conceptual model that your engineers have when describing your software.
|
||||
Secondly, Backstage helps your engineers manage their software by integrating
|
||||
the infrastructure tooling through plugins. Different plugins are used for
|
||||
managing different types of components.
|
||||
|
||||
For example, the
|
||||
[Lighthouse plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse)
|
||||
only makes sense for Websites. The more specific you can be in how you model
|
||||
your software, the easier it is to provide plugins that are contextual.
|
||||
|
||||
Adding a new type takes relatively little effort and carries little risk. Any
|
||||
type value is accepted by the catalog backend, but plugins may have to be
|
||||
updated if you want particular behaviors attached to that new type.
|
||||
|
||||
## Changing the Validation Rules for The Entity Envelope or Metadata Fields
|
||||
|
||||
Example intents:
|
||||
|
||||
> "We want to import our old catalog but the default set of allowed characters
|
||||
> for a metadata.name are too strict."
|
||||
|
||||
> "I want to change the rules for annotations so that I'm allowed to store any
|
||||
> data in annotation values, not just strings."
|
||||
|
||||
After pieces of raw entity data have been read from a location, they are passed
|
||||
through a field format validation step. This ensures that the types and syntax
|
||||
of the base envelope and metadata make sense - in short, things that aren't
|
||||
entity-kind-specific. Some or all of these validators can be replaced when
|
||||
building the backend using the catalog's dedicated `catalogModelExtensionPoint`
|
||||
(or directly on the `CatalogBuilder` if you are still using the old backend
|
||||
system).
|
||||
|
||||
The risk and impact of this type of extension varies, based on what it is that
|
||||
you want to do. For example, extending the valid character set for kinds,
|
||||
namespaces and names can be fairly harmless, with a few notable exceptions -
|
||||
there is code that expects these to never ever contain a colon or slash, for
|
||||
example, and introducing URL-unsafe characters risks breaking plugins that
|
||||
aren't careful about encoding arguments. Supporting non-strings in annotations
|
||||
may be possible but has not yet been tried out in the real world - there is
|
||||
likely to be some level of plugin breakage that can be hard to predict.
|
||||
|
||||
You must also be careful about not making the rules _more strict_ than they used
|
||||
to be after populating the catalog with data. This risks making previously valid
|
||||
entities start having processing errors and fail to update.
|
||||
|
||||
Before making this kind of extension, we recommend that you contact the
|
||||
Backstage maintainers or a support partner to discuss your use case.
|
||||
|
||||
This is an example of relaxing the format rules of the `metadata.name` field:
|
||||
|
||||
```ts
|
||||
import { createBackend } from '@backstage/backend-defaults';
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
|
||||
const myCatalogCustomizations = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'catalog-customization',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
catalogModel: catalogModelExtensionPoint,
|
||||
},
|
||||
async init({ catalogModel }) {
|
||||
catalogModel.setFieldValidators({
|
||||
// This is only one of many methods that you can pass into
|
||||
// setFieldValidators; your editor of choice should help you
|
||||
// find the others. The length checks and regexp inside are
|
||||
// just examples and can be adjusted as needed, but take care
|
||||
// to test your changes thoroughly to ensure that you get
|
||||
// them right.
|
||||
isValidEntityName(value) {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 63 &&
|
||||
/^[A-Za-z0-9@+_.-]+$/.test(value)
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const backend = createBackend();
|
||||
// ... add other backend features and the catalog backend itself here ...
|
||||
backend.add(myCatalogCustomizations);
|
||||
backend.start();
|
||||
```
|
||||
|
||||
## Changing the Validation Rules for Core Entity Fields
|
||||
|
||||
Example intent:
|
||||
|
||||
> "I don't like that the owner is mandatory. I'd like it to be optional."
|
||||
|
||||
After reading and policy-checked entity data from a location, it is sent through
|
||||
the processor chain looking for processors that implement the
|
||||
`validateEntityKind` step, to see that the data is of a known kind and abides by
|
||||
its schema. There is a builtin processor that implements this for all known core
|
||||
kinds and matches the data against their fixed validation schema. This processor
|
||||
can be replaced when building the backend catalog using the `CatalogBuilder`,
|
||||
with a processor of your own that validates the data differently.
|
||||
This replacement processor must have a name that matches the builtin processor, `BuiltinKindsEntityProcessor`.
|
||||
|
||||
This type of extension is high risk, and may have high impact across the
|
||||
ecosystem depending on the type of change that is made. It is therefore not
|
||||
recommended in normal cases. There will be a large number of plugins and
|
||||
processors - and even the core itself - that make assumptions about the shape of
|
||||
the data and import the typescript data type from the `@backstage/catalog-model`
|
||||
package.
|
||||
|
||||
## Adding New Fields to the Metadata Object
|
||||
|
||||
Example intent:
|
||||
|
||||
> "Our entities have this auxiliary property that I would like to express for
|
||||
> several entity kinds and it doesn't really fit as a spec field."
|
||||
|
||||
The metadata object is currently left open for extension. Any unknown fields
|
||||
found in the metadata will just be stored verbatim in the catalog. However we
|
||||
want to caution against extending the metadata excessively. Firstly, you run the
|
||||
risk of colliding with future extensions to the model. Secondly, it is common
|
||||
that this type of extension lives more comfortably elsewhere - primarily in the
|
||||
metadata labels or annotations, but sometimes you even may want to make a new
|
||||
component type or similar instead.
|
||||
|
||||
There are some situations where metadata can be the right place. If you feel
|
||||
that you have run into such a case and that it would apply to others, do feel
|
||||
free to contact the Backstage maintainers or a support partner to discuss your
|
||||
use case. Maybe we can extend the core model to benefit both you and others.
|
||||
|
||||
## Adding New Fields to the Spec Object of an Existing Kind
|
||||
|
||||
Example intent:
|
||||
|
||||
> "The builtin Component kind is fine but we want to add an additional field to
|
||||
> the spec for describing whether it's in prod or staging."
|
||||
|
||||
A kind's schema validation typically doesn't forbid "unknown" fields in an
|
||||
entity `spec`, and the catalog will happily store whatever is in it. So doing
|
||||
this will usually work from the catalog's point of view.
|
||||
|
||||
Adding fields like this is subject to the same risks as mentioned about metadata
|
||||
extensions above. Firstly, you run the risk of colliding with future extensions
|
||||
to the model. Secondly, it is common that this type of extension lives more
|
||||
comfortably elsewhere - primarily in the metadata labels or annotations, but
|
||||
sometimes you even may want to make a new component type or similar instead.
|
||||
|
||||
There are some situations where the spec can be the right place. If you feel
|
||||
that you have run into such a case and that it would apply to others, do feel
|
||||
free to contact the Backstage maintainers or a support partner to discuss your
|
||||
use case. Maybe we can extend the core model to benefit both you and others.
|
||||
|
||||
## Adding a New Annotation
|
||||
|
||||
Example intents:
|
||||
|
||||
> "Our custom made build system has the concept of a named pipeline-set, and we
|
||||
> want to associate individual components with their corresponding pipeline-sets
|
||||
> so we can show their build status."
|
||||
|
||||
> "We have an alerting system that automatically monitors service health, and
|
||||
> there's this integration key that binds the service to an alerts pool. We want
|
||||
> to be able to show the ongoing alerts for our services in Backstage so it'd be
|
||||
> nice to attach that integration key to the entity somehow."
|
||||
|
||||
Annotations are mainly intended to be consumed by plugins, for feature detection
|
||||
or linking into external systems. Sometimes they are added by humans, but often
|
||||
they are automatically generated at ingestion time by processors. There is a set
|
||||
of [well-known annotations](well-known-annotations.md), but you are free to add
|
||||
additional ones. This carries no risk or impact to other systems as long as you
|
||||
abide by the following naming rules.
|
||||
|
||||
- The `backstage.io` annotation prefix is reserved for use by the Backstage
|
||||
maintainers. Reach out to us if you feel that you would like to make an
|
||||
addition to that prefix.
|
||||
- Annotations that pertain to a well known third party system should ideally be
|
||||
prefixed with a domain, in a way that makes sense to a reader and connects it
|
||||
clearly to the system (or the maker of the system). For example, you might use
|
||||
a `pagerduty.com` prefix for pagerduty related annotations, but maybe not
|
||||
`ldap.com` for LDAP annotations since it's not directly affiliated with or
|
||||
owned by an LDAP foundation/company/similar.
|
||||
- Annotations that have no prefix at all, are considered local to your Backstage
|
||||
instance and can be used freely as such, but you should not make use of them
|
||||
outside of your organization. For example, if you were to open source a plugin
|
||||
that generates or consumes annotations, then those annotations must be
|
||||
properly prefixed with your company domain or a domain that pertains to the
|
||||
annotation at hand.
|
||||
|
||||
## Adding a New Label
|
||||
|
||||
Example intents:
|
||||
|
||||
> "Our process reaping system wants to periodically scrape for components that
|
||||
> have a certain property."
|
||||
|
||||
> "It'd be nice if our service owners could just tag their components somehow to
|
||||
> let the CD system know to automatically generate SRV records or not for that
|
||||
> service."
|
||||
|
||||
Labels are mainly intended to be used for filtering of entities, by external
|
||||
systems that want to find entities that have some certain property. This is
|
||||
sometimes used for feature detection / selection. An example could be to add a
|
||||
label `deployments.my-company.net/register-srv: "true"`.
|
||||
|
||||
At the time of writing this, the use of labels is very limited and we are still
|
||||
settling together with the community on how to best use them. If you feel that
|
||||
your use case fits the labels best, we would appreciate if you let the Backstage
|
||||
maintainers know.
|
||||
|
||||
You are free to add labels. This carries no risk or impact to other systems as
|
||||
long as you abide by the following naming rules.
|
||||
|
||||
- The `backstage.io` label prefix is reserved for use by the Backstage
|
||||
maintainers. Reach out to us if you feel that you would like to make an
|
||||
addition to that prefix.
|
||||
- Labels that pertain to a well known third party system should ideally be
|
||||
prefixed with a domain, in a way that makes sense to a reader and connects it
|
||||
clearly to the system (or the maker of the system). For example, you might use
|
||||
a `pagerduty.com` prefix for pagerduty related labels, but maybe not
|
||||
`ldap.com` for LDAP labels since it's not directly affiliated with or owned by
|
||||
an LDAP foundation/company/similar.
|
||||
- Labels that have no prefix at all, are considered local to your Backstage
|
||||
instance and can be used freely as such, but you should not make use of them
|
||||
outside of your organization. For example, if you were to open source a plugin
|
||||
that generates or consumes labels, then those labels must be properly prefixed
|
||||
with your company domain or a domain that pertains to the label at hand.
|
||||
|
||||
## Adding a New Relation Type
|
||||
|
||||
Example intents:
|
||||
|
||||
> "We have this concept of service maintainership, separate from ownership, that
|
||||
> we would like to make relations to individual users for."
|
||||
|
||||
> "We feel that we want to explicitly model the team-to-global-department
|
||||
> mapping as a relation, because it is core to our org setup and we frequently
|
||||
> query for it."
|
||||
|
||||
Any processor can emit relations for entities as they are being processed, and
|
||||
new processors can be added when building the backend catalog using the
|
||||
`CatalogBuilder`. They can emit relations based on the entity data itself, or
|
||||
based on information gathered from elsewhere. Relations are directed and go from
|
||||
a source entity to a target entity. They are also tied to the entity that
|
||||
originated them - the one that was subject to processing when the relation was
|
||||
emitted. Relations may be dangling (referencing something that does not actually
|
||||
exist by that name in the catalog), and callers need to be aware of that.
|
||||
|
||||
There is a set of [well-known relations](well-known-relations.md), but you are
|
||||
free to emit your own as well. You cannot change the fact that they are directed
|
||||
and have a source and target that have to be an
|
||||
[entity reference](references.md), but you can invent your own types. You do not
|
||||
have to make any changes to the catalog backend in order to accept new relation
|
||||
types.
|
||||
|
||||
At the time of writing this, we do not have any namespacing/prefixing scheme for
|
||||
relation types. The type is also not validated to contain only some particular
|
||||
set of characters. Until rules for this are settled, you should stick to using
|
||||
only letters, dashes and digits, and to avoid collisions with future core
|
||||
relation types, you may want to prefix the type somehow. For example:
|
||||
`myCompany-maintainerOf` + `myCompany-maintainedBy`.
|
||||
|
||||
If you have a suggestion for a relation type to be elevated to the core
|
||||
offering, reach out to the Backstage maintainers or a support partner.
|
||||
|
||||
## Using a Well-Known Relation Type for a New Purpose
|
||||
|
||||
Example intents:
|
||||
|
||||
> "The ownerOf/ownedBy relation types sound like a good fit for expressing how
|
||||
> users are technical owners of our company specific ServiceAccount kind, and we
|
||||
> want to reuse those relation types for that."
|
||||
|
||||
At the time of writing, this is uncharted territory. If the documented use of a
|
||||
relation states that one end of the relation commonly is a User or a Group, for
|
||||
example, then consumers are likely to have conditional statements on the form
|
||||
`if (x.kind === 'User') {} else {}`, which get confused when an unexpected kind
|
||||
appears.
|
||||
|
||||
If you want to extend the use of an established relation type in a way that has
|
||||
an effect outside of your organization, reach out to the Backstage maintainers
|
||||
or a support partner to discuss risk/impact. It may even be that one end of the
|
||||
relation could be considered for addition to the core.
|
||||
|
||||
## Adding a New Status field
|
||||
|
||||
Example intent:
|
||||
|
||||
> "We would like to convey entity statuses through the catalog in a generic way,
|
||||
> as an integration layer. Our monitoring and alerting system has a plugin with
|
||||
> Backstage, and it would be useful if the entity's status field contained the
|
||||
> current alert state close to the actual entity data for anyone to consume. We
|
||||
> find the `status.items` semantics a poor fit, so we would prefer to make our
|
||||
> own custom field under `status` for these purposes."
|
||||
|
||||
We have not yet ventured to define any generic semantics for the `status`
|
||||
object. We recommend sticking with the `status.items` mechanism where possible
|
||||
(see below), since third party consumers will not be able to consume your status
|
||||
information otherwise. Please reach out to the maintainers on Discord or by
|
||||
making a GitHub issue describing your use case if you are interested in this
|
||||
topic.
|
||||
|
||||
## Adding a New Status Item Type
|
||||
|
||||
Example intent:
|
||||
|
||||
> "The semantics of the entity `status.items` field are fine for our needs, but
|
||||
> we want to contribute our own type of status into that array instead of the
|
||||
> catalog specific one."
|
||||
|
||||
This is a simple, low risk way of adding your own status information to
|
||||
entities. Consumers will be able to easily track and display the status together
|
||||
with other types / sources.
|
||||
|
||||
We recommend that any status type that are not strictly private within the
|
||||
organization be namespaced to avoid collisions. Statuses emitted by Backstage
|
||||
core processes will for example be prefixed with `backstage.io/`, your
|
||||
organization may prefix with `my-org.net/`, and `pagerduty.com/active-alerts`
|
||||
could be a sensible complete status item type for that particular external
|
||||
system.
|
||||
|
||||
The mechanics for how to emit custom statuses is not in place yet, so if this is
|
||||
of interest to you, you might consider contacting the maintainers on Discord or
|
||||
my making a GitHub issue describing your use case.
|
||||
[This issue](https://github.com/backstage/backstage/issues/2292) also contains
|
||||
more context.
|
||||
|
||||
## Referencing different environments with the model
|
||||
|
||||
Example intent:
|
||||
|
||||
> "I have multiple versions of my API deployed in different environments so I
|
||||
> want to have `mytool-dev` and `mytool-prod` as different entities."
|
||||
|
||||
While it's possible to have different versions of the same thing represented as
|
||||
separate entities, it's something we generally recommend against. We believe
|
||||
that a developer should be able to just find for example one `Component`
|
||||
representing a service, and to be able to see the different code versions that
|
||||
are deployed throughout your stack within its view. This reasoning works
|
||||
similarly for other kinds as well, such as `API`.
|
||||
|
||||
That being said - sometimes the differences between versions are so large, that
|
||||
they represent what is for all intents and purposes an entirely new entity as
|
||||
seen from the consumer's point of view. This can happen for example for
|
||||
different _significant_ major versions of an API, and in particular if the two
|
||||
major versions coexist in the ecosystem for some time. In those cases, it can be
|
||||
motivated to have one `my-api-v2` and one `my-api-v3` named entity. This matches
|
||||
the end user's expectations when searching for the API, and matches the desire
|
||||
to maybe have separate documentation for the two and similar. But use this
|
||||
sparingly - only do it if the extra modelling burden is outweighed by any
|
||||
potential better clarity for users.
|
||||
|
||||
When writing your custom plugins, we encourage designing them such that they can
|
||||
show all the different variations through environments etc under one canonical
|
||||
reference to your software in the catalog. For example for a continuous
|
||||
deployment plugin, a user is likely to be greatly helped by being able to see
|
||||
the entity's versions deployed in all different environments next to each other
|
||||
in one view. That is also where they might be offered the ability to promote
|
||||
from one environment to the other, do rollbacks, see their relative performance
|
||||
metrics, and similar. This coherency and collection of tooling in one place is
|
||||
where something like Backstage can offer the most value and effectiveness of
|
||||
use. Splitting your entities apart into small islands makes this harder.
|
||||
|
||||
## Implementing custom model extensions
|
||||
|
||||
This section walks you through the steps involved extending the catalog model
|
||||
with a new Entity type.
|
||||
|
||||
### Creating a custom entity definition
|
||||
|
||||
The first step of introducing a custom entity is to define what shape and schema
|
||||
it has. We do this using a TypeScript type, as well as a JSONSchema schema.
|
||||
|
||||
Most of the time you will want to have at least the TypeScript type of your
|
||||
extension available in both frontend and backend code, which means you likely
|
||||
want to have an isomorphic package that houses these types. Within the Backstage
|
||||
main repo the package naming pattern of `<plugin>-common` is used for isomorphic
|
||||
packages, and you may choose to adopt this pattern as well.
|
||||
|
||||
You can generate an isomorphic plugin package by running:`yarn new --select plugin-common`
|
||||
or you can run `yarn new` and then select "plugin-common" from the list of options
|
||||
|
||||
There's at this point no existing templates for generating isomorphic plugins
|
||||
using the `@backstage/cli`. Perhaps the simplest way to get started right now is
|
||||
to copy the contents of one of the existing packages in the main repository,
|
||||
such as `plugins/scaffolder-common`, and rename the folder and file contents to
|
||||
the desired name. This example uses _foobar_ as the plugin name so the plugin
|
||||
will be named _foobar-common_.
|
||||
|
||||
Once you have a common package in place you can start adding your own entity
|
||||
definitions. For the exact details on how to do that we defer to getting
|
||||
inspired by the existing
|
||||
[scaffolder-common](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-common/src/index.ts)
|
||||
package. But in short you will need to declare a TypeScript type and a
|
||||
JSONSchema for the new entity kind.
|
||||
|
||||
### Building a custom processor for the entity
|
||||
|
||||
The next step is to create a custom processor for your new entity kind. This
|
||||
will be used within the catalog to make sure that it's able to ingest and
|
||||
validate entities of our new kind. Just like with the definition package, you
|
||||
can find inspiration in for example the existing
|
||||
[ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts).
|
||||
We also provide a high-level example of what a catalog process for a custom
|
||||
entity might look like:
|
||||
|
||||
```ts
|
||||
import { CatalogProcessor, CatalogProcessorEmit, processingResult } from '@backstage/plugin-catalog-node';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common'
|
||||
import { Entity, entityKindSchemaValidator } from '@backstage/catalog-model';
|
||||
|
||||
// For an example of the JSONSchema format and how to use $ref markers to the
|
||||
// base definitions, see:
|
||||
// https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
|
||||
import { foobarEntityV1alpha1Schema } from '@internal/catalog-model';
|
||||
|
||||
export class FoobarEntitiesProcessor implements CatalogProcessor {
|
||||
// You often end up wanting to support multiple versions of your kind as you
|
||||
// iterate on the definition, so we keep each version inside this array as a
|
||||
// convenient pattern.
|
||||
private readonly validators = [
|
||||
// This is where we use the JSONSchema that we export from our isomorphic
|
||||
// package
|
||||
entityKindSchemaValidator(foobarEntityV1alpha1Schema),
|
||||
];
|
||||
|
||||
// Return processor name
|
||||
getProcessorName(): string {
|
||||
return 'FoobarEntitiesProcessor'
|
||||
}
|
||||
|
||||
// validateEntityKind is responsible for signaling to the catalog processing
|
||||
// engine that this entity is valid and should therefore be submitted for
|
||||
// further processing.
|
||||
async validateEntityKind(entity: Entity): Promise<boolean> {
|
||||
for (const validator of this.validators) {
|
||||
// If the validator throws an exception, the entity will be marked as
|
||||
// invalid.
|
||||
if (validator(entity)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Returning false signals that we don't know what this is, passing the
|
||||
// responsibility to other processors to try to validate it instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
async postProcessEntity(
|
||||
entity: Entity,
|
||||
_location: LocationSpec,
|
||||
emit: CatalogProcessorEmit,
|
||||
): Promise<Entity> {
|
||||
if (
|
||||
entity.apiVersion === 'example.com/v1alpha1' &&
|
||||
entity.kind === 'Foobar'
|
||||
) {
|
||||
const foobarEntity = entity as FoobarEntityV1alpha1;
|
||||
|
||||
// Typically you will want to emit any relations associated with the
|
||||
// entity here.
|
||||
emit(processingResult.relation({ ... }))
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once the processor is created it can be wired up to the catalog via the
|
||||
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/* highlight-add-next-line */
|
||||
builder.addProcessor(new FoobarEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
// ..
|
||||
}
|
||||
```
|
||||
@@ -519,6 +519,9 @@ will be used within the catalog to make sure that it's able to ingest and
|
||||
validate entities of our new kind. Just like with the definition package, you
|
||||
can find inspiration in for example the existing
|
||||
[ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts).
|
||||
|
||||
The custom processor should be created as a separate module for the catalog plugin. For information on how to set that up, see the [plugin docs](../../plugins/backend-plugin.md#creating-a-backend-plugin). Use `yarn new --select backend-module` instead to create a module. For our case, the module ID will be `foobar` and the plugin ID will be `catalog`.
|
||||
|
||||
We also provide a high-level example of what a catalog process for a custom
|
||||
entity might look like:
|
||||
|
||||
@@ -585,20 +588,43 @@ export class FoobarEntitiesProcessor implements CatalogProcessor {
|
||||
}
|
||||
```
|
||||
|
||||
Once the processor is created it can be wired up to the catalog via the
|
||||
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
|
||||
#### New Backend
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
To use your custom processor, you'll need to add the module to your backend as well as integrate your module with the catalog plugin.
|
||||
|
||||
```ts title="plugins/catalog-backend-module-foobar/src/index.ts"
|
||||
import {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
/* highlight-add-next-line */
|
||||
import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
|
||||
import { FoobarEntitiesProcessor } from './providers';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/* highlight-add-next-line */
|
||||
builder.addProcessor(new FoobarEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
// ..
|
||||
}
|
||||
export const catalogModuleFoobarEntitiesProcessor = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'foobar',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
},
|
||||
async init({ catalog }) {
|
||||
catalog.addProcessor(new FoobarEntitiesProcessor());
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default catalogModuleFoobarEntitiesProcessor;
|
||||
```
|
||||
|
||||
This module can then be installed to your backend like so,
|
||||
|
||||
```ts
|
||||
backend.add(import('@internal/plugin-catalog-backend-module-foobar'));
|
||||
```
|
||||
|
||||
#### Legacy Backend
|
||||
|
||||
Look through the [legacy documentation](./extending-the-model--old.md).
|
||||
|
||||
@@ -162,9 +162,13 @@ steps and merging them into the final object which is what is visible from the
|
||||
catalog API. As the final entity itself gets updated, the stitcher makes sure
|
||||
that the search table gets refreshed accordingly as well.
|
||||
|
||||
> Note: The search table mentioned here is not related to the core Search
|
||||
> feature of Backstage. It's rather the table that backs the ability to filter
|
||||
> catalog API query results.
|
||||
:::note Note
|
||||
|
||||
The search table mentioned here is not related to the core Search
|
||||
feature of Backstage. It's rather the table that backs the ability to filter
|
||||
catalog API query results.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -86,12 +86,17 @@ contains more information about the required fields.
|
||||
Once we have a `template.yaml` ready, we can then add it to the software catalog
|
||||
for use by the scaffolder.
|
||||
|
||||
> Note: When you add or modify a template, you will need to refresh the location entity.
|
||||
> Otherwise, Backstage won't display the template in the available templates,
|
||||
> or it will keep showing the old template. You can refresh the location instance by
|
||||
> going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct location entity.
|
||||
> From there, you can click on the refresh icon representing "Scheduled entity refresh" action.
|
||||
> Afterwards, you should see your template updated.
|
||||
:::note Note
|
||||
|
||||
When you add or modify a template, you will need to refresh the location entity.
|
||||
Otherwise, Backstage won't display the template in the available templates,
|
||||
or it will keep showing the old template. You can refresh the location instance by
|
||||
going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct
|
||||
location entity.
|
||||
From there, you can click on the refresh icon representing "Scheduled entity refresh" action.
|
||||
Afterwards, you should see your template updated.
|
||||
|
||||
:::
|
||||
|
||||
You can add the template files to the catalog through
|
||||
[static location configuration](../software-catalog/configuration.md#static-location-configuration),
|
||||
|
||||
+67
-6
@@ -1,10 +1,10 @@
|
||||
---
|
||||
id: authorizing-parameters-steps-and-actions
|
||||
title: 'Authorizing parameters, steps and actions'
|
||||
description: How to authorize part of a template
|
||||
id: authorizing-scaffolder-template-details
|
||||
title: 'Authorizing scaffolder tasks, parameters, steps, and actions'
|
||||
description: How to authorize parts of a template and authorize scaffolder task access
|
||||
---
|
||||
|
||||
The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template.
|
||||
The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks.
|
||||
|
||||
### Authorizing parameters and steps
|
||||
|
||||
@@ -174,7 +174,64 @@ class ExamplePermissionPolicy implements PermissionPolicy {
|
||||
}
|
||||
```
|
||||
|
||||
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex cases.
|
||||
### Authorizing scaffolder tasks
|
||||
|
||||
The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder.
|
||||
|
||||
```ts title="packages/src/backend/plugins/permissions.ts"
|
||||
/* highlight-add-start */
|
||||
import {
|
||||
taskCancelPermission,
|
||||
taskCreatePermission,
|
||||
taskReadPermission,
|
||||
} from '@backstage/plugin-scaffolder-common/alpha';
|
||||
/* highlight-add-end */
|
||||
|
||||
class ExamplePermissionPolicy implements PermissionPolicy {
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
user?: BackstageIdentityResponse,
|
||||
): Promise<PolicyDecision> {
|
||||
/* highlight-add-start */
|
||||
if (isPermission(request.permission, taskCreatePermission)) {
|
||||
if (user?.identity.userEntityRef === 'user:default/spiderman') {
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (isPermission(request.permission, taskCancelPermission)) {
|
||||
if (user?.identity.userEntityRef === 'user:default/spiderman') {
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (isPermission(request.permission, taskReadPermission)) {
|
||||
if (user?.identity.userEntityRef === 'user:default/spiderman') {
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
}
|
||||
/* highlight-add-end */
|
||||
|
||||
return {
|
||||
result: AuthorizeResult.DENY,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources:
|
||||
|
||||
- Read all scaffolder tasks and their associated events/logs.
|
||||
- Cancel any ongoing scaffolder tasks.
|
||||
- Trigger software templates, which effectively creates new scaffolder tasks.
|
||||
|
||||
Any other user would be denied access to these actions/resources.
|
||||
|
||||
Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases.
|
||||
|
||||
### Authorizing in the New Backend System
|
||||
|
||||
@@ -229,4 +286,8 @@ backend.add(customPermissionBackendModule);
|
||||
/* highlight-add-end */
|
||||
```
|
||||
|
||||
> Note: the `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them.
|
||||
:::note Note
|
||||
|
||||
The `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them.
|
||||
|
||||
:::
|
||||
@@ -57,7 +57,11 @@ backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
|
||||
backend.start();
|
||||
```
|
||||
|
||||
> Note: This is a simplified example of what your backend may look like, you may have more code in here then this.
|
||||
:::note Note
|
||||
|
||||
This is a simplified example of what your backend may look like, you may have more code in here then this.
|
||||
|
||||
:::
|
||||
|
||||
## Listing Actions
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@ This is done in your `app-config.yaml` by adding
|
||||
[Backstage integrations](https://backstage.io/docs/integrations/) for the
|
||||
appropriate source code repository for your organization.
|
||||
|
||||
> Note: Integrations may already be set up as part of your `app-config.yaml`.
|
||||
:::note Note
|
||||
|
||||
Integrations may already be set up as part of your `app-config.yaml`.
|
||||
|
||||
:::
|
||||
|
||||
The next step is to [add templates](http://backstage.io/docs/features/software-templates/adding-templates)
|
||||
to your Backstage app.
|
||||
|
||||
@@ -20,10 +20,14 @@ locations like GitHub or GitLab.
|
||||
> Be sure to have covered
|
||||
> [Getting Started with Backstage](../../getting-started) before proceeding.
|
||||
|
||||
> Note: if you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to
|
||||
> use the templates feature.
|
||||
> One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage:
|
||||
> `export NODE_OPTIONS=--no-node-snapshot`
|
||||
:::note Note
|
||||
|
||||
If you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to
|
||||
use the templates feature.
|
||||
One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage:
|
||||
`export NODE_OPTIONS=--no-node-snapshot`
|
||||
|
||||
:::
|
||||
|
||||
The Software Templates are available under `/create`. For local development you
|
||||
should be able to reach them at `http://localhost:3000/create`.
|
||||
|
||||
@@ -5,7 +5,11 @@ title: 'Migrating to react-jsonschema-form@v5'
|
||||
description: Docs on migrating to `react-jsonschema-form`@v5 and the new designs
|
||||
---
|
||||
|
||||
> Note: If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc.
|
||||
:::note Note
|
||||
|
||||
If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc.
|
||||
|
||||
:::
|
||||
|
||||
## What's `react-jsonschema-form`?
|
||||
|
||||
|
||||
@@ -8,10 +8,14 @@ If you want to extend the functionality of the Scaffolder, you can do so
|
||||
by writing custom actions which can be used alongside our
|
||||
[built-in actions](./builtin-actions.md).
|
||||
|
||||
> Note: When adding custom actions, the actions array will **replace the
|
||||
> built-in actions too**. Meaning, you will no longer be able to use them.
|
||||
> If you want to continue using the builtin actions, include them in the actions
|
||||
> array when registering your custom actions, as seen below.
|
||||
:::note Note
|
||||
|
||||
When adding custom actions, the actions array will **replace the
|
||||
built-in actions too**. Meaning, you will no longer be able to use them.
|
||||
If you want to continue using the builtin actions, include them in the actions
|
||||
array when registering your custom actions, as seen below.
|
||||
|
||||
:::
|
||||
|
||||
## Streamlining Custom Action Creation with Backstage CLI
|
||||
|
||||
@@ -226,7 +230,7 @@ const scaffolderModuleCustomExtensions = createBackendModule({
|
||||
async init({ scaffolder /* ..., other dependencies */ }) {
|
||||
// Here you have the opportunity to interact with the extension
|
||||
// point before the plugin itself gets instantiated
|
||||
scaffolder.addActions(new createNewFileAction()); // just an example
|
||||
scaffolder.addActions(createNewFileAction()); // just an example
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -70,6 +70,7 @@ See [TechDocs Architecture](architecture.md) to get an overview of where the bel
|
||||
| GitLab Enterprise | Yes ✅ |
|
||||
| Gitea | Yes ✅ |
|
||||
| AWS CodeCommit | Yes ✅ |
|
||||
| Harness Code | Yes ✅ |
|
||||
|
||||
### File storage providers
|
||||
|
||||
|
||||
@@ -13,9 +13,14 @@ out-of-the box experience.
|
||||
|
||||

|
||||
|
||||
> Note: See below for our recommended deployment architecture which takes care
|
||||
> of stability, scalability and speed. Also look at the
|
||||
> [HOW TO migrate guide](how-to-guides.md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
|
||||
:::note Note
|
||||
|
||||
See below for our recommended deployment architecture which takes care
|
||||
of stability, scalability and speed. Also look at the
|
||||
[HOW TO migrate guide](how-to-guides
|
||||
md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach).
|
||||
|
||||
:::
|
||||
|
||||
When you open a TechDocs site in Backstage, the
|
||||
[TechDocs Reader](./concepts.md#techdocs-reader) makes a request to
|
||||
|
||||
@@ -219,7 +219,11 @@ backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
|
||||
backend.start();
|
||||
```
|
||||
|
||||
> Note: The above is a very simplified example, you may have more content then this in your version.
|
||||
:::note Note
|
||||
|
||||
The above is a very simplified example, you may have more content then this in your version.
|
||||
|
||||
:::
|
||||
|
||||
## Setting the configuration
|
||||
|
||||
|
||||
@@ -499,8 +499,12 @@ Start writing your documentation by adding more markdown (.md) files to this
|
||||
folder (/docs) or replace the content in this file.
|
||||
```
|
||||
|
||||
> Note: The values of `site_name`, `component_id` and `site_description` depends
|
||||
> on how you have configured your `template.yaml`
|
||||
:::note Note
|
||||
|
||||
The values of `site_name`, `component_id` and `site_description` depends
|
||||
on how you have configured your `template.yaml`.
|
||||
|
||||
:::
|
||||
|
||||
Done! You now have support for TechDocs in your own software template!
|
||||
|
||||
@@ -514,7 +518,11 @@ theme:
|
||||
font: false
|
||||
```
|
||||
|
||||
> Note: The addition `name: material` is necessary. Otherwise it will not work
|
||||
:::note Note
|
||||
|
||||
The addition `name: material` is necessary. Otherwise it will not work
|
||||
|
||||
:::
|
||||
|
||||
## How to enable iframes in TechDocs
|
||||
|
||||
@@ -623,12 +631,16 @@ plugins:
|
||||
- kroki
|
||||
```
|
||||
|
||||
> Note: you will very likely want to set a `kroki` `ServerURL` configuration in your
|
||||
> `mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If
|
||||
> you have sensitive information in your organization's diagrams, you should set
|
||||
> up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it
|
||||
> instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config)
|
||||
> for more plugin configuration details.
|
||||
:::note Note
|
||||
|
||||
You will very likely want to set a `kroki` `ServerURL` configuration in your
|
||||
`mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If
|
||||
you have sensitive information in your organization's diagrams, you should set
|
||||
up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it
|
||||
instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config)
|
||||
for more plugin configuration details.
|
||||
|
||||
:::
|
||||
|
||||
4. **Add mermaid code into TechDocs:**
|
||||
|
||||
@@ -766,7 +778,11 @@ backend.add(techdocsCustomBuildStrategy());
|
||||
backend.start();
|
||||
```
|
||||
|
||||
> Note: You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already.
|
||||
:::note Note
|
||||
|
||||
You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already.
|
||||
|
||||
:::
|
||||
|
||||
## How to use other mkdocs plugins?
|
||||
|
||||
|
||||
@@ -169,17 +169,21 @@ permissions to:
|
||||
- `s3:ListBucket` - To retrieve bucket metadata
|
||||
- `s3:GetObject` - To retrieve files from the bucket
|
||||
|
||||
> Note: If you need to migrate documentation objects from an older-style path
|
||||
> format including case-sensitive entity metadata, you will need to add some
|
||||
> additional permissions to be able to perform the migration, including:
|
||||
>
|
||||
> - `s3:PutBucketAcl` (for copying files,
|
||||
> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html))
|
||||
> - `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files,
|
||||
> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html))
|
||||
>
|
||||
> ...And you will need to ensure the permissions apply to the bucket itself, as
|
||||
> well as all resources under the bucket. See the example policy below.
|
||||
:::note Note
|
||||
|
||||
If you need to migrate documentation objects from an older-style path
|
||||
format including case-sensitive entity metadata, you will need to add some
|
||||
additional permissions to be able to perform the migration, including:
|
||||
|
||||
- `s3:PutBucketAcl` (for copying files,
|
||||
[more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html))
|
||||
- `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files,
|
||||
[more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html))
|
||||
|
||||
...And you will need to ensure the permissions apply to the bucket itself, as
|
||||
well as all resources under the bucket. See the example policy below.
|
||||
|
||||
:::
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ description: App instances
|
||||
|
||||
## The App Instance
|
||||
|
||||
The app instance is main entry point for creating a frontend app. It doesn't do much on its own, but is instead responsible for wiring things together that have been provided as features from other parts of the system.
|
||||
The app instance is the main entry point for creating a frontend app. It doesn't do much on its own, but is instead responsible for wiring things together that have been provided as features from other parts of the system.
|
||||
|
||||
Below is a simple example of how to create and render an app instance:
|
||||
|
||||
@@ -35,7 +35,7 @@ ReactDOM.createRoot(rootEl).render(app);
|
||||
|
||||
We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./03-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./06-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`.
|
||||
|
||||
It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./03-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and pass along data to its parent. The following diagram illustrates the shape of a small app extension tree.
|
||||
It is possible to explicitly install features when creating the app, although typically these will instead be discovered automatically which we'll explore later on. Nevertheless these features are what build out the actual functionality of the app by providing [Extensions](./03-extensions.md). These extensions are wired together by the app into a tree structure known as the app extension tree. Each node in this tree receives data from its child nodes, and passes along data to its parent. The following diagram illustrates the shape of a small app extension tree.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Each extensions has a number of different properties that define how it behaves
|
||||
Update this to be 3 different sections: name, kind and namespace
|
||||
-->
|
||||
|
||||
The ID of an extension is used to uniquely identity it, and it should ideally by unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics.
|
||||
The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics.
|
||||
|
||||
### Output
|
||||
|
||||
@@ -183,7 +183,7 @@ const navigationExtension = createExtension({
|
||||
});
|
||||
```
|
||||
|
||||
The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly on attached extension.
|
||||
The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can be attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly one attached extension.
|
||||
|
||||
So how can we now attach the output to the parent extension's input? If we think about a navigation component, like the Sidebar in Backstage, there might be plugins that want to attach a link to their plugin to this navigation component. In this case the plugin only needs to know the extension `id` and the name of the extension `input` to attach the extension `output` returned by the `factory` to the specified extension:
|
||||
|
||||
|
||||
@@ -274,6 +274,21 @@ Note that we are not importing and using the `RouteRef`s directly in the app, an
|
||||
|
||||
Another thing to note is that this indirection in the routing is particularly useful for open source plugins that need to provide flexibility in how they are integrated. For plugins that you build internally for your own Backstage application, you can choose to use direct imports or even concrete route path strings directly. Although there can be some benefits to using the full routing system even in internal plugins: it can help you structure your routes, and as you will see further down it also helps you manage route parameters.
|
||||
|
||||
### Default Targets for External Route References
|
||||
|
||||
It is possible to define a default target for an external route reference, potentially removing the need to bind the route in the app. This reduces the need for configuration when installing new plugins through providing a sensible default. It is of course still possible to override the route binding in the app.
|
||||
|
||||
The default target uses the same syntax as the route binding configuration, and will only be used if the target plugin and route exist. For example, this is how the catalog can define a default target for the create component external route in a way that removes the need for the binding in the previous example:
|
||||
|
||||
```tsx title="plugins/catalog/src/routes.ts"
|
||||
import { createExternalRouteRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const createComponentExternalRouteRef = createExternalRouteRef({
|
||||
// highlight-next-line
|
||||
defaultTarget: 'scaffolder.createComponent',
|
||||
});
|
||||
```
|
||||
|
||||
### Optional External Route References
|
||||
|
||||
It is possible to define an `ExternalRouteRef` as optional, so it is not required to bind it in the app.
|
||||
|
||||
@@ -10,7 +10,11 @@ description: Testing plugins in the frontend system
|
||||
|
||||
# Testing Frontend Plugins
|
||||
|
||||
> NOTE: The new frontend system is in alpha, and some plugins do not yet fully implement it.
|
||||
:::note Note
|
||||
|
||||
The new frontend system is in alpha, and some plugins do not yet fully implement it.
|
||||
|
||||
:::
|
||||
|
||||
Utilities for testing frontend features and components are available in `@backstage/frontend-test-utils`.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ starting point that's meant to be evolved.
|
||||
|
||||
The Backstage CLI has a command to bump all `@backstage` packages and
|
||||
dependencies you're using to the latest versions:
|
||||
[versions:bump](https://backstage.io/docs/local-dev/cli-commands#versionsbump).
|
||||
[versions:bump](https://backstage.io/docs/tooling/cli/03-commands#versionsbump).
|
||||
|
||||
```bash
|
||||
yarn backstage-cli versions:bump
|
||||
@@ -70,7 +70,7 @@ example, depends on global referential equality. This can cause problems in
|
||||
Backstage with API lookup, or config loading.
|
||||
|
||||
To help resolve these situations, the Backstage CLI has
|
||||
[versions:check](https://backstage.io/docs/local-dev/cli-commands#versionscheck). This
|
||||
[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:
|
||||
|
||||
|
||||
@@ -68,11 +68,15 @@ integrations:
|
||||
|
||||
If you do not specify the `organizations` field the credential will be used for all organizations for which no other credential is configured.
|
||||
|
||||
> Note: An Azure DevOps provider is added automatically at startup for
|
||||
> convenience, so you only need to list it if you want to supply a
|
||||
> [personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate),
|
||||
> a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity),
|
||||
> or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity)
|
||||
:::note Note
|
||||
|
||||
An Azure DevOps provider is added automatically at startup for
|
||||
convenience, so you only need to list it if you want to supply a
|
||||
[personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate),
|
||||
a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity),
|
||||
or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity)
|
||||
|
||||
:::
|
||||
|
||||
The configuration is a structure with these elements:
|
||||
|
||||
@@ -86,9 +90,11 @@ The `credentials` element is a structure with these elements:
|
||||
- `tenantId`: The tenant ID of the service principal (required for service principal)
|
||||
- `personalAccessToken`: The personal access token (required for personal access token)
|
||||
|
||||
> Note:
|
||||
>
|
||||
> - You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations
|
||||
> - You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations
|
||||
> - You can only specify one credential per host without any organizations specified
|
||||
> - The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
|
||||
:::note Note
|
||||
|
||||
- You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations
|
||||
- You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations
|
||||
- You can only specify one credential per host without any organizations specified
|
||||
- The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
|
||||
|
||||
:::
|
||||
|
||||
@@ -22,10 +22,18 @@ integrations:
|
||||
appPassword: ${BITBUCKET_CLOUD_PASSWORD}
|
||||
```
|
||||
|
||||
> Note: A public Bitbucket Cloud provider is added automatically at startup for
|
||||
> convenience, so you only need to list it if you want to supply credentials.
|
||||
:::note Note
|
||||
|
||||
> Note: The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work
|
||||
A public Bitbucket Cloud provider is added automatically at startup for
|
||||
convenience, so you only need to list it if you want to supply credentials.
|
||||
|
||||
:::
|
||||
|
||||
:::note Note
|
||||
|
||||
The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work.
|
||||
|
||||
:::
|
||||
|
||||
Directly under the `bitbucketCloud` key is a list of provider configurations, where
|
||||
you can list the Bitbucket Cloud providers you want to fetch data from.
|
||||
|
||||
@@ -200,6 +200,7 @@ This provider supports multiple organizations via unique provider IDs.
|
||||
- **`filters`** _(optional)_:
|
||||
- **`branch`** _(optional)_:
|
||||
String used to filter results based on the branch name.
|
||||
Defaults to the default Branch of the repository.
|
||||
- **`repository`** _(optional)_:
|
||||
Regular expression used to filter results based on the repository name.
|
||||
- **`topic`** _(optional)_:
|
||||
|
||||
@@ -130,8 +130,12 @@ catalog:
|
||||
|
||||
This provider supports multiple organizations via unique provider IDs.
|
||||
|
||||
> **Note:** It is possible but certainly not recommended to skip the provider ID level.
|
||||
> If you do so, `default` will be used as provider ID.
|
||||
:::note Note
|
||||
|
||||
It is possible but certainly not recommended to skip the provider ID level.
|
||||
If you do so, `default` will be used as provider ID.
|
||||
|
||||
:::
|
||||
|
||||
- **`catalogPath`** _(optional)_:
|
||||
Default: `/catalog-info.yaml`.
|
||||
@@ -141,6 +145,7 @@ This provider supports multiple organizations via unique provider IDs.
|
||||
- **`filters`** _(optional)_:
|
||||
- **`branch`** _(optional)_:
|
||||
String used to filter results based on the branch name.
|
||||
Defaults to the default Branch of the repository.
|
||||
- **`repository`** _(optional)_:
|
||||
Regular expression used to filter results based on the repository name.
|
||||
- **`topic`** _(optional)_:
|
||||
|
||||
@@ -42,7 +42,7 @@ yarn backstage-cli create-github-app <github org>
|
||||
```
|
||||
|
||||
You can read more about the
|
||||
[`backstage-cli create-github-app`](../../local-dev/cli-commands.md#create-github-app) command.
|
||||
[`backstage-cli create-github-app`](../../tooling/cli/03-commands.md#create-github-app) command.
|
||||
|
||||
Once you've gone through the CLI command, it should produce a YAML file in the
|
||||
root of the project which you can then use as an `include` in your
|
||||
|
||||
@@ -17,9 +17,13 @@ is a hierarchy of
|
||||
[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
|
||||
entities that mirror your org setup.
|
||||
|
||||
> Note: This adds `User` and `Group` entities to the catalog, but does not
|
||||
> provide authentication. See the
|
||||
> [GitHub auth provider](../../auth/github/provider.md) for that.
|
||||
:::note Note
|
||||
|
||||
This adds `User` and `Group` entities to the catalog, but does not
|
||||
provide authentication. See the
|
||||
[GitHub auth provider](../../auth/github/provider.md) for that.
|
||||
|
||||
:::
|
||||
|
||||
## Permissions
|
||||
|
||||
|
||||
@@ -136,7 +136,11 @@ To use the discovery provider, you'll need a GitLab integration
|
||||
[set up](locations.md) with a `token`. Then you can add a provider config per group
|
||||
to the catalog configuration.
|
||||
|
||||
> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
|
||||
:::note Note
|
||||
|
||||
If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
|
||||
|
||||
:::
|
||||
|
||||
```yaml title="app-config.yaml"
|
||||
catalog:
|
||||
|
||||
@@ -158,7 +158,11 @@ amount of data, this can take significant time and resources.
|
||||
The token used must have the `read_api` scope, and the Users and Groups fetched
|
||||
will be those visible to the account which provisioned the token.
|
||||
|
||||
> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
|
||||
:::note Note
|
||||
|
||||
If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below.
|
||||
|
||||
:::
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
---
|
||||
id: org--old
|
||||
title: LDAP Organizational Data
|
||||
sidebar_label: Org Data
|
||||
# prettier-ignore
|
||||
description: Setting up ingestion of organizational data from LDAP
|
||||
---
|
||||
|
||||
The Backstage catalog can be set up to ingest organizational data - users and
|
||||
groups - directly from an LDAP compatible service. The result is a hierarchy of
|
||||
[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
|
||||
[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
|
||||
entities that mirror your org setup.
|
||||
|
||||
## Supported vendors
|
||||
|
||||
Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md).
|
||||
|
||||
## Installation
|
||||
|
||||
This guide will use the Entity Provider method. If you for some reason prefer
|
||||
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
|
||||
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
|
||||
```
|
||||
|
||||
:::note Note
|
||||
|
||||
When configuring to use a Provider instead of a Processor you do not
|
||||
need to add a _location_ pointing to your LDAP server
|
||||
|
||||
:::
|
||||
|
||||
Update the catalog plugin initialization in your backend to add the provider and
|
||||
schedule it:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
|
||||
/* highlight-add-start */
|
||||
// The target parameter below needs to match the ldap.providers.target
|
||||
// value specified in your app-config.
|
||||
builder.addEntityProvider(
|
||||
LdapOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'our-ldap-master',
|
||||
target: 'ldaps://ds.example.net',
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 60 },
|
||||
timeout: { minutes: 15 },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
After this, you also have to add some configuration in your app-config that
|
||||
describes what you want to import for that target.
|
||||
|
||||
## Configuration
|
||||
|
||||
The following configuration is a small example of how a setup could look for
|
||||
importing groups and users from a corporate LDAP server.
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
providers:
|
||||
- target: ldaps://ds.example.net
|
||||
bind:
|
||||
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
secret: ${LDAP_SECRET}
|
||||
users:
|
||||
dn: ou=people,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (uid=*)
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
groups:
|
||||
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (&(objectClass=some-group-class)(!(groupType=email)))
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
```
|
||||
|
||||
There may be many providers, each targeting a specific `target` which is
|
||||
supposed to match the `target` of a dedicated provider instance - i.e., you will
|
||||
add one entity provider class instance per target to ingest from.
|
||||
|
||||
These config blocks have a lot of options in them, so we will describe each
|
||||
"root" key within the block separately.
|
||||
|
||||
### target
|
||||
|
||||
This is the URL of the targeted server, typically on the form
|
||||
`ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net`
|
||||
without SSL.
|
||||
|
||||
#### target.tls.keys
|
||||
|
||||
`keys` in TLS options specifies location of a file, that contains private keys
|
||||
to establish connection with your LDAP server, in PEM format. See an example
|
||||
for Google Secure LDAP Service below.
|
||||
|
||||
#### target.tls.certs
|
||||
|
||||
`certs` in TLS options specifies location of a file, that contains certificate
|
||||
chains to establish connection with your LDAP server, in PEM format. See an
|
||||
example for Google Secure LDAP Service below.
|
||||
|
||||
### bind
|
||||
|
||||
The bind block specifies how the plugin should bind (essentially, to
|
||||
authenticate) towards the server. It has the following fields.
|
||||
|
||||
```yaml
|
||||
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
secret: ${LDAP_SECRET}
|
||||
```
|
||||
|
||||
The `dn` is the full LDAP Distinguished Name for the user that the plugin
|
||||
authenticates itself as. At this point, only regular user based authentication
|
||||
is supported.
|
||||
|
||||
The `secret` is the password of the same user. In this example, it is given in
|
||||
the form of an environment variable `LDAP_SECRET`, that has to be set when the
|
||||
backend starts.
|
||||
|
||||
### users
|
||||
|
||||
The `users` block defines the settings that govern the reading and
|
||||
interpretation of users. Its fields are explained in separate sections below.
|
||||
|
||||
#### users.dn
|
||||
|
||||
The DN under which users are stored, e.g.
|
||||
`ou=people,ou=example,dc=example,dc=net`.
|
||||
|
||||
#### users.options
|
||||
|
||||
The search options to use when sending the query to the server, when reading all
|
||||
users. All the options are shown below, with their default values, but they are
|
||||
all optional.
|
||||
|
||||
```yaml
|
||||
options:
|
||||
# One of 'base', 'one', or 'sub'.
|
||||
scope: one
|
||||
# The filter is the one that you commonly will want to specify explicitly. It
|
||||
# is a string on the standard LDAP query format. Use it to select out the set
|
||||
# of users that are of actual interest to ingest. For example, you may want
|
||||
# to filter out disabled users.
|
||||
filter: (uid=*)
|
||||
# The attribute selectors for each item, as passed to the LDAP server.
|
||||
attributes: ['*', '+']
|
||||
# This field is either 'false' to disable paging when reading from the
|
||||
# server, or an object on the form '{ pageSize: 100, pagePause: true }' that
|
||||
# specifies the details of how the paging shall work.
|
||||
paged: false
|
||||
```
|
||||
|
||||
#### users.set
|
||||
|
||||
This optional piece lets you specify a number of JSON paths (on a.b.c form) and
|
||||
hard coded values to set on those paths. This can be useful for example if you
|
||||
want to hard code a namespace or similar on the generated entities.
|
||||
|
||||
```yaml
|
||||
set:
|
||||
# Just an example; the key and value can be anything
|
||||
metadata.namespace: 'ldap'
|
||||
```
|
||||
|
||||
#### users.map
|
||||
|
||||
Mappings from well known entity fields, to LDAP attribute names. This is where
|
||||
you are able to define how to interpret the attributes of each LDAP result item,
|
||||
and to move them into the corresponding entity fields. All the options are shown
|
||||
below, with their default values, but they are all optional.
|
||||
|
||||
If you leave out an optional mapping, it will still be copied using that default
|
||||
value. For example, even if you do not put in the field `displayName` in your
|
||||
config, the provider will still copy the attribute `cn` into the entity field
|
||||
`spec.profile.displayName`.
|
||||
|
||||
```yaml
|
||||
map:
|
||||
# The name of the attribute that holds the relative
|
||||
# distinguished name of each entry.
|
||||
rdn: uid
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the metadata.name field of the entity.
|
||||
name: uid
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the metadata.description field of the entity.
|
||||
description: description
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.displayName field of the entity.
|
||||
displayName: cn
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.email field of the entity.
|
||||
email: mail
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.picture field of the entity.
|
||||
picture: <nothing, left out>
|
||||
# The name of the attribute that shall be used for the values of
|
||||
# the spec.memberOf field of the entity.
|
||||
memberOf: memberOf
|
||||
```
|
||||
|
||||
### groups
|
||||
|
||||
The `groups` block defines the settings that govern the reading and
|
||||
interpretation of groups. Its fields are explained in separate sections below.
|
||||
|
||||
#### groups.dn
|
||||
|
||||
The DN under which groups are stored, e.g.
|
||||
`ou=people,ou=example,dc=example,dc=net`.
|
||||
|
||||
#### groups.options
|
||||
|
||||
The search options to use when sending the query to the server, when reading all
|
||||
groups. All the options are shown below, with their default values, but they are
|
||||
all optional.
|
||||
|
||||
```yaml
|
||||
options:
|
||||
# One of 'base', 'one', or 'sub'.
|
||||
scope: one
|
||||
# The filter is the one that you commonly will want to specify explicitly. It
|
||||
# is a string on the standard LDAP query format. Use it to select out the set
|
||||
# of groups that are of actual interest to ingest. For example, you may want
|
||||
# to filter out disabled groups.
|
||||
filter: (&(objectClass=some-group-class)(!(groupType=email)))
|
||||
# The attribute selectors for each item, as passed to the LDAP server.
|
||||
attributes: ['*', '+']
|
||||
# This field is either 'false' to disable paging when reading from the
|
||||
# server, or an object on the form '{ pageSize: 100, pagePause: true }' that
|
||||
# specifies the details of how the paging shall work.
|
||||
paged: false
|
||||
```
|
||||
|
||||
#### groups.set
|
||||
|
||||
This optional piece lets you specify a number of JSON paths (on a.b.c form) and
|
||||
hard coded values to set on those paths. This can be useful for example if you
|
||||
want to hard code a namespace or similar on the generated entities.
|
||||
|
||||
```yaml
|
||||
set:
|
||||
# Just an example; the key and value can be anything
|
||||
metadata.namespace: 'ldap'
|
||||
```
|
||||
|
||||
#### groups.map
|
||||
|
||||
Mappings from well known entity fields, to LDAP attribute names. This is where
|
||||
you are able to define how to interpret the attributes of each LDAP result item,
|
||||
and to move them into the corresponding entity fields. All of the options are
|
||||
shown below, with their default values, but they are all optional.
|
||||
|
||||
If you leave out an optional mapping, it will still be copied using that default
|
||||
value. For example, even if you do not put in the field `displayName` in your
|
||||
config, the provider will still copy the attribute `cn` into the entity field
|
||||
`spec.profile.displayName`. If the target field is optional, such as the display
|
||||
name, the importer will accept missing attributes and just leave the target
|
||||
field unset. If the target field is mandatory, such as the name of the entity,
|
||||
validation will fail if the source attribute is missing.
|
||||
|
||||
```yaml
|
||||
map:
|
||||
# The name of the attribute that holds the relative
|
||||
# distinguished name of each entry. This value is copied into a
|
||||
# well known annotation to be able to query by it later.
|
||||
rdn: cn
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the metadata.name field of the entity.
|
||||
name: cn
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the metadata.description field of the entity.
|
||||
description: description
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.type field of the entity.
|
||||
type: groupType
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.displayName field of the entity.
|
||||
displayName: cn
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.email field of the entity.
|
||||
email: <nothing, left out>
|
||||
# The name of the attribute that shall be used for the value of
|
||||
# the spec.profile.picture field of the entity.
|
||||
picture: <nothing, left out>
|
||||
# The name of the attribute that shall be used for the values of
|
||||
# the spec.parent field of the entity.
|
||||
memberOf: memberOf
|
||||
# The name of the attribute that shall be used for the values of
|
||||
# the spec.children field of the entity.
|
||||
members: member
|
||||
```
|
||||
|
||||
## Customize the Provider
|
||||
|
||||
In case you want to customize the ingested entities, the provider allows to pass
|
||||
transformers for users and groups. Here we will show an example of overriding
|
||||
the group transformer.
|
||||
|
||||
1. Create a transformer:
|
||||
|
||||
```ts
|
||||
export async function myGroupTransformer(
|
||||
vendor: LdapVendor,
|
||||
config: GroupConfig,
|
||||
group: SearchEntry,
|
||||
): Promise<GroupEntity | undefined> {
|
||||
// Transformations may change namespace, change entity naming pattern, fill
|
||||
// profile with more or other details...
|
||||
|
||||
// Create the group entity on your own, or wrap the default transformer
|
||||
return await defaultGroupTransformer(vendor, config, group);
|
||||
}
|
||||
```
|
||||
|
||||
2. Configure the provider with the transformer:
|
||||
|
||||
```ts
|
||||
const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'our-ldap-master',
|
||||
target: 'ldaps://ds.example.net',
|
||||
logger: env.logger,
|
||||
groupTransformer: myGroupTransformer,
|
||||
});
|
||||
```
|
||||
|
||||
## Using a Processor instead of a Provider
|
||||
|
||||
An alternative to using the Provider for ingesting LDAP entries is to use a
|
||||
Processor. This is the old way that's based on registering locations with the
|
||||
proper type and target, triggering the processor to run.
|
||||
|
||||
The drawback of this method is that it will leave orphaned Group/User entities
|
||||
whenever they are deleted on your LDAP server, and you cannot control the
|
||||
frequency with which they are refreshed, separately from other processors.
|
||||
|
||||
### Processor Installation
|
||||
|
||||
The `LdapOrgReaderProcessor` is not registered by default, so you have to
|
||||
register it in the catalog plugin:
|
||||
|
||||
```typescript title="packages/backend/src/plugins/catalog.ts"
|
||||
builder.addProcessor(
|
||||
LdapOrgReaderProcessor.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
### Driving LDAP Org Processor Ingestion with Locations
|
||||
|
||||
Locations point out the specific org(s) you want to import. The `type` of these
|
||||
locations must be `ldap-org`, and the `target` must point to the exact URL
|
||||
(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can
|
||||
have several such location entries if you want, but typically you will have just
|
||||
one.
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: ldap-org
|
||||
target: ldaps://ds.example.net
|
||||
rules:
|
||||
- allow: [User, Group]
|
||||
```
|
||||
|
||||
### Example configurations
|
||||
|
||||
#### Google Secure LDAP Service
|
||||
|
||||
To sync Google Workspace/Cloud Identity organization data to users and groups in backstage,
|
||||
you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first.
|
||||
|
||||
Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration,
|
||||
as mentioned below. `keys` and `certs` specify the location of files that are generated
|
||||
while configuring Secure LDAP Service above.
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
providers:
|
||||
- target: ldaps://ldap.google.com:636
|
||||
tls:
|
||||
rejectUnauthorized: false
|
||||
keys: '/var/secrets/tls/gldap.key'
|
||||
certs: '/var/secrets/tls/gldap.crt'
|
||||
users:
|
||||
# users configuration comes here
|
||||
groups:
|
||||
# groups configuration comes here
|
||||
```
|
||||
+69
-150
@@ -18,9 +18,6 @@ Backstage in general supports OpenLDAP compatible vendors, as well as Active Dir
|
||||
|
||||
## Installation
|
||||
|
||||
This guide will use the Entity Provider method. If you for some reason prefer
|
||||
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.
|
||||
|
||||
@@ -29,43 +26,30 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package.
|
||||
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
|
||||
```
|
||||
|
||||
> Note: When configuring to use a Provider instead of a Processor you do not
|
||||
> need to add a _location_ pointing to your LDAP server
|
||||
Next add the basic configuration to `app-config.yaml`
|
||||
|
||||
Update the catalog plugin initialization in your backend to add the provider and
|
||||
schedule it:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
|
||||
/* highlight-add-start */
|
||||
// The target parameter below needs to match the ldap.providers.target
|
||||
// value specified in your app-config.
|
||||
builder.addEntityProvider(
|
||||
LdapOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'our-ldap-master',
|
||||
target: 'ldaps://ds.example.net',
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 60 },
|
||||
timeout: { minutes: 15 },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
}
|
||||
```yaml title="app-config.yaml"
|
||||
catalog:
|
||||
providers:
|
||||
ldapOrg:
|
||||
default:
|
||||
target: ldaps://ds.example.net
|
||||
bind:
|
||||
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
secret: ${LDAP_SECRET}
|
||||
schedule:
|
||||
frequency: PT1H
|
||||
timeout: PT15M
|
||||
```
|
||||
|
||||
After this, you also have to add some configuration in your app-config that
|
||||
describes what you want to import for that target.
|
||||
Finally, updated your backend by adding the following line:
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
|
||||
/* highlight-add-start */
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-ldap'));
|
||||
/* highlight-add-end */
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -73,34 +57,32 @@ The following configuration is a small example of how a setup could look for
|
||||
importing groups and users from a corporate LDAP server.
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
catalog:
|
||||
providers:
|
||||
- target: ldaps://ds.example.net
|
||||
bind:
|
||||
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
secret: ${LDAP_SECRET}
|
||||
users:
|
||||
dn: ou=people,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (uid=*)
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
groups:
|
||||
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (&(objectClass=some-group-class)(!(groupType=email)))
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
ldapOrg:
|
||||
default:
|
||||
target: ldaps://ds.example.net
|
||||
bind:
|
||||
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
secret: ${LDAP_SECRET}
|
||||
users:
|
||||
dn: ou=people,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (uid=*)
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
groups:
|
||||
dn: ou=access,ou=groups,ou=example,dc=example,dc=net
|
||||
options:
|
||||
filter: (&(objectClass=some-group-class)(!(groupType=email)))
|
||||
map:
|
||||
description: l
|
||||
set:
|
||||
metadata.customField: 'hello'
|
||||
```
|
||||
|
||||
There may be many providers, each targeting a specific `target` which is
|
||||
supposed to match the `target` of a dedicated provider instance - i.e., you will
|
||||
add one entity provider class instance per target to ingest from.
|
||||
|
||||
These config blocks have a lot of options in them, so we will describe each
|
||||
"root" key within the block separately.
|
||||
|
||||
@@ -317,97 +299,34 @@ map:
|
||||
## Customize the Provider
|
||||
|
||||
In case you want to customize the ingested entities, the provider allows to pass
|
||||
transformers for users and groups. Here we will show an example of overriding
|
||||
the group transformer.
|
||||
transformers for users and groups.
|
||||
|
||||
1. Create a transformer:
|
||||
Transformers can be configured by extending `ldapOrgEntityProviderTransformExtensionPoint`. Here is an example:
|
||||
|
||||
```ts
|
||||
export async function myGroupTransformer(
|
||||
vendor: LdapVendor,
|
||||
config: GroupConfig,
|
||||
group: SearchEntry,
|
||||
): Promise<GroupEntity | undefined> {
|
||||
// Transformations may change namespace, change entity naming pattern, fill
|
||||
// profile with more or other details...
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { ldapOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-ldap';
|
||||
import { myUserTransformer, myGroupTransformer } from './transformers';
|
||||
|
||||
// Create the group entity on your own, or wrap the default transformer
|
||||
return await defaultGroupTransformer(vendor, config, group);
|
||||
}
|
||||
```
|
||||
|
||||
2. Configure the provider with the transformer:
|
||||
|
||||
```ts
|
||||
const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'our-ldap-master',
|
||||
target: 'ldaps://ds.example.net',
|
||||
logger: env.logger,
|
||||
groupTransformer: myGroupTransformer,
|
||||
});
|
||||
```
|
||||
|
||||
## Using a Processor instead of a Provider
|
||||
|
||||
An alternative to using the Provider for ingesting LDAP entries is to use a
|
||||
Processor. This is the old way that's based on registering locations with the
|
||||
proper type and target, triggering the processor to run.
|
||||
|
||||
The drawback of this method is that it will leave orphaned Group/User entities
|
||||
whenever they are deleted on your LDAP server, and you cannot control the
|
||||
frequency with which they are refreshed, separately from other processors.
|
||||
|
||||
### Processor Installation
|
||||
|
||||
The `LdapOrgReaderProcessor` is not registered by default, so you have to
|
||||
register it in the catalog plugin:
|
||||
|
||||
```typescript title="packages/backend/src/plugins/catalog.ts"
|
||||
builder.addProcessor(
|
||||
LdapOrgReaderProcessor.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
backend.add(
|
||||
createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'ldap-extensions',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
/* highlight-add-start */
|
||||
ldapTransformers: ldapOrgEntityProviderTransformExtensionPoint,
|
||||
/* highlight-add-end */
|
||||
},
|
||||
async init({ ldapTransformers }) {
|
||||
/* highlight-add-start */
|
||||
ldapTransformers.setUserTransformer(myUserTransformer);
|
||||
ldapTransformers.setGroupTransformer(myGroupTransformer);
|
||||
/* highlight-add-end */
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
### Driving LDAP Org Processor Ingestion with Locations
|
||||
|
||||
Locations point out the specific org(s) you want to import. The `type` of these
|
||||
locations must be `ldap-org`, and the `target` must point to the exact URL
|
||||
(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can
|
||||
have several such location entries if you want, but typically you will have just
|
||||
one.
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: ldap-org
|
||||
target: ldaps://ds.example.net
|
||||
rules:
|
||||
- allow: [User, Group]
|
||||
```
|
||||
|
||||
### Example configurations
|
||||
|
||||
#### Google Secure LDAP Service
|
||||
|
||||
To sync Google Workspace/Cloud Identity organization data to users and groups in backstage,
|
||||
you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first.
|
||||
|
||||
Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration,
|
||||
as mentioned below. `keys` and `certs` specify the location of files that are generated
|
||||
while configuring Secure LDAP Service above.
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
providers:
|
||||
- target: ldaps://ldap.google.com:636
|
||||
tls:
|
||||
rejectUnauthorized: false
|
||||
keys: '/var/secrets/tls/gldap.key'
|
||||
certs: '/var/secrets/tls/gldap.crt'
|
||||
users:
|
||||
# users configuration comes here
|
||||
groups:
|
||||
# groups configuration comes here
|
||||
```
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: getting-started--new
|
||||
title: Getting Started
|
||||
description: How to get started with the permission framework as an integrator
|
||||
---
|
||||
|
||||
Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy.
|
||||
|
||||
### Upgrade to the latest version of Backstage
|
||||
|
||||
To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade!
|
||||
|
||||
### Supply an identity resolver to populate group membership on sign in
|
||||
|
||||
**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up.
|
||||
|
||||
Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity.
|
||||
|
||||
[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in.
|
||||
|
||||
## Enable and test the permissions system
|
||||
|
||||
All you need to do now is enable the permissions system in your Backstage instance!
|
||||
|
||||
1. Set the property `permission.enabled` to `true` in `app-config.yaml`.
|
||||
|
||||
```yaml title="app-config.yaml"
|
||||
permission:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Congratulations! Now that the framework is configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)!
|
||||
@@ -8,7 +8,11 @@ If you prefer to watch a video instead, you can start with this video introducti
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/EQr9tFClgG0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases.
|
||||
:::note Note
|
||||
|
||||
This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases.
|
||||
|
||||
:::
|
||||
|
||||
Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ For this tutorial, we've automatically exported all permissions from this file (
|
||||
|
||||
:::note Note
|
||||
|
||||
We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/local-dev/cli-build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies.
|
||||
We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ This approach will work for simple cases, but it has a downside: it forces us to
|
||||
|
||||
To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior.
|
||||
|
||||
> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format.
|
||||
:::note Note
|
||||
|
||||
In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format.
|
||||
|
||||
:::
|
||||
|
||||
## Creating the read permission
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ In the previous sections, we learned how to protect our plugin's backend API rou
|
||||
|
||||
Take, for example, the "Add" button in our todo list application. When a user clicks this button, the frontend makes a `POST` request to the `/todos` route of our backend. If a user tries to add a todo but is not authorized, they will have no way of knowing this until they perform the action and are faced with an error. This is a poor user experience. We can do better by disabling the add button.
|
||||
|
||||
> Note: Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component.
|
||||
:::note Note
|
||||
|
||||
Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component.
|
||||
|
||||
:::
|
||||
|
||||
## Using `usePermission`
|
||||
|
||||
|
||||
@@ -44,7 +44,11 @@ cd plugins/carmen-backend
|
||||
yarn start
|
||||
```
|
||||
|
||||
> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md)
|
||||
:::note Note
|
||||
|
||||
This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md)
|
||||
|
||||
:::
|
||||
|
||||
This will think for a bit, and then say `Listening on :7007`. In a different
|
||||
terminal window, now run
|
||||
|
||||
@@ -325,6 +325,33 @@ concrete routes directly. Although there can be some benefits to using the full
|
||||
routing system even in internal plugins. It can help you structure your routes,
|
||||
and as you will see further down it also helps you manage route parameters.
|
||||
|
||||
You can also use static configuration to bind routes, removing the need to make
|
||||
changes to the app code. It does however mean that you won't get type safety
|
||||
when binding routes and compile-time validation of the bindings. Static
|
||||
configuration of route bindings is done under the `app.routes.bindings` key in
|
||||
`app-config.yaml`. It works the same way as [route bindings in the new frontend system](../frontend-system/architecture/07-routes.md#binding-external-route-references),
|
||||
for example:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
routes:
|
||||
bindings:
|
||||
bar.headerLink: foo.root
|
||||
```
|
||||
|
||||
### Default Targets for External Route References
|
||||
|
||||
Following the `1.28` release of Backstage you can now define default targets for
|
||||
external route references. They work the same way as [default targets in the new frontend system](../frontend-system/architecture/07-routes.md#default-targets-for-external-route-references),
|
||||
for example:
|
||||
|
||||
```ts
|
||||
export const createComponentExternalRouteRef = createExternalRouteRef({
|
||||
// highlight-next-line
|
||||
defaultTarget: 'scaffolder.createComponent',
|
||||
});
|
||||
```
|
||||
|
||||
### Optional External Routes
|
||||
|
||||
When creating an `ExternalRouteRef` it is possible to mark it as optional:
|
||||
|
||||
@@ -11,7 +11,7 @@ A Backstage Plugin adds functionality to Backstage.
|
||||
To create a new frontend plugin, make sure you've run `yarn install` and installed
|
||||
dependencies, then run the following on your command line (a shortcut to
|
||||
invoking the
|
||||
[`backstage-cli new --select plugin`](../local-dev/cli-commands.md#new))
|
||||
[`backstage-cli new --select plugin`](../tooling/cli/03-commands.md#new))
|
||||
from the root of your project.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -28,12 +28,7 @@ backend.add(import('@backstage/plugin-proxy-backend/alpha'));
|
||||
In `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
/** ... other routers ... */
|
||||
.addRouter('/proxy', await proxy(proxyEnv));
|
||||
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -50,6 +45,7 @@ proxy:
|
||||
/simple-example: http://simple.example.com:8080
|
||||
'/larger-example/v1':
|
||||
target: http://larger.example.com:8080/svc.v1
|
||||
credentials: require
|
||||
headers:
|
||||
Authorization: ${EXAMPLE_AUTH_HEADER}
|
||||
# ...or interpolating a value into part of a string,
|
||||
@@ -66,6 +62,23 @@ backend requests to `/api/proxy/simple-example/...` and
|
||||
The value inside each route is either a simple URL string, or an object on the
|
||||
format accepted by
|
||||
[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware).
|
||||
Additionally, it has an optional `credentials` key which can have the following
|
||||
values:
|
||||
|
||||
- `require`: Callers must provide Backstage user or service credentials with
|
||||
each request. The credentials are not forwarded to the proxy target. This is
|
||||
the default.
|
||||
- `forward`: Callers must provide Backstage user or service credentials with
|
||||
each request, and those credentials are forwarded to the proxy target.
|
||||
- `dangerously-allow-unauthenticated`: No Backstage credentials are required to
|
||||
access this proxy target. The target can still apply its own credentials
|
||||
checks, but the proxy will not help block non-Backstage-blessed callers. If
|
||||
you also add `allowedHeaders: ['Authorization']` to an endpoint configuration,
|
||||
then the Backstage token (if provided) WILL be forwarded.
|
||||
|
||||
Note that if you have `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to
|
||||
`true`, the `credentials` value does not apply; the proxy will behave as if all
|
||||
endpoints were set to `dangerously-allow-unauthenticated`.
|
||||
|
||||
If the value is a string, it is assumed to correspond to:
|
||||
|
||||
@@ -74,6 +87,7 @@ target: <the string>
|
||||
changeOrigin: true
|
||||
pathRewrite:
|
||||
'^<url prefix><the string>/': '/'
|
||||
credentials: require
|
||||
```
|
||||
|
||||
When the target is an object, it is given verbatim to `http-proxy-middleware`
|
||||
@@ -86,6 +100,7 @@ except with the following caveats for convenience:
|
||||
`'^/api/proxy/larger-example/v1/': '/'` is added. That means that a request to
|
||||
`/api/proxy/larger-example/v1/some/path` will be translated to a request to
|
||||
`http://larger.example.com:8080/svc.v1/some/path`.
|
||||
- If `credentials` is not specified, it is set to `require`.
|
||||
|
||||
There are also additional settings:
|
||||
|
||||
|
||||
@@ -369,4 +369,4 @@ Note: wrapping in the test application **requires** you to do a `find()` or
|
||||
|
||||
## Debugging Jest Tests
|
||||
|
||||
You can find it [here](https://backstage.io/docs/local-dev/cli-build-system#debugging-jest-tests)
|
||||
You can find it [here](https://backstage.io/docs/tooling/cli/build-system#debugging-jest-tests)
|
||||
|
||||
@@ -201,7 +201,7 @@ A service that hosts [packages](#package). The most prominent example is [NPM](h
|
||||
|
||||
## Package Role
|
||||
|
||||
The declared role of a package, see [package roles](../local-dev/cli-build-system.md#package-roles).
|
||||
The declared role of a package, see [package roles](../tooling/cli/02-build-system.md#package-roles).
|
||||
|
||||
## Permission (core Backstage plugin)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ A huge thanks to the whole team of maintainers and contributors as well as the a
|
||||
|
||||
### Support for experimental type build has been removed
|
||||
|
||||
The `--experimental-type-build` option is no longer supported by any commands in the Backstage CLI. Existing usage should be migrated to using [subpath exports](https://backstage.io/docs/local-dev/cli-build-system#subpath-exports) instead.
|
||||
The `--experimental-type-build` option is no longer supported by any commands in the Backstage CLI. Existing usage should be migrated to using [subpath exports](https://backstage.io/docs/tooling/cli/build-system#subpath-exports) instead.
|
||||
|
||||
### Experimental support for Vite ⚡
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
---
|
||||
id: cli-overview
|
||||
id: overview
|
||||
title: Overview
|
||||
description: Overview of the Backstage CLI
|
||||
---
|
||||
@@ -7,12 +7,12 @@ description: Overview of the Backstage CLI
|
||||
## Introduction
|
||||
|
||||
A goal of Backstage is to provide a delightful developer experience in and
|
||||
around the project. Creating new [apps](../references/glossary.md#app) and
|
||||
[plugins](../references/glossary.md#plugin) should be simple, iteration
|
||||
around the project. Creating new [apps](../../references/glossary.md#app) and
|
||||
[plugins](../../references/glossary.md#plugin) should be simple, iteration
|
||||
speed should be fast, and the overhead of maintaining custom tooling should be
|
||||
minimal. As a part of accomplishing this goal, Backstage provides its own build
|
||||
system and tooling, delivered primarily through the
|
||||
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) [package](../references/glossary.md#package). When
|
||||
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) [package](../../references/glossary.md#package). When
|
||||
creating an app using
|
||||
[`@backstage/create-app`](https://www.npmjs.com/package/@backstage/create-app),
|
||||
you receive a project that's already prepared with a typical setup and package
|
||||
@@ -23,8 +23,8 @@ Under the hood the CLI uses [Webpack](https://webpack.js.org/) for bundling,
|
||||
[Jest](https://jestjs.io/) for testing, and [eslint](https://eslint.org/) for
|
||||
linting. It also includes tooling for working within Backstage apps, for example
|
||||
for keeping the app up to date and verifying static configuration. For a more
|
||||
in-depth look into the tooling, see the [build system](./cli-build-system.md)
|
||||
page, and for a list of commands, see the [commands](./cli-commands.md) page.
|
||||
in-depth look into the tooling, see the [build system](./02-build-system.md)
|
||||
page, and for a list of commands, see the [commands](./03-commands.md) page.
|
||||
|
||||
While the Backstage tooling is opinionated in how it works, it is also possible
|
||||
to use your own tooling either partially or fully. For example, the CLI provides
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
id: cli-build-system
|
||||
id: build-system
|
||||
title: Build System
|
||||
description: A deep dive into the Backstage build system
|
||||
---
|
||||
@@ -74,7 +74,7 @@ implemented in a typical Backstage app.
|
||||
|
||||
## Package Roles
|
||||
|
||||
> Package roles were introduced in March 2022. To migrate existing projects, see the [migration guide](../tutorials/package-role-migration.md).
|
||||
> Package roles were introduced in March 2022. To migrate existing projects, see the [migration guide](../../tutorials/package-role-migration.md).
|
||||
|
||||
The Backstage build system uses the concept of package roles in order to help keep
|
||||
configuration lean, provide utility and tooling, and enable optimizations. A package
|
||||
@@ -258,7 +258,7 @@ When building CommonJS or ESM output, the build commands will always use
|
||||
`src/index.ts` as the entrypoint. All non-relative modules imports are considered
|
||||
external, meaning the Rollup build will only compile the source code of the package
|
||||
itself. All import statements of external dependencies, even within the same
|
||||
[monorepo](../references/glossary.md#monorepo), will stay intact.
|
||||
[monorepo](../../references/glossary.md#monorepo), will stay intact.
|
||||
|
||||
The build of the type definitions works quite differently. The entrypoint of the
|
||||
type definition build is the relative location of the package within the
|
||||
@@ -308,7 +308,7 @@ support for them instead.
|
||||
### Frontend Production
|
||||
|
||||
The frontend production bundling creates your typical web content
|
||||
[bundle](../references/glossary.md#bundle), all contained within a single
|
||||
[bundle](../../references/glossary.md#bundle), all contained within a single
|
||||
folder, ready for static serving. It is used when building packages with the
|
||||
`'frontend'` role, and unlike the development bundling there is no way to
|
||||
build a production bundle of an individual plugin.
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
id: cli-commands
|
||||
id: commands
|
||||
title: Commands
|
||||
description: Descriptions of all commands available in the CLI.
|
||||
---
|
||||
@@ -102,7 +102,7 @@ Options:
|
||||
|
||||
## package start
|
||||
|
||||
Starts the package for local development. See the frontend and backend development parts in the build system [bundling](./cli-build-system.md#bundling) section for more details.
|
||||
Starts the package for local development. See the frontend and backend development parts in the build system [bundling](./02-build-system.md#bundling) section for more details.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli package start [options]
|
||||
@@ -119,7 +119,7 @@ Options:
|
||||
|
||||
## package build
|
||||
|
||||
Build an individual package based on its role. See the build system [building](./cli-build-system.md#building) and [bundling](./cli-build-system.md#bundling) sections for more details.
|
||||
Build an individual package based on its role. See the build system [building](./02-build-system.md#building) and [bundling](./02-build-system.md#bundling) sections for more details.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli package build [options]
|
||||
@@ -139,7 +139,7 @@ Options:
|
||||
Lint a package. In addition to the default `eslint` behavior, this command will
|
||||
include TypeScript files, treat warnings as errors, and default to linting the
|
||||
entire directory if no specific files are listed. For more information, see the
|
||||
build system [linting](./cli-build-system.md#linting) section.
|
||||
build system [linting](./02-build-system.md#linting) section.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli package lint [options]
|
||||
@@ -165,7 +165,7 @@ a yarn workspaces monorepo by automatically creating one grouped configuration
|
||||
that includes all packages that have `backstage-cli test` in their package
|
||||
`test` script.
|
||||
|
||||
For more information about configuration overrides and editor support, see the [Jest Configuration section](./cli-build-system.md#jest-configuration) in the build system documentation.
|
||||
For more information about configuration overrides and editor support, see the [Jest Configuration section](./02-build-system.md#jest-configuration) in the build system documentation.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli package test [options]
|
||||
@@ -190,7 +190,7 @@ Delete cache directories
|
||||
|
||||
This command should be added as `scripts.prepack` in all packages. It enables
|
||||
packaging- and publish-time overrides for fields inside `packages.json`.
|
||||
For more details, see the build system [publishing](./cli-build-system.md#publishing) section.
|
||||
For more details, see the build system [publishing](./02-build-system.md#publishing) section.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli package prepack [options]
|
||||
@@ -369,8 +369,8 @@ Usage: backstage-cli build-workspace [options] <workspace-dir>
|
||||
## create-github-app
|
||||
|
||||
Creates a GitHub App in your GitHub organization. This is an alternative to
|
||||
token-based [GitHub integration](../integrations/github/locations.md). See
|
||||
[GitHub Apps for Backstage Authentication](../integrations/github/github-apps.md).
|
||||
token-based [GitHub integration](../../integrations/github/locations.md). See
|
||||
[GitHub Apps for Backstage Authentication](../../integrations/github/github-apps.md).
|
||||
|
||||
Launches a browser to create the App through GitHub and saves the result as a
|
||||
YAML file that can be referenced in the GitHub integration configuration.
|
||||
@@ -10,7 +10,7 @@ information about the change can be found in the [original RFC](https://github.c
|
||||
|
||||
Package roles are implemented through a well-known `"backstage"."role"` field in the
|
||||
`package.json` of each package. There are a handful of roles defined so far, and it
|
||||
is not possible to use values outside the [set of predefined roles](../local-dev/cli-build-system.md#package-roles).
|
||||
is not possible to use values outside the [set of predefined roles](../tooling/cli/02-build-system.md#package-roles).
|
||||
|
||||
With roles in place in all packages, the Backstage CLI is able to automatically
|
||||
determine how to handle each package. For example, the different build commands
|
||||
@@ -55,7 +55,7 @@ yarn backstage-cli migrate package-roles
|
||||
|
||||
The automatic detection is not perfect, so it is recommended to manually review the
|
||||
roles that were assigned to each package.
|
||||
You can use the [package role definitions](../local-dev/cli-build-system.md#package-roles) as a reference.
|
||||
You can use the [package role definitions](../tooling/cli/02-build-system.md#package-roles) as a reference.
|
||||
|
||||
### Step 2 - Migrate package scripts
|
||||
|
||||
@@ -85,7 +85,7 @@ If you in the end do not want to use this exact script setup, it is still recomm
|
||||
|
||||
### Step 3 - Migrate package ESLint configurations
|
||||
|
||||
An area that has been simplified as part of the move to package roles is the ESLint configuration. Rather than having each package select which configuration they want (and getting it wrong), they now use a shared configuration factory that utilizes the package role. You can read more about the new configuration setup in the [build system documentation](../local-dev/cli-build-system.md#linting).
|
||||
An area that has been simplified as part of the move to package roles is the ESLint configuration. Rather than having each package select which configuration they want (and getting it wrong), they now use a shared configuration factory that utilizes the package role. You can read more about the new configuration setup in the [build system documentation](../tooling/cli/02-build-system.md#linting).
|
||||
|
||||
To migrate the ESLint configuration of all packages in your project, run the following command:
|
||||
|
||||
@@ -97,7 +97,7 @@ This will migrate all existing `.eslintrc.js` that extend the old configuration
|
||||
|
||||
### Step 4 - Use `backstage-cli repo`
|
||||
|
||||
The Backstage CLI recently introduced a new `repo` command category, which houses commands that operate on an entire monorepo at once. These commands work particularly well once packages have been migrated to use roles, as that allows for some very effective optimizations. It is typically much faster to use these commands compared to using tools like `lerna`, as they're able to avoid the overhead of calling package scripts through `yarn` and can operate on multiple packages at once. You can read more about the `repo` command in the [CLI command documentation](../local-dev/cli-commands.md#repo-build).
|
||||
The Backstage CLI recently introduced a new `repo` command category, which houses commands that operate on an entire monorepo at once. These commands work particularly well once packages have been migrated to use roles, as that allows for some very effective optimizations. It is typically much faster to use these commands compared to using tools like `lerna`, as they're able to avoid the overhead of calling package scripts through `yarn` and can operate on multiple packages at once. You can read more about the `repo` command in the [CLI command documentation](../tooling/cli/03-commands.md#repo-build).
|
||||
|
||||
The way to execute this step of the migration is not as well defined as the previous steps, as it depends on what your development and CI/CD setup looks like. Look for the following patterns to replace in your root `package.json` as well as CI/CD setup:
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export class MyAwesomeApiClient implements MyAwesomeApi {
|
||||
|
||||
private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> {
|
||||
// As configured previously for the backend proxy
|
||||
const proxyUri = '${await this.discoveryApi.getBaseUrl('proxy')}/<your-proxy-uri>';
|
||||
const proxyUri = `${await this.discoveryApi.getBaseUrl('proxy')}/<your-proxy-uri>`;
|
||||
|
||||
const resp = await fetch(`${proxyUri}${input}`, init);
|
||||
if (!resp.ok) throw new Error(resp);
|
||||
|
||||
Reference in New Issue
Block a user