Merge remote-tracking branch 'upstream/master' into easyauth
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 23 KiB |
Executable → Regular
+1
-1
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 52 KiB |
@@ -61,7 +61,7 @@ how this is done. Note that for the Bitbucket provider, you'll want to use
|
||||
factory.
|
||||
|
||||
The `@backstage/plugin-auth-backend` plugin also comes with two built-in
|
||||
resolves that can be used if desired. The first one is the
|
||||
resolvers that can be used if desired. The first one is the
|
||||
`bitbucketUsernameSignInResolver`, which identifies users by matching their
|
||||
Bitbucket username to `bitbucket.org/username` annotations of `User` entities in
|
||||
the catalog. Note that you must populate your catalog with matching entities or
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: provider
|
||||
title: Bitbucket Server Authentication Provider
|
||||
sidebar_label: Bitbucket Server
|
||||
description: Adding Bitbucket Server OAuth as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate
|
||||
users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud.
|
||||
|
||||
## Create an Application Link in Bitbucket Server
|
||||
|
||||
To add Bitbucket Server authentication, you must create an outgoing application link. Follow the steps described in
|
||||
the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-outgoing-link-1108483656.html)
|
||||
to create one.
|
||||
|
||||
## Configuration
|
||||
|
||||
The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
environment: development
|
||||
providers:
|
||||
bitbucketServer:
|
||||
development:
|
||||
host: bitbucket.org
|
||||
clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID}
|
||||
clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET}
|
||||
```
|
||||
|
||||
The Bitbucket Server provider is a structure with two configuration keys:
|
||||
|
||||
- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`.
|
||||
- `clientSecret`: The client secret tied to the generated client ID.
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown
|
||||
in [Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
## Using Bitbucket Server for sign-in
|
||||
|
||||
In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See
|
||||
the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the
|
||||
Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID,
|
||||
and `providers.bitbucketServer.create` for the provider factory.
|
||||
|
||||
The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired.
|
||||
The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email
|
||||
address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will
|
||||
not be able to sign in with this resolver.
|
||||
@@ -29,7 +29,9 @@ auth:
|
||||
jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT
|
||||
```
|
||||
|
||||
You can find the project number and service ID in the Google Cloud Console.
|
||||
The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following:
|
||||
|
||||

|
||||
|
||||
This config section must be in place for the provider to load at all. Now let's
|
||||
add the provider itself.
|
||||
|
||||
@@ -18,6 +18,7 @@ Backstage comes with many common authentication providers in the core library:
|
||||
- [Auth0](auth0/provider.md)
|
||||
- [Azure](microsoft/provider.md)
|
||||
- [Bitbucket](bitbucket/provider.md)
|
||||
- [Bitbucket Server](bitbucketServer/provider.md)
|
||||
- [Cloudflare Access](cloudflare/access.md)
|
||||
- [GitHub](github/provider.md)
|
||||
- [GitLab](gitlab/provider.md)
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: System Architecture
|
||||
description: The structure and architecture of the new Backend System and its component parts
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
## Building Blocks
|
||||
|
||||
This section introduces the high-level building blocks upon which this new
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Backend
|
||||
description: Backend instances
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
## The Backend Instance
|
||||
|
||||
This is the main entry point for creating a backend. It does not have any functionality in and of itself, but is simply responsible for wiring things together.
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Services
|
||||
description: Services for backend plugins
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
Backend services provide shared functionality available to all backend plugins and modules. They are made available through service references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
|
||||
|
||||
The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created.
|
||||
@@ -59,7 +61,7 @@ class DefaultFooService implements FooService {
|
||||
}
|
||||
}
|
||||
|
||||
export const fooFactory = createServiceFactory({
|
||||
export const fooServiceFactory = createServiceFactory({
|
||||
service: fooServiceRef,
|
||||
deps: { bar: barServiceRef },
|
||||
factory({ bar }) {
|
||||
@@ -73,7 +75,7 @@ To create a service factory we need to provide a reference to the `service` for
|
||||
If you need the creation of the service instance to be asynchronous, you can make the `factory` function async. For example:
|
||||
|
||||
```ts
|
||||
export const fooFactory = createServiceFactory({
|
||||
export const fooServiceFactory = createServiceFactory({
|
||||
service: fooServiceRef,
|
||||
deps: {},
|
||||
async factory() {
|
||||
@@ -92,18 +94,18 @@ To install a service factory in a backend instance, we pass it in through the `s
|
||||
|
||||
```ts
|
||||
const backend = createBackend({
|
||||
services: [fooFactory()],
|
||||
services: [fooServiceFactory()],
|
||||
});
|
||||
```
|
||||
|
||||
Note that we call `fooFactory` to create the service factory instance. This is because `createServiceFactory` always returns a factory function that creates the actual service factory. This is done to always allow for options to be added to the service factory in the future, without breaking existing code. To add options to your service factory, you wrap the object passed to `createServiceFactory` in a callback that accepts the desired options. For example:
|
||||
Note that we call `fooServiceFactory` to create the service factory instance. This is because `createServiceFactory` always returns a factory function that creates the actual service factory. This is done to always allow for options to be added to the service factory in the future, without breaking existing code. To add options to your service factory, you wrap the object passed to `createServiceFactory` in a callback that accepts the desired options. For example:
|
||||
|
||||
```ts
|
||||
export interface FooFactoryOptions {
|
||||
mode: 'eager' | 'lazy';
|
||||
}
|
||||
|
||||
export const fooFactory = createServiceFactory(
|
||||
export const fooServiceFactory = createServiceFactory(
|
||||
(options?: FooFactoryOptions) => ({
|
||||
service: fooServiceRef,
|
||||
deps: { bar: barServiceRef },
|
||||
@@ -118,7 +120,7 @@ This lets us use the options to customize the factory implementation in any way
|
||||
|
||||
```ts
|
||||
const backend = createBackend({
|
||||
services: [fooFactory({ mode: 'eager' })],
|
||||
services: [fooServiceFactory({ mode: 'eager' })],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -162,7 +164,7 @@ Plugin scoped services have access to a plugin metadata service, which is a spec
|
||||
The plugin metadata service is the base for all plugin specific customizations for services. For example, the default implementation of the plugin scoped logger service uses the plugin metadata service to attach the plugin ID as a field in all log messages:
|
||||
|
||||
```ts
|
||||
export const loggerFactory = createServiceFactory({
|
||||
export const loggerServiceFactory = createServiceFactory({
|
||||
service: coreServices.logger,
|
||||
deps: {
|
||||
rootLogger: coreServices.rootLogger,
|
||||
@@ -181,7 +183,7 @@ Some services may benefit from having a context that is shared across all instan
|
||||
The root context is defined as part of the service factory by passing the `createRootContext` option:
|
||||
|
||||
```ts
|
||||
export const fooFactory = createServiceFactory({
|
||||
export const fooServiceFactory = createServiceFactory({
|
||||
service: fooServiceRef,
|
||||
deps: { rootLogger: coreServices.rootLogger, bar: barServiceRef },
|
||||
createRootContext({ rootLogger }) {
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Plugins
|
||||
description: Backend plugins
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
Plugins provide the actual base features of a Backstage backend. Each plugin operates completely independently of all other plugins and they only communicate with each other through network calls. This means that there is a strong degree of isolation between plugins, and that each plugin can be considered a separate microservice. While a default Backstage project has all plugins installed within a single backend, it is also possible to split this setup into multiple backends, with each backend housing one or more plugins.
|
||||
|
||||
## Defining a Plugin
|
||||
@@ -20,7 +22,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -51,7 +53,7 @@ export interface ExamplePluginOptions {
|
||||
|
||||
export const examplePlugin = createBackendPlugin(
|
||||
(options?: ExamplePluginOptions) => ({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Extension Points
|
||||
description: Extension points of backend plugins
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
While plugins are able to accept options for lightweight forms of customization and extension, you quickly hit a limit where you need something more powerful to allow users to extend your plugin. For this purpose, the backend system provides a mechanism for plugins to provide extension points, which can be used to expose deeper customizations for your plugin. Extension points are used by modules, which are installed in the backend adjacent to plugins. Modules are covered more in-depth in the [next section](./06-modules.md).
|
||||
|
||||
Extension points are quite similar to services, in that they both encapsulate an interface in a reference object. The key difference is that extension points are registered and provided by plugins themselves, and do not have any factory associated with them. Extension points for a given plugin are also only accessible to modules that extend that same plugin.
|
||||
@@ -42,7 +44,7 @@ class ActionsExtension implements ScaffolderActionsExtensionPoint {
|
||||
|
||||
export const scaffolderPlugin = createBackendPlugin(
|
||||
{
|
||||
id: 'scaffolder',
|
||||
pluginId: 'scaffolder',
|
||||
register(env) {
|
||||
const actionsExtensions = new ActionsExtension();
|
||||
env.registerExtensionPoint(
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Modules
|
||||
description: Modules for backend plugins
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
Backend modules are used to extend [plugins](./04-plugins.md) with additional features or change existing behavior. They must always be installed in the same backend instance as the plugin that they extend, and may only extend a single plugin. Modules interact with their target plugin using the [extension points](./05-extension-points.md) registered by the plugin, while also being able to depend on the [services](./03-services.md) of that plugin.
|
||||
|
||||
Both modules and plugins register an `init` method that is called during startup. In order to ensure that modules have registered all their extensions before the plugin starts up, all modules for each plugin are completely initialized before the plugin itself is initialized. In practice this means that all promises returned by each `init` method of the modules need to resolve before the plugin `init` method is called. This also means that it is not possible to further interact with the extension points once the `init` method has resolved.
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Naming Patterns
|
||||
description: Naming patterns in the backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
These are the naming patterns to adhere to within the backend system. They help us keep exports consistent across packages and make it easier to understand the usage and intent of exports.
|
||||
|
||||
### Plugins
|
||||
@@ -19,7 +21,7 @@ Example:
|
||||
|
||||
```ts
|
||||
export const catalogPlugin = createBackendPlugin({
|
||||
id: 'catalog',
|
||||
pluginId: 'catalog',
|
||||
...
|
||||
})
|
||||
```
|
||||
@@ -34,7 +36,7 @@ export const catalogPlugin = createBackendPlugin({
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export const catalogModuleGithubEntityProvider = createBackendPlugin({
|
||||
export const catalogModuleGithubEntityProvider = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'githubEntityProvider',
|
||||
...
|
||||
@@ -64,12 +66,12 @@ export const catalogProcessingExtensionPoint = createExtensionPoint<CatalogProce
|
||||
|
||||
### Services
|
||||
|
||||
| Description | Pattern | Examples |
|
||||
| ----------- | ------------------- | -------------------------------------------------- |
|
||||
| Interface | `<Name>Service` | `LoggerService`, `DatabaseService` |
|
||||
| Reference | `<name>ServiceRef` | `loggerServiceRef`, `databaseServiceRef` |
|
||||
| ID | `<pluginId>.<name>` | `'core.rootHttpRouter'`, `'catalog.catalogClient'` |
|
||||
| Factory | `<name>Factory` | `loggerFactory`, `databaseFactory` |
|
||||
| Description | Pattern | Examples |
|
||||
| ----------- | ---------------------- | -------------------------------------------------- |
|
||||
| Interface | `<Name>Service` | `LoggerService`, `DatabaseService` |
|
||||
| Reference | `<name>ServiceRef` | `loggerServiceRef`, `databaseServiceRef` |
|
||||
| ID | `<pluginId>.<name>` | `'core.rootHttpRouter'`, `'catalog.catalogClient'` |
|
||||
| Factory | `<name>ServiceFactory` | `loggerServiceFactory`, `databaseServiceFactory` |
|
||||
|
||||
Example:
|
||||
|
||||
@@ -83,7 +85,7 @@ export const catalogClientServiceRef = createServiceRef<CatalogClientService>({
|
||||
...
|
||||
})
|
||||
|
||||
export const catalogClientFactory = createServiceFactory({
|
||||
export const catalogClientServiceFactory = createServiceFactory({
|
||||
service: catalogClientServiceRef,
|
||||
...
|
||||
})
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Overview
|
||||
description: Building backends using the new backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
> NOTE: If you have an existing backend that is not yet using the new backend
|
||||
> system, see [migrating](./08-migrating.md).
|
||||
|
||||
@@ -59,11 +61,11 @@ All of these services can be replaced with your own implementations if you need
|
||||
For example, let's say we want to customize the core configuration service to enable remote configuration loading. That would look something like this:
|
||||
|
||||
```ts
|
||||
import { configFactory } from '@backstage/backend-app-api';
|
||||
import { configServiceFactory } from '@backstage/backend-app-api';
|
||||
|
||||
const backend = createBackend({
|
||||
services: [
|
||||
configFactory({
|
||||
configServiceFactory({
|
||||
remote: { reloadIntervalSeconds: 60 },
|
||||
}),
|
||||
],
|
||||
@@ -158,11 +160,11 @@ A shared environment is defined using `createSharedEnvironment`. In this example
|
||||
```ts
|
||||
// packages/backend-env/src/index.ts
|
||||
import { createSharedEnvironment } from '@backstage/backend-plugin-api';
|
||||
import { customDiscoveryFactory } from './customDiscoveryFactory';
|
||||
import { customDiscoveryServiceFactory } from './customDiscoveryServiceFactory';
|
||||
|
||||
export const env = createSharedEnvironment({
|
||||
services: [
|
||||
customDiscoveryFactory(), // custom DiscoveryService implementation
|
||||
customDiscoveryServiceFactory(), // custom DiscoveryService implementation
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Migration Guide
|
||||
description: How to migrate existing backends to the new backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. As such, it is not considered stable, and it is not recommended to migrate production backends to the new backend system until it has a stable release.**
|
||||
|
||||
## Overview
|
||||
|
||||
This section describes how to migrate an existing Backstage backend service
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Overview
|
||||
description: Building backend plugins and modules using the new backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
> 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).
|
||||
|
||||
@@ -34,7 +36,7 @@ import {
|
||||
import { createExampleRouter } from './router';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -181,7 +183,7 @@ that needs to be different across environments.
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { config: coreServices.config },
|
||||
@@ -214,7 +216,7 @@ export interface ExampleOptions {
|
||||
|
||||
export const examplePlugin = createBackendPlugin(
|
||||
(options?: ExampleOptions) => ({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Testing
|
||||
description: Learn how to test your backend plugins and modules
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
Utilities for testing backend plugins and modules are available in
|
||||
`@backstage/backend-test-utils`. This section describes those facilities.
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Migration Guide
|
||||
description: How to migrate existing backend plugins to the new backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
Migrating an existing backend plugin to the new backend system is fairly straightforward. The process is similar across the majority of plugins which just return a `Router` that is then wired up in the `index.ts` file of your backend. The primary thing that we need to do is to make sure that the dependencies that are required by the plugin are available, and then registering the router with the HTTP router service.
|
||||
|
||||
Let's look at an example of migrating the Kubernetes backend plugin. In the existing (old) system, the kubernetes backend is structured like this:
|
||||
@@ -44,7 +46,7 @@ import { Router } from 'express';
|
||||
import { KubernetesBuilder } from './KubernetesBuilder';
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -93,7 +95,7 @@ export interface KubernetesOptions {
|
||||
}
|
||||
|
||||
const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -180,7 +182,7 @@ class ClusterSupplier implements KubernetesClusterSupplierExtensionPoint {
|
||||
}
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
const extensionPoint = new ClusterSupplier();
|
||||
// We register the extension point with the backend, which allows modules to
|
||||
|
||||
@@ -6,6 +6,8 @@ sidebar_label: Core Services
|
||||
description: Core backend service APIs
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, URL Readers, databases and more.
|
||||
|
||||
All core services are available through the `coreServices` namespace in the `@backstage/backend-plugin-api` package.
|
||||
@@ -31,7 +33,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { http: coreServices.httpRouter },
|
||||
@@ -57,11 +59,11 @@ There's additional configuration that you can optionally pass to setup the `http
|
||||
You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
|
||||
|
||||
```ts
|
||||
import { httpRouterFactory } from '@backstage/backend-app-api';
|
||||
import { httpRouterServiceFactory } from '@backstage/backend-app-api';
|
||||
|
||||
const backend = createBackend({
|
||||
services: [
|
||||
httpRouterFactory({
|
||||
httpRouterServiceFactory({
|
||||
getPath: (pluginId: string) => `/plugins/${pluginId}`,
|
||||
}),
|
||||
],
|
||||
@@ -84,7 +86,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -114,11 +116,11 @@ There's additional options that you can pass to configure the root HTTP Router s
|
||||
You can configure the root HTTP Router service by passing the options to the `createBackend` function.
|
||||
|
||||
```ts
|
||||
import { rootHttpRouterFactory } from '@backstage/backend-app-api';
|
||||
import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api';
|
||||
|
||||
const backend = createBackend({
|
||||
services: [
|
||||
rootHttpRouterFactory({
|
||||
rootHttpRouterServiceFactory({
|
||||
configure: ({ app, middleware, routes, config, logger, lifecycle }) => {
|
||||
// the built in middleware is provided through an option in the configure function
|
||||
app.use(middleware.helmet());
|
||||
@@ -155,7 +157,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -181,11 +183,11 @@ There's additional configuration that you can optionally pass to setup the `conf
|
||||
You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
|
||||
|
||||
```ts
|
||||
import { configFactory } from '@backstage/backend-app-api';
|
||||
import { configServiceFactory } from '@backstage/backend-app-api';
|
||||
|
||||
const backend = createBackend({
|
||||
services: [
|
||||
configFactory({
|
||||
configServiceFactory({
|
||||
argv: [
|
||||
'--config',
|
||||
'/backstage/app-config.development.yaml',
|
||||
@@ -213,7 +215,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -286,7 +288,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -325,7 +327,7 @@ import {
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -364,7 +366,7 @@ import {
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -395,7 +397,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -436,11 +438,11 @@ There's additional configuration that you can optionally pass to setup the `iden
|
||||
You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
|
||||
|
||||
```ts
|
||||
import { identityFactory } from '@backstage/backend-app-api';
|
||||
import { identityServiceFactory } from '@backstage/backend-app-api';
|
||||
|
||||
const backend = createBackend({
|
||||
services: [
|
||||
identityFactory({
|
||||
identityServiceFactory({
|
||||
issuer: 'backstage',
|
||||
algorithms: ['ES256', 'RS256'],
|
||||
}),
|
||||
@@ -463,7 +465,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -477,10 +479,7 @@ createBackendPlugin({
|
||||
// do some other stuff.
|
||||
}, 1000);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
fn: () => clearInterval(interval),
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => clearInterval(interval));
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -497,17 +496,19 @@ The following example shows how to override the default implementation of the li
|
||||
|
||||
```ts
|
||||
class MyCustomLifecycleService implements RootLifecycleService {
|
||||
constructor(private readonly logger: LoggerService) {
|
||||
['SIGKILL', 'SIGTERM'].map(signal =>
|
||||
process.on(signal, () => this.shutdown()),
|
||||
);
|
||||
}
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
#isCalled = false;
|
||||
#shutdownTasks: Array<LifecycleServiceShutdownHook> = [];
|
||||
#shutdownTasks: Array<{
|
||||
hook: LifecycleServiceShutdownHook;
|
||||
options?: LifecycleServiceShutdownOptions;
|
||||
}> = [];
|
||||
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
this.#shutdownTasks.push(options);
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
this.#shutdownTasks.push({ hook, options });
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
@@ -518,10 +519,10 @@ class MyCustomLifecycleService implements RootLifecycleService {
|
||||
|
||||
this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
|
||||
await Promise.all(
|
||||
this.#shutdownTasks.map(async hook => {
|
||||
const { logger = this.logger } = hook;
|
||||
this.#shutdownTasks.map(async ({ hook, options }) => {
|
||||
const logger = options?.logger ?? this.logger;
|
||||
try {
|
||||
await hook.fn();
|
||||
await hook();
|
||||
logger.info(`Shutdown hook succeeded`);
|
||||
} catch (error) {
|
||||
logger.error(`Shutdown hook failed, ${error}`);
|
||||
@@ -562,7 +563,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -611,7 +612,7 @@ import {
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -650,7 +651,7 @@ import {
|
||||
import os from 'os';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -6,10 +6,10 @@ sidebar_label: Overview
|
||||
description: The Backend System
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is under active development and is not considered stable**
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
## Status
|
||||
|
||||
The new backend system is under active development, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
|
||||
The new backend system is in alpha, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
|
||||
|
||||
You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: proxy
|
||||
title: Kubernetes Backend Proxy Endpoint
|
||||
sidebar_label: Proxy
|
||||
description: Interacting with the Kubernetes API in Backstage plugins
|
||||
---
|
||||
|
||||
[Contributors](https://backstage.io/docs/overview/glossary#backstage-user-profiles) wanting to
|
||||
create developer portal experiences based on data from Kubernetes (e.g. for
|
||||
interacting with [Custom
|
||||
Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
|
||||
beyond the default behaviors of the existing Kubernetes plugin) can leverage the
|
||||
Kubernetes backend plugin's proxy endpoint to allow them to make arbitrary
|
||||
requests to the [REST
|
||||
API](https://kubernetes.io/docs/reference/using-api/api-concepts/).
|
||||
|
||||
Here is a snippet fetching namespaces from a cluster configured with the
|
||||
`google` [auth provider](https://backstage.io/docs/features/kubernetes/configuration#clustersauthprovider):
|
||||
|
||||
```typescript
|
||||
import {
|
||||
discoveryApiRef,
|
||||
googleAuthApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
const CLUSTER_NAME = ''; // use a known cluster name
|
||||
|
||||
// get a bearer token from Google
|
||||
const googleAuthApi = useApi(googleAuthApiRef);
|
||||
const token = await googleAuthApi.getAccessToken(
|
||||
'https://www.googleapis.com/auth/cloud-platform',
|
||||
);
|
||||
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
const kubernetesBaseUrl = await discoveryApi.getBaseUrl('kubernetes');
|
||||
const kubernetesProxyEndpoint = `${kubernetesBaseUrl}/proxy`;
|
||||
|
||||
// fetch namespaces
|
||||
await fetch(`${kubernetesProxyEndpoint}/api/v1/namespaces`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Kubernetes-Cluster': CLUSTER_NAME,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
The proxy will interpret the
|
||||
[`X-Kubernetes-Cluster`
|
||||
header](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster)
|
||||
as the name of the cluster to target. This name will be compared to each cluster
|
||||
returned by all the configured [cluster
|
||||
locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods)
|
||||
-- the first cluster whose [`name` field](https://backstage.io/docs/features/kubernetes/configuration#clustersname) matches
|
||||
the value in the header will be targeted.
|
||||
|
||||
Then the request will be forwarded verbatim (but with the endpoint's base URL
|
||||
prefix stripped) to the cluster.
|
||||
|
||||
## Authentication
|
||||
|
||||
Until some security and permission decisions are made (see [this
|
||||
conversation](https://github.com/backstage/backstage/pull/13026/files#r1029376939)
|
||||
for context), contributors consuming the proxy endpoint in their plugin code are
|
||||
responsible for negotiating their own bearer token out-of-band. This requires
|
||||
knowing some auth details about the cluster being contacted -- in practice, only
|
||||
clusters with [client side auth
|
||||
providers](https://backstage.io/docs/features/kubernetes/authentication#client-side-providers) can reasonably be reached.
|
||||
|
||||
The proxy has no provisions for mTLS, so it cannot be used to connect to
|
||||
clusters using the [x509 Client
|
||||
Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs)
|
||||
authentication strategy. [Bearer
|
||||
tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request)
|
||||
will be forwarded as-is.
|
||||
|
||||
## Other known limitations
|
||||
|
||||
The proxy as it was released in [Backstage
|
||||
1.9](https://github.com/backstage/backstage/blob/master/docs/releases/v1.9.0-changelog.md#patch-changes-15)
|
||||
has a known bug:
|
||||
|
||||
- [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot
|
||||
reliably target clusters who share the same name with another located cluster.
|
||||
@@ -235,6 +235,20 @@ export const YourSearchResultListItemExtension = plugin.provide(
|
||||
);
|
||||
```
|
||||
|
||||
If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props:
|
||||
|
||||
```tsx
|
||||
export const YourSearchResultListItemExtension: (
|
||||
props: SearchResultListItemExtensionProps<YourSearchResultListItemProps>,
|
||||
) => JSX.Element | null = plugin.provide(
|
||||
createSearchResultListItemExtension({
|
||||
name: 'YourSearchResultListItem',
|
||||
component: () =>
|
||||
import('./components').then(m => m.YourSearchResultListItem),
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not:
|
||||
|
||||
```tsx
|
||||
|
||||
@@ -89,23 +89,23 @@ export class FrobsProvider implements EntityProvider {
|
||||
private readonly reader: UrlReader;
|
||||
private connection?: EntityProviderConnection;
|
||||
|
||||
/** [1] **/
|
||||
/** [1] */
|
||||
constructor(env: string, reader: UrlReader) {
|
||||
this.env = env;
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/** [2] **/
|
||||
/** [2] */
|
||||
getProviderName(): string {
|
||||
return `frobs-${this.env}`;
|
||||
}
|
||||
|
||||
/** [3] **/
|
||||
/** [3] */
|
||||
async connect(connection: EntityProviderConnection): Promise<void> {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/** [4] **/
|
||||
/** [4] */
|
||||
async run(): Promise<void> {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
@@ -116,10 +116,10 @@ export class FrobsProvider implements EntityProvider {
|
||||
);
|
||||
const data = JSON.parse(await response.buffer()).toString();
|
||||
|
||||
/** [5] **/
|
||||
/** [5] */
|
||||
const entities: Entity[] = frobsToEntities(data);
|
||||
|
||||
/** [6] **/
|
||||
/** [6] */
|
||||
await this.connection.applyMutation({
|
||||
type: 'full',
|
||||
entities: entities.map(entity => ({
|
||||
|
||||
@@ -63,6 +63,17 @@ BigTable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together
|
||||
with components and systems will better allow us to visualize resource
|
||||
footprint, and create tooling around them.
|
||||
|
||||
## Organizational Entities
|
||||
|
||||
### User
|
||||
|
||||
A user describes a person, such as an employee, a contractor, or similar.
|
||||
|
||||
### Group
|
||||
|
||||
A group describes an organizational entity, such as for example a team, a
|
||||
business unit, or a loose collection of people in an interest group.
|
||||
|
||||
## Ecosystem Modeling
|
||||
|
||||
A large catalogue of components, APIs and resources can be highly granular and
|
||||
@@ -108,3 +119,20 @@ domain would come with some documentation on how to accept payments for a new
|
||||
product or use-case, share the same entity types in their APIs, and integrate
|
||||
well with each other. Other domains could be “Content Ingestion”, “Ads” or
|
||||
“Search”.
|
||||
|
||||
## Other
|
||||
|
||||
### Location
|
||||
|
||||
A location is a marker that references other places to look for catalog data.
|
||||
|
||||
### Type
|
||||
|
||||
The type field in the system has no set meaning. It is up to the user to assign their own types and use them as desired, such as for link validation or creating custom UI components. Some common pre-defined types are depicted in the
|
||||
[ecosystem modeling diagram](#ecosystem-modeling).
|
||||
|
||||
### Template
|
||||
|
||||
A template definition describes both the parameters that are rendered in the
|
||||
frontend part of the scaffolding wizard, and the steps that are executed when
|
||||
scaffolding that component.
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
id: testing-scaffolder-alpha
|
||||
title: 'Experimental: Testing out the `alpha` Scaffolder plugin'
|
||||
# prettier-ignore
|
||||
description: Docs on the upcoming breaking release for the scaffolder plugin
|
||||
---
|
||||
|
||||
## What's `scaffolder/next`?
|
||||
|
||||
The `alpha` version, or as you might have seen referred to in other places the `scaffolder/next` release, is a new version of the `scaffolder` plugin that will be the first breaking change to the plugin, so you can also think of it as `@backstage/plugin-scaffolder@2.0.0`.
|
||||
Its mostly a rewrite of a lot of the frontend components and pages that had very limited test coverage, which made adding new features to the `scaffolder` plugin quite hard, and we were lacking in confidence when making changes.
|
||||
|
||||
There is of course some other things that have changed when re-writing this, which are essentially what has caused some breaking changes.
|
||||
Now, this is not like previous scaffolder changes where you would have to change all of your templates as this is only the frontend plugin that is going to have breaking changes. You can read more about the [breaking changes](#breaking-changes) below.
|
||||
|
||||
## What's new?
|
||||
|
||||
First off, the main dependency that we have for the frontend which is responsible for rendering the `JSONSchema` into `material-ui` components is [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form).
|
||||
This dependency in the current version of the plugin is 3.x.x, which is now 2 major versions out of date. Long story short, `v4` of this plugin contained some bug fixes, and new features but we we're unable to upgrade due to some issues with having support for `material-ui@v4`, so we had to wait for `v5` to be released, and because of the `FieldExtensions` and how they are very tightly coupled to the `react-jsonschema-form` library, we also wanted to make sure that this release was stable before getting people to migrate their `Field Extensions`.
|
||||
|
||||
With that in mind, this release has `v5` of `react-jsonschema-form`, and with that comes all the new features and bugfixes in `v4` that we were waiting for - one of the main ones being the ability to use `if / then / else` syntax in the `template.yaml` definitions! 🎉
|
||||
|
||||
We've also rebuilt how validation works in the `scaffolder` components, which now means that we've opened the ability to have `async` validation functions in your `Field Extensions`.
|
||||
|
||||
Some of the pages have gotten a little bit of an overhaul in terms of UI based on some research and feedback from the community and internally.
|
||||
|
||||
- The `TemplateList` page has gotten some new `Card` components which show a little more information than the previous version with a little `material-ui` standards.
|
||||
- The `WizardPage` has received some new updates with the stepper now running horizontally, and the `Review` step being a dedicated step in the stepper.
|
||||
- The `OngoingTask` page now does not show the logs by default, and instead has a much cleaner interface for tracking the ongoing steps and the pipeline of actions that are currently showing.
|
||||
- You can also now provide your own `OutputsComponent` which can be used to render the outputs from an ongoing / completed task in a way that suits your templates the best. For instance, if your template produces `Pull Requests`, it could be useful to render these in an interactive way where you can see the statuses of each of these `Pull Requests` in the `Ongoing Task` page.
|
||||
|
||||
There's also a lot of bug fixes, and other things, but these are the main ones that we wanted to highlight.
|
||||
|
||||
## How do I test out the `alpha` version?
|
||||
|
||||
With the release of `v1.12.0` it's now possible to run the `scaffolder/next` plugin and it be a drop in replacement for the current version that you use today. This means that you can start using the new code, and start testing it out. Once we have collected enough feedback, and squashed any bugs that might block us from releasing, it will be promoted from the `/alpha` exports and replace the existing code leading to breaking changes if you haven't already made these changes as part of this testing pilot. Those that have chosen to opt into this testing pilot means that once we promote it from the `/alpha` exports, you will need to update your code to point to the original exports from the `scaffolder` plugin, just like the code is today but with the [breaking changes](#breaking-changes) that you already made to your `Custom Field Extensions`.
|
||||
|
||||
It's also worth calling out that if you do test this out, and find some issues or something not working out as expected, feel free to raise an issue in the [repo](https://github.com/backstage/backstage) or reach out to us on Discord!
|
||||
|
||||
### Make the required changes to `App.tsx`
|
||||
|
||||
The `ScaffolderPage` router has a completely different export for the `scaffolder/next` work, so you will want to change any import from the old `ScaffolderPage` to the new `NextScaffolderPage`
|
||||
|
||||
```diff
|
||||
- import { ScaffolderPage } from '@backstage/plugin-scaffolder';
|
||||
+ import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
|
||||
|
||||
```
|
||||
|
||||
And this API should be the exact same as the previous Router, so you should be able to make a change like the following further down in this file:
|
||||
|
||||
```diff
|
||||
<Route
|
||||
path="/create"
|
||||
element={
|
||||
- <ScaffolderPage
|
||||
+ <NextScaffolderPage
|
||||
groups={[
|
||||
{
|
||||
title: 'Recommended',
|
||||
filter: entity =>
|
||||
entity?.metadata?.tags?.includes('recommended') ?? false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
... other extensions
|
||||
</ScaffolderFieldExtensions>
|
||||
<ScaffolderLayouts>
|
||||
<TwoColumnLayout />
|
||||
... other layouts
|
||||
</ScaffolderLayouts>
|
||||
</Route>
|
||||
```
|
||||
|
||||
### Make the required changes to your `CustomFieldExtensions`
|
||||
|
||||
There's differently named function for creating field extensions part of the `/alpha` exports as these are the ones that can contain breaking changes because of the breaking changes that have been applied in `react-jsonschema-form`.
|
||||
|
||||
Let's take the following example:
|
||||
|
||||
```ts
|
||||
export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: EntityNamePicker,
|
||||
name: 'EntityNamePicker',
|
||||
validation: entityNamePickerValidation,
|
||||
schema: EntityNamePickerSchema,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
References for `createScaffolderFieldExtension` have an `/alpha` version of `createNextScaffolderFieldExtension`, which should be used instead.
|
||||
|
||||
```diff
|
||||
-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
|
||||
+import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder/alpha';
|
||||
|
||||
export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
- createScaffolderFieldExtension({
|
||||
+ createNextScaffolderFieldExtension({
|
||||
component: EntityNamePicker,
|
||||
name: 'EntityNamePicker',
|
||||
validation: entityNamePickerValidation,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Once you've done this you will find that you will have two squiggly lines under the properties that are passed in. One for the component and one for the validation (if provided.)
|
||||
|
||||
Let's take the following code for the `EntityNamePicker` component:
|
||||
|
||||
```ts
|
||||
export const EntityNamePicker = (
|
||||
props: FieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
formData,
|
||||
uiSchema: { 'ui:autofocus': autoFocus },
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
There's another `/alpha` export that you need to replace `FieldExtensionComponentProps` with which is the `NextFieldExtensionComponentProps`.
|
||||
|
||||
```diff
|
||||
- import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
|
||||
+ import { NextFieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
export const EntityNamePicker = (
|
||||
- props: FieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
+ props: NextFieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
formData,
|
||||
- uiSchema: { 'ui:autofocus': autoFocus },
|
||||
+ uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You'll notice that there's an additional change here, which is that we're now defaulting the `uiSchema` to an empty object. This is because the `uiSchema` is now optional, and if you don't provide it, it will be `undefined` instead of an empty object. There's more around this in the [breaking changes](#breaking-changes) section.
|
||||
|
||||
To fix the previous validation error, you will need to change the import for the `FieldValidation` type that is used in the `validation` function.
|
||||
|
||||
Let's take the following example of the validation function:
|
||||
|
||||
```ts
|
||||
import { FieldValidation } from '@rjsf/utils';
|
||||
import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
|
||||
|
||||
export const entityNamePickerValidation = (
|
||||
value: string,
|
||||
validation: FieldValidation,
|
||||
) => {
|
||||
if (!KubernetesValidatorFunctions.isValidObjectName(value)) {
|
||||
validation.addError(
|
||||
'Must start and end with an alphanumeric character, and contain only alphanumeric characters, hyphens, underscores, and periods. Maximum length is 63 characters.',
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You will need to change the import for `FieldValidation` to point at the new `react-jsonschema-form` dependency.
|
||||
|
||||
> Note: you will probably need to install this dependency too, by using `yarn add @rjsf/utils` in the package where you define these validation functions, this could also be in the `packages/app` folder, so you can install it there if needed.
|
||||
|
||||
```diff
|
||||
- import { FieldValidation } from '@rjsf/core';
|
||||
+ import { FieldValidation } from '@rjsf/utils;
|
||||
import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
|
||||
|
||||
export const entityNamePickerValidation = (
|
||||
value: string,
|
||||
validation: FieldValidation,
|
||||
) => {
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Once we fully release the code that is in the `/alpha` exports right now onto the current API and release v2.0.0 of `@backstage/plugin-scaffolder` the breaking changes will be as follows:
|
||||
|
||||
### `uiSchema` is now optional
|
||||
|
||||
Later releases of `react-jsonschema-form` have made the `uiSchema` optional, and if you don't provide it, it will be `undefined` instead of an empty object. This means that you will need to make sure that you're defaulting the `uiSchema` to an empty object if you're using it in your code.
|
||||
|
||||
```diff
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
formData,
|
||||
- uiSchema: { 'ui:autofocus': autoFocus },
|
||||
+ uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
```
|
||||
|
||||
### `formData` can also be `undefined`
|
||||
|
||||
If you were using the `formData` and assuming that it was set to an empty object when building `Field Extensions` that return objects, then this will be `undefined` now due to a change in the `react-jsonschema-form` library.
|
||||
|
||||
```diff
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
- formData,
|
||||
+ formData = {}, // or maybe some other default value that you would prefer
|
||||
uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
```
|
||||
@@ -168,5 +168,6 @@ scaffolder backend:
|
||||
| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) |
|
||||
| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) |
|
||||
| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) |
|
||||
| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) |
|
||||
|
||||
Have fun! 🚀
|
||||
|
||||
@@ -112,11 +112,11 @@ steps which would be rendered as different steps in the scaffolder plugin
|
||||
frontend.
|
||||
|
||||
Each `Step` is `JSONSchema` with some extra goodies for styling what it might
|
||||
look like in the frontend. For these steps we rely very heavily on this library:
|
||||
https://github.com/rjsf-team/react-jsonschema-form. They have some great docs
|
||||
too here: https://react-jsonschema-form.readthedocs.io/ and a playground where
|
||||
you can play around with some examples here
|
||||
https://rjsf-team.github.io/react-jsonschema-form.
|
||||
look like in the frontend. For these steps we rely very heavily on this
|
||||
[library](https://github.com/rjsf-team/react-jsonschema-form). They have some
|
||||
[great docs](https://rjsf-team.github.io/react-jsonschema-form/docs/) and a
|
||||
[playground](https://rjsf-team.github.io/react-jsonschema-form) where you can
|
||||
play around with some examples.
|
||||
|
||||
There's another option for that library called `uiSchema` which we've taken
|
||||
advantage of, and we've merged it with the existing `JSONSchema` that you
|
||||
|
||||
@@ -125,6 +125,7 @@ discover available Addons, we've compiled a list of them here:
|
||||
| [`<ExpandableNavigation />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
|
||||
| [`<ReportIssue />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
|
||||
| [`<TextSize />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
|
||||
| [`<LightBox />`](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. |
|
||||
|
||||
Got an Addon to contribute? Feel free to add a row above!
|
||||
|
||||
|
||||
@@ -262,12 +262,12 @@ You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from
|
||||
pip, as well as `graphviz` and `plantuml` from your OS package manager (e.g.
|
||||
apt).
|
||||
|
||||
You can do so by including the following lines in the last step of your
|
||||
You can do so by including the following lines right above `USER node` of your
|
||||
`Dockerfile`:
|
||||
|
||||
```Dockerfile
|
||||
RUN apt-get update && apt-get install -y python3 python3-pip
|
||||
RUN pip3 install mkdocs-techdocs-core==1.0.1
|
||||
RUN pip3 install mkdocs-techdocs-core==1.1.7
|
||||
```
|
||||
|
||||
Please be aware that the version requirement could change, you need to check our
|
||||
|
||||
@@ -22,7 +22,8 @@ to an entity in the software catalog.
|
||||
1. Add the plugin's npm package to the repo:
|
||||
|
||||
```bash
|
||||
yarn workspace app add @backstage/plugin-circleci
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/app @backstage/plugin-circleci
|
||||
```
|
||||
|
||||
Note the plugin is added to the `app` package, rather than the root
|
||||
|
||||
@@ -63,7 +63,7 @@ As this provider is not one of the default providers, you will first need to ins
|
||||
the AWS catalog plugin:
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-aws
|
||||
```
|
||||
|
||||
|
||||
@@ -28,3 +28,5 @@ The configuration is a structure with two elements:
|
||||
|
||||
- `host`: The DevOps host; only `dev.azure.com` is supported.
|
||||
- `token` (optional): A personal access token as expected by Azure DevOps.
|
||||
|
||||
> Note: The 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
|
||||
|
||||
@@ -17,7 +17,7 @@ As this provider is not one of the default providers, you will first need to ins
|
||||
the Gerrit provider plugin:
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-gerrit
|
||||
```
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ catalog:
|
||||
yourProviderId:
|
||||
host: gitlab-host # Identifies one of the hosts set up in the integrations
|
||||
branch: main # Optional. Uses `master` as default
|
||||
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned
|
||||
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
|
||||
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
|
||||
projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything
|
||||
projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything
|
||||
schedule: # optional; same options as in TaskScheduleDefinition
|
||||
# supports cron, ISO duration, "human duration" as used in code
|
||||
frequency: { minutes: 30 }
|
||||
@@ -36,7 +36,7 @@ As this provider is not one of the default providers, you will first need to ins
|
||||
the gitlab catalog plugin:
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-gitlab
|
||||
```
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ integrations:
|
||||
```
|
||||
|
||||
This will query all users and groups from your gitlab installation. Depending on the size
|
||||
of the Gitlab Instance, this can take some time our resources.
|
||||
of the Gitlab Instance, this can take some time and resources.
|
||||
|
||||
The token that is used for the Organization Integration, has to be an Admin PAT.
|
||||
The token that is used for the Organization Integration, has to be an Admin Personal Access Token (PAT).
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
|
||||
@@ -556,7 +556,22 @@ The overrides in a single `package.json` may for example look like this:
|
||||
},
|
||||
```
|
||||
|
||||
If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest rather than running through the Yarn test script. For example, with the Jest extension for VS Code the configuration would look something like this:
|
||||
### Debugging Jest Tests
|
||||
|
||||
For your productivity working with unit tests it's quite essential to have your debugging configured in IDE. It will help you to identify the root cause of the issue faster.
|
||||
|
||||
#### IntelliJ IDEA
|
||||
|
||||
1. Update Jest configuration template by:
|
||||
|
||||
- Click on "Edit Configurations" on top panel
|
||||
- In the modal dialog click on link "Edit configuration templates..." located in the bottom left corner.
|
||||
- In "Jest package" you have to point to relative path of jest module (it will be suggested by IntelliJ), i.e. `~/proj/backstage/node_modules/jest`
|
||||
- In "Jest config" point to your jest configuration file, use absolute path for that, i.e. `--config /Users/user/proj/backstage/packages/cli/config/jest.js --runInBand`
|
||||
|
||||
2. Now you can run any tests by clicking on green arrow located on `describe` or `it`.
|
||||
|
||||
#### VS Code
|
||||
|
||||
```jsonc
|
||||
{
|
||||
@@ -571,7 +586,7 @@ If you want to configure editor integration for tests we recommend executing the
|
||||
}
|
||||
```
|
||||
|
||||
If you also want to enable source maps when debugging tests, you can do so by setting the `ENABLE_SOURCE_MAPS` environment variable. For example, a complete launch configuration for VS Code debugging may look like this:
|
||||
A complete launch configuration for VS Code debugging may look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -583,9 +598,6 @@ If you also want to enable source maps when debugging tests, you can do so by se
|
||||
"disableOptimisticBPs": true,
|
||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"ENABLE_SOURCE_MAPS": "true"
|
||||
},
|
||||
"args": [
|
||||
"--config",
|
||||
"node_modules/@backstage/cli/config/jest.js",
|
||||
|
||||
@@ -47,7 +47,8 @@ The permissions framework uses a new `permission-backend` plugin to accept autho
|
||||
1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend:
|
||||
|
||||
```bash
|
||||
$ yarn workspace backend add @backstage/plugin-permission-backend
|
||||
# From your Backstage root directory
|
||||
$ yarn add --cwd packages/backend @backstage/plugin-permission-backend
|
||||
```
|
||||
|
||||
2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything.
|
||||
|
||||
@@ -34,6 +34,8 @@ The permission framework was designed with a few key properties in mind:
|
||||
|
||||
4. An authorization decision is sent to the plugin from the permission backend.
|
||||
|
||||
5. The user is either granted access or an error is shown. The plugin is responsible for implementing a response to the user.
|
||||
|
||||
## How do I get started?
|
||||
|
||||
See the "[getting started](./getting-started.md)" permission documentation for Backstage integrators.
|
||||
|
||||
@@ -26,6 +26,9 @@ The source code is available here:
|
||||
git checkout master -- plugins/example-todo-list/
|
||||
git checkout master -- plugins/example-todo-list-backend/
|
||||
git checkout master -- plugins/example-todo-list-common/
|
||||
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json
|
||||
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json
|
||||
sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json
|
||||
for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done
|
||||
cd -
|
||||
```
|
||||
@@ -36,16 +39,17 @@ The source code is available here:
|
||||
|
||||
2. Add these packages as dependencies for your Backstage app:
|
||||
|
||||
```
|
||||
$ yarn workspace backend add @internal/plugin-todo-list-backend@^1.0.0 @internal/plugin-todo-list-common@^1.0.0
|
||||
$ yarn workspace app add @internal/plugin-todo-list@^1.0.0
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
$ yarn add --cwd packages/backend @internal/plugin-todo-list-backend @internal/plugin-todo-list-common
|
||||
$ yarn add --cwd packages/app @internal/plugin-todo-list
|
||||
```
|
||||
|
||||
3. Include the backend and frontend plugin in your application:
|
||||
|
||||
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@internal/plugin-todo-list-backend';
|
||||
import { Router } from 'express';
|
||||
|
||||
@@ -146,7 +146,7 @@ In order to test the logic above, the integrators of your backstage instance nee
|
||||
- async handle(): Promise<PolicyDecision> {
|
||||
+ async handle(
|
||||
+ request: PolicyQuery,
|
||||
+ user?: BackstageIdentityResponse,
|
||||
+ _user?: BackstageIdentityResponse,
|
||||
+ ): Promise<PolicyDecision> {
|
||||
+ if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
+ return {
|
||||
@@ -204,7 +204,7 @@ First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`:
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
identity: {} as DefaultIdentityClient,
|
||||
+ permissions: toPermissionEvaluator,
|
||||
+ permissions: permissionEvaluator,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
@@ -235,7 +235,7 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
+ ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
+ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
@@ -285,4 +285,44 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
module.hot?.accept();
|
||||
```
|
||||
|
||||
Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`:
|
||||
|
||||
```diff
|
||||
import { loggerToWinstonLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './service/router';
|
||||
|
||||
/**
|
||||
* The example TODO list backend plugin.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const exampleTodoListPlugin = createBackendPlugin({
|
||||
pluginId: 'exampleTodoList',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
identity: coreServices.identity,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
+ permissions: coreServices.permissions,
|
||||
},
|
||||
- async init({ identity, logger, httpRouter }) {
|
||||
+ async init({ identity, logger, httpRouter, permissions }) {
|
||||
httpRouter.use(
|
||||
await createRouter({
|
||||
identity,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
permissions,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now when you run `yarn tsc` you should have no more errors.
|
||||
|
||||
@@ -76,7 +76,7 @@ This enables decisions based on characteristics of the resource, but it's import
|
||||
Install the missing module:
|
||||
|
||||
```
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node zod
|
||||
```
|
||||
|
||||
Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code:
|
||||
@@ -89,7 +89,8 @@ import { Todo, TodoFilter } from './todos';
|
||||
|
||||
export const createTodoListPermissionRule = makeCreatePermissionRule<
|
||||
Todo,
|
||||
TodoFilter
|
||||
TodoFilter,
|
||||
typeof TODO_LIST_RESOURCE_TYPE
|
||||
>();
|
||||
|
||||
export const isOwner = createTodoListPermissionRule({
|
||||
@@ -97,8 +98,8 @@ export const isOwner = createTodoListPermissionRule({
|
||||
description: 'Should allow only if the todo belongs to the user',
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
paramsSchema: z.object({
|
||||
userId: z.string().describe('User ID to match on the resource')
|
||||
})
|
||||
userId: z.string().describe('User ID to match on the resource'),
|
||||
}),
|
||||
apply: (resource: Todo, { userId }) => {
|
||||
return resource.author === userId;
|
||||
},
|
||||
@@ -187,6 +188,7 @@ Make sure `todoListConditions` and `createTodoListConditionalDecision` are expor
|
||||
```diff
|
||||
export * from './service/router';
|
||||
+ export * from './conditionExports';
|
||||
export { exampleTodoListPlugin } from './plugin';
|
||||
```
|
||||
|
||||
## Test the authorized update endpoint
|
||||
@@ -209,7 +211,6 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ import {
|
||||
+ todoListCreatePermission,
|
||||
+ todoListUpdatePermission,
|
||||
+ TODO_LIST_RESOURCE_TYPE,
|
||||
+ } from '@internal/plugin-todo-list-common';
|
||||
+ import {
|
||||
+ todoListConditions,
|
||||
@@ -217,7 +218,11 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ } from '@internal/plugin-todo-list-backend';
|
||||
|
||||
...
|
||||
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
- _user?: BackstageIdentityResponse,
|
||||
+ user?: BackstageIdentityResponse,
|
||||
): Promise<PolicyDecision> {
|
||||
if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
@@ -236,6 +241,7 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For any incoming update requests, we now return a _Conditional Decision_. We are saying:
|
||||
|
||||
@@ -15,7 +15,7 @@ One possible solution may leverage the batching functionality to authorize all o
|
||||
- res.json(getAll())
|
||||
+ const items = getAll();
|
||||
+ const decisions = await permissions.authorize(
|
||||
+ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })),
|
||||
+ items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })),
|
||||
+ );
|
||||
|
||||
+ const filteredItems = decisions.filter(
|
||||
@@ -53,7 +53,7 @@ Let's add another permission to the plugin.
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
});
|
||||
+
|
||||
+ export const todosListRead = createPermission({
|
||||
+ export const todoListReadPermission = createPermission({
|
||||
+ name: 'todos.list.read',
|
||||
+ attributes: { action: 'read' },
|
||||
+ resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
@@ -81,10 +81,9 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
import {
|
||||
todosListCreate,
|
||||
todosListUpdate,
|
||||
+ todosListRead,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from './permissions';
|
||||
+ import { rules } from './rules';
|
||||
|
||||
+ const transformConditions: ConditionTransformer<TodoFilter> = createConditionTransformer(Object.values(rules));
|
||||
|
||||
@@ -95,7 +94,7 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ );
|
||||
+
|
||||
+ const decision = (
|
||||
+ await permissions.authorizeConditional([{ permission: todosListRead }], {
|
||||
+ await permissions.authorizeConditional([{ permission: todoListReadPermission }], {
|
||||
+ token,
|
||||
+ })
|
||||
+ )[0];
|
||||
@@ -110,7 +109,6 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ } else {
|
||||
+ res.json(getAll());
|
||||
+ }
|
||||
+ }
|
||||
- res.json(getAll());
|
||||
});
|
||||
```
|
||||
@@ -121,7 +119,7 @@ Since `TodoFilter` used in our plugin matches the structure of the conditions ob
|
||||
|
||||
## Test the authorized read endpoint
|
||||
|
||||
Let's update our permission policy to return a conditional result whenever a `todosListRead` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/permission.ts
|
||||
@@ -132,7 +130,6 @@ import {
|
||||
todoListCreatePermission,
|
||||
todoListUpdatePermission,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from '@internal/plugin-todo-list-common';
|
||||
|
||||
...
|
||||
|
||||
@@ -56,7 +56,11 @@ Let's make the following changes in `plugins/todo-list/src/components/TodoListPa
|
||||
- Add
|
||||
- </Button>
|
||||
+ {!loadingPermission && (
|
||||
+ <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
+ <Button
|
||||
+ disabled={!canAddTodo}
|
||||
+ variant="contained"
|
||||
+ onClick={() => onAdd(title.current)}
|
||||
+ >
|
||||
+ Add
|
||||
+ </Button>
|
||||
+ )}
|
||||
@@ -118,7 +122,10 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
- <Grid item>
|
||||
- <AddTodo onAdd={handleAdd} />
|
||||
- </Grid>
|
||||
+ <RequirePermission permission={todoListCreatePermission} errorPage={<></>}>
|
||||
+ <RequirePermission
|
||||
+ permission={todoListCreatePermission}
|
||||
+ errorPage={<></>}
|
||||
+ >
|
||||
+ <Grid item>
|
||||
+ <AddTodo onAdd={handleAdd} />
|
||||
+ </Grid>
|
||||
@@ -149,11 +156,15 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
onChange={e => (title.current = e.target.value)}
|
||||
/>
|
||||
- {!loadingPermission && (
|
||||
- <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
- <Button
|
||||
- disabled={!canAddTodo}
|
||||
- variant="contained"
|
||||
- onClick={() => onAdd(title.current)}
|
||||
- >
|
||||
- Add
|
||||
- </Button>
|
||||
- )}
|
||||
+ <Button variant="contained" onClick={handleAdd}>
|
||||
+ <Button variant="contained" onClick={() => onAdd(title.current)}>
|
||||
+ Add
|
||||
+ </Button>
|
||||
</Box>
|
||||
|
||||
@@ -66,7 +66,7 @@ To actually attach and run the plugin router, you will make some modifications
|
||||
to your backend.
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @internal/plugin-carmen-backend@^0.1.1 # Change this to match the plugin's package.json
|
||||
```
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ title: New Backend System
|
||||
description: Details of the upcoming backend system
|
||||
---
|
||||
|
||||
> **DISCLAIMER: The new backend system is under active development and is not considered stable**
|
||||
> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
|
||||
|
||||
## Status
|
||||
|
||||
The new backend system is under active development, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
|
||||
The new backend system is in alpha, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
|
||||
|
||||
You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
|
||||
|
||||
@@ -84,7 +84,7 @@ import {
|
||||
// export type ExamplePluginOptions = { exampleOption: boolean };
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
// unique id for the plugin
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
// It's possible to provide options to the plugin
|
||||
// register(env, options: ExamplePluginOptions) {
|
||||
register(env) {
|
||||
@@ -111,7 +111,7 @@ If we wanted our plugin to accept options as well, we'd accept the options as th
|
||||
|
||||
```ts
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env, options?: { silent?: boolean }) {
|
||||
env.registerInit({
|
||||
deps: { logger: coreServices.logger },
|
||||
|
||||
+1
-11
@@ -371,14 +371,4 @@ Note: wrapping in the test application **requires** you to do a `find()` or
|
||||
|
||||
# Debugging Jest Tests
|
||||
|
||||
Currently, debugging Jest tests using IntelliJ or `node-debugger` is possible
|
||||
but can be
|
||||
[problematic to set up.](https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000634564-Debugging-Jest-unit-tests)
|
||||
|
||||
It is possible, but you might spend a decent amount of time configuring your
|
||||
IDE.
|
||||
|
||||
In most cases, we have found that using `console.log` works well.
|
||||
|
||||
Note: if your console.logs are not being displayed, focus your specific unit
|
||||
test from the command line by running them like so `yarn test MyTest`.
|
||||
You can find it [here](https://backstage.io/docs/local-dev/cli-build-system#debugging-jest-tests)
|
||||
|
||||
@@ -40,7 +40,7 @@ i.e.`packages/backend/src/index.ts`.
|
||||
```ts
|
||||
// File: packages/backend/src/index.ts
|
||||
|
||||
import { URLReaders } from '@backstage/backend-common';
|
||||
import { UrlReaders } from '@backstage/backend-common';
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
// ....
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ package. If you intend to use both PostgreSQL and SQLite, you can install
|
||||
both of them.
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
# From your Backstage root directory
|
||||
# install pg if you need PostgreSQL
|
||||
yarn add --cwd packages/backend pg
|
||||
|
||||
|
||||
Reference in New Issue
Block a user