Merge branch 'backstage:master' into master
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
---
|
||||
id: cfaccess
|
||||
title: Cloudflare Access Provider
|
||||
sidebar_label: cfaccess
|
||||
# prettier-ignore
|
||||
description: Adding Cloudflare Access as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
Similar to GCP IAP Proxy Provider or AWS ALB provider, developers can offload authentication
|
||||
support to Cloudflare Access.
|
||||
|
||||
This tutorial shows how to use authentication on Cloudflare Access sitting in
|
||||
front of Backstage.
|
||||
|
||||
It is assumed a Cloudflare tunnel is already serving traffic in front of a
|
||||
Backstage instance configured to serve the frontend app from the backend and is
|
||||
already gated using Cloudflare Access.
|
||||
|
||||
## Configuration
|
||||
|
||||
Let's start by adding the following `auth` configuration in your
|
||||
`app-config.yaml` or `app-config.production.yaml` or similar:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
cfaccess:
|
||||
teamName: <Team Name>
|
||||
```
|
||||
|
||||
You can find the team name in the Cloudflare Zero Trust dashboard.
|
||||
|
||||
This config section must be in place for the provider to load at all. Now let's
|
||||
add the provider itself.
|
||||
|
||||
## Backend Changes
|
||||
|
||||
Add a `providerFactories` entry to the router in
|
||||
`packages/backend/plugin/auth.ts`.
|
||||
|
||||
```ts
|
||||
import { providers } from '@backstage/plugin-auth-backend';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
database: env.database,
|
||||
discovery: env.discovery,
|
||||
providerFactories: {
|
||||
'cfaccess': providers.cfAccess.create({
|
||||
// Replace the auth handler if you want to customize the returned user
|
||||
// profile info (can be left out; the default implementation is shown
|
||||
// below which only returns the email). You may want to amend this code
|
||||
// with something that loads additional user profile data out.
|
||||
async authHandler({ accessToken }) {
|
||||
return { profile: { email: accessToken.email } };
|
||||
},
|
||||
signIn: {
|
||||
// You need to supply an identity resolver, that takes the profile
|
||||
// and the access token and produces the Backstage token with the
|
||||
// relevant user info.
|
||||
async resolver({ profile, result }, ctx) {
|
||||
// Somehow compute the Backstage token claims. Just some dummy code
|
||||
// shown here, but you may want to query your LDAP server, or
|
||||
// https://<teamName>.cloudflareaccess.com/cdn-cgi/access/get-identity
|
||||
// https://developers.cloudflare.com/cloudflare-one/identity/users/validating-json/#groups-within-a-jwt
|
||||
const id = profile.email.split('@')[0];
|
||||
const sub = stringifyEntityRef({ kind: 'User', name: id });
|
||||
const ent = [sub, stringifyEntityRef({ kind: 'Group', name: 'team-name' });
|
||||
return ctx.issueToken({ claims: { sub, ent } });
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Now the backend is ready to serve auth requests on the
|
||||
`/api/auth/cfaccess/refresh` endpoint. All that's left is to update the
|
||||
frontend sign-in mechanism to poll that endpoint through Cloudflare Access, on
|
||||
the user's behalf.
|
||||
|
||||
## Frontend Changes
|
||||
|
||||
It is recommended to use the `ProxiedSignInPage` for this provider, which is
|
||||
installed in `packages/app/src/App.tsx` like this:
|
||||
|
||||
```diff
|
||||
+import { ProxiedSignInPage } from '@backstage/core-components';
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
+ SignInPage: props => <ProxiedSignInPage {...props} provider="cfaccess" />,
|
||||
```
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
It is recommended to use the `ProxiedSignInPage` for this provider, which is
|
||||
installed in `packages/app/src/App.tsx` like this:
|
||||
|
||||
```diff
|
||||
+import { ProxiedSignInPage } from '@backstage/core-components';
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
+ SignInPage: props => <ProxiedSignInPage {...props} provider="cfaccess" />,
|
||||
```
|
||||
|
||||
See [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) for pointers on how to set up the sign-in page to also work smoothly for local development.
|
||||
@@ -273,46 +273,57 @@ that the user belongs to using the user access token in the provided result obje
|
||||
|
||||
```ts
|
||||
// File: packages/backend/src/plugins/auth.ts
|
||||
import { DEFAULT_NAMESPACE, stringifyEntityRef, } from '@backstage/catalog-model';
|
||||
import { createRouter, providers } from '@backstage/plugin-auth-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import {
|
||||
stringifyEntityRef,
|
||||
DEFAULT_NAMESPACE,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
export default async function createPlugin(
|
||||
// ...
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
return await createRouter({
|
||||
// ...
|
||||
...env,
|
||||
providerFactories: {
|
||||
// ...
|
||||
google: async ({ profile }, ctx) => {
|
||||
if (!profile.email) {
|
||||
throw new Error(
|
||||
'Login failed, user profile does not contain an email',
|
||||
);
|
||||
}
|
||||
// Split the email into the local part and the domain.
|
||||
const [localPart, domain] = profile.email.split('@');
|
||||
google: providers.google.create({
|
||||
signIn: {
|
||||
resolver: async ({ profile }, ctx) => {
|
||||
if (!profile.email) {
|
||||
throw new Error(
|
||||
'Login failed, user profile does not contain an email',
|
||||
);
|
||||
}
|
||||
// Split the email into the local part and the domain.
|
||||
const [localPart, domain] = profile.email.split('@');
|
||||
|
||||
// Next we verify the email domain. It is recommended to include this
|
||||
// kind of check if you don't look up the user in an external service.
|
||||
if (domain !== 'acme.org') {
|
||||
throw new Error('Login failed, user email domain check failed');
|
||||
}
|
||||
// Next we verify the email domain. It is recommended to include this
|
||||
// kind of check if you don't look up the user in an external service.
|
||||
if (domain !== 'acme.org') {
|
||||
throw new Error(
|
||||
`Login failed, this email ${profile.email} does not belong to the expected domain`,
|
||||
);
|
||||
}
|
||||
|
||||
// By using `stringifyEntityRef` we ensure that the reference is formatted correctly
|
||||
const userEntityRef = stringifyEntityRef({
|
||||
kind: 'User',
|
||||
name: localPart,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
});
|
||||
|
||||
return ctx.issueToken({
|
||||
claims: {
|
||||
sub: userEntityRef,
|
||||
ent: [userEntityRef],
|
||||
// By using `stringifyEntityRef` we ensure that the reference is formatted correctly
|
||||
const userEntity = stringifyEntityRef({
|
||||
kind: 'User',
|
||||
name: localPart,
|
||||
namespace: DEFAULT_NAMESPACE,
|
||||
});
|
||||
return ctx.issueToken({
|
||||
claims: {
|
||||
sub: userEntity,
|
||||
ent: [userEntity],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## AuthHandler
|
||||
|
||||
+2
-1
@@ -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)
|
||||
- [Cloudflare Access](cloudflare/access.md)
|
||||
- [GitHub](github/provider.md)
|
||||
- [GitLab](gitlab/provider.md)
|
||||
- [Google](google/provider.md)
|
||||
@@ -131,7 +132,7 @@ allows allowing guest access:
|
||||
|
||||
Some auth providers are so-called "proxy" providers, meaning they're meant to be used
|
||||
behind an authentication proxy. Examples of these are
|
||||
[AWS ALB](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md),
|
||||
[AWS ALB](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md), [Cloudflare Access](./cloudflare/access.md),
|
||||
[GCP IAP](./google/gcp-iap-auth.md), and [OAuth2 Proxy](./oauth2-proxy/provider.md).
|
||||
|
||||
When using a proxy provider, you'll end up wanting to use a different sign-in page, as
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
id: scaling
|
||||
title: Scaling Backstage Deployments
|
||||
sidebar_label: Scaling
|
||||
description: Scaling Backstage production deployments
|
||||
---
|
||||
|
||||
There are several different methods for scaling Backstage deployments. The most
|
||||
straight-forward one is to simply deploy multiple identical instances and distributing
|
||||
incoming requests across them. The one requirement for this to work is that all instances
|
||||
need to share the same external resources, such as the database, and optional caching or
|
||||
search services. The Backstage backend plugins will coordinate through the database
|
||||
to share state and coordinate work.
|
||||
|
||||
Another method for scaling Backstage deployments is to break apart the backend
|
||||
into multiple different services, each running a different set of plugins. This
|
||||
is a more advanced approach and requires you to be able to route requests to
|
||||
the appropriate backends based on the plugin ID. Both for ingress, but also
|
||||
internal traffic between Backstage backends, which is done by creating a custom
|
||||
implementation of the [PluginEndpointDiscover](../reference/backend-common.pluginendpointdiscovery.md) interface.
|
||||
|
||||
Lastly, you can also replicate the Backstage deployments across multiple regions.
|
||||
This is not a pattern that there is built-in support for and typically only makes
|
||||
sense to do for individual backend plugins.
|
||||
@@ -57,11 +57,16 @@ This is an array used to determine where to retrieve cluster configuration from.
|
||||
|
||||
Valid cluster locator methods are:
|
||||
|
||||
- [`catalog`](#catalog)
|
||||
- [`localKubectlProxy`](#localKubectlProxy)
|
||||
- [`config`](#config)
|
||||
- [`gke`](#gke)
|
||||
- [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier)
|
||||
|
||||
#### `catalog`
|
||||
|
||||
This cluster locator method will read cluster information from the catalog.
|
||||
|
||||
#### `localKubectlProxy`
|
||||
|
||||
This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001).
|
||||
|
||||
@@ -14,60 +14,16 @@ Backstage Search lets you find the right information you are looking for in the
|
||||
|
||||
## Features
|
||||
|
||||
- A federated, faceted search, searching across all entities registered in your Backstage instance.
|
||||
- A search that lets you bring your own search engine.
|
||||
- A search that lets you extend it by creating collators for easily indexing content from plugins and other sources.
|
||||
- A search that lets you create composable search page experiences.
|
||||
- A search that lets you customize the look and feel of each search result.
|
||||
|
||||
- A search that lets you plug in your own search engine of choice.
|
||||
|
||||
- A standardized search API where you can choose to index data from other plugins.
|
||||
|
||||
## Project roadmap
|
||||
|
||||
### Now
|
||||
|
||||
**Backstage Search 1.0**
|
||||
|
||||
A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users.
|
||||
|
||||
We will consider Backstage Search to be 1.0 when the above
|
||||
use-cases are met, and an ecosystem of search-enabled plugins are available and
|
||||
stable.
|
||||
|
||||
- As a plugin developer, there should be at least one example of a Backstage
|
||||
plugin that integrates with search that I can use as inspiration for my own
|
||||
plugin's search capabilities (for example, the TechDocs plugin).
|
||||
- As an app integrator, there should be plenty of examples and documentation on
|
||||
how to customize and extend search in my Backstage instance to meet my
|
||||
organization's needs.
|
||||
|
||||
### Next
|
||||
|
||||
_Not specified_
|
||||
|
||||
### Someday/Maybe
|
||||
|
||||
_Not specified_
|
||||
|
||||
### Done
|
||||
|
||||
See [Done](#done) below for a list of completed roadmap items.
|
||||
See the more detailed [architecture](./architecture.md) and [tech stack](./architecture.md#tech-stack).
|
||||
|
||||
## Supported
|
||||
|
||||
The following sections show the search engines and plugins currently supported by Backstage Search.
|
||||
|
||||
### Search engines
|
||||
|
||||
See [Backstage Search Architecture](architecture.md) to get an overview of how
|
||||
the search engines are used.
|
||||
|
||||
| Search Engines | Support Status |
|
||||
| -------------------------------------------------- | -------------- |
|
||||
| [Lunr](./search-engines.md#lunr) | ✅ |
|
||||
| [ElasticSearch](./search-engines.md#elasticsearch) | ✅ |
|
||||
| [Postgres](./search-engines.md#postgres) | ✅ |
|
||||
|
||||
[Reach out to us](#get-involved) if you want to chat about support for more
|
||||
search engines.
|
||||
The following sections show the plugins and search engines currently supported by Backstage Search.
|
||||
|
||||
### Plugins integrated with Backstage Search
|
||||
|
||||
@@ -77,20 +33,19 @@ search engines.
|
||||
| [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ |
|
||||
| [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/stack-overflow-backend/README.md#index-stack-overflow-questions-to-search) | ✅ |
|
||||
|
||||
[Reach out to us](#get-involved) if you want to chat about support for more
|
||||
plugins integrated to search.
|
||||
### Search engines
|
||||
|
||||
## Tech Stack
|
||||
See [Backstage Search Architecture](architecture.md) to get an overview of how
|
||||
the search engines are used.
|
||||
|
||||
| Stack | Location |
|
||||
| ------------------------- | ----------------------------------------------------- |
|
||||
| Frontend Plugin | @backstage/plugin-search |
|
||||
| Frontend Plugin Library | @backstage/plugin-search-react |
|
||||
| Isomorphic Plugin Library | @backstage/plugin-search-common |
|
||||
| Backend Plugin | @backstage/plugin-search-backend |
|
||||
| Backend Plugin Library | @backstage/plugin-search-backend-node |
|
||||
| Backend Plugin Module | @backstage/plugin-search-backend-module-elasticsearch |
|
||||
| Backend Plugin Module | @backstage/plugin-search-backend-module-pg |
|
||||
| Search Engines | Support Status |
|
||||
| -------------------------------------------------- | -------------- |
|
||||
| [ElasticSearch](./search-engines.md#elasticsearch) | ✅ |
|
||||
| [Lunr](./search-engines.md#lunr) | ✅ |
|
||||
| [Postgres](./search-engines.md#postgres) | Community ✅ |
|
||||
|
||||
[Reach out to us](#get-involved) if you want to chat about support for more plugin integrations and
|
||||
search engines.
|
||||
|
||||
## Get involved
|
||||
|
||||
@@ -98,9 +53,9 @@ For any questions, feedback, or to help move search forward, reach out to us in
|
||||
the **#search** channel of our
|
||||
[Discord chatroom](https://github.com/backstage/backstage#community).
|
||||
|
||||
## Done
|
||||
## Use Cases supported
|
||||
|
||||
**Backstage Search Pre-Alpha**
|
||||
#### **Backstage Search Pre-Alpha**
|
||||
|
||||
Search Frontend letting you search through the entities of the software catalog.
|
||||
|
||||
@@ -118,7 +73,7 @@ there by means of a front-end only, non-extensible MVP.
|
||||
- As a software engineer I should be able to hide the filters if I don’t need to
|
||||
use them.
|
||||
|
||||
**Backstage Search Alpha**
|
||||
#### **Backstage Search Alpha**
|
||||
|
||||
Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog.
|
||||
|
||||
@@ -136,7 +91,7 @@ met, but built on top of a flexible, extensible platform.
|
||||
should be possible to have the pre-alpha user experiences covered without
|
||||
having to set up and configure a search engine.
|
||||
|
||||
**Backstage Search Beta**
|
||||
#### **Backstage Search Beta**
|
||||
|
||||
At least one production-ready search engine that supports the same use-cases as in the alpha.
|
||||
|
||||
@@ -151,3 +106,18 @@ are met, and can be deployed using a production-ready search engine.
|
||||
- As an integrator, I should be able to tune the queries sent to my chosen
|
||||
search engine according to my organization's needs, but a sensible default
|
||||
query should be in place so that I am not required to do so.
|
||||
|
||||
#### **Backstage Search 1.0**
|
||||
|
||||
A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users.
|
||||
|
||||
We will consider Backstage Search to be 1.0 when the above
|
||||
use-cases are met, and an ecosystem of search-enabled plugins are available and
|
||||
stable.
|
||||
|
||||
- As a plugin developer, there should be at least one example of a Backstage
|
||||
plugin that integrates with search that I can use as inspiration for my own
|
||||
plugin's search capabilities (for example, the TechDocs plugin).
|
||||
- As an app integrator, there should be plenty of examples and documentation on
|
||||
how to customize and extend search in my Backstage instance to meet my
|
||||
organization's needs.
|
||||
|
||||
@@ -4,10 +4,6 @@ title: Search Architecture
|
||||
description: Documentation on Search Architecture
|
||||
---
|
||||
|
||||
> _This architecture has not been fully implemented yet. Find our milestones to
|
||||
> follow our progress and help contribute on the
|
||||
> [Search Roadmap](./README.md#project-roadmap)._
|
||||
|
||||
Below you can explore the Search Architecture. Our aim with this architecture is
|
||||
to support a wide variety of search engines, while providing a simple developer
|
||||
experience for plugin developers, and a good out-of-the-box experience for
|
||||
@@ -44,3 +40,15 @@ Architecture non-goals:
|
||||
- At this time, we do not intend to directly support event-driven or incremental
|
||||
index management. Instead, we'll be focused on scheduled, bulk index
|
||||
management.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Stack | Location |
|
||||
| ------------------------- | ----------------------------------------------------- |
|
||||
| Frontend Plugin | @backstage/plugin-search |
|
||||
| Frontend Plugin Library | @backstage/plugin-search-react |
|
||||
| Isomorphic Plugin Library | @backstage/plugin-search-common |
|
||||
| Backend Plugin | @backstage/plugin-search-backend |
|
||||
| Backend Plugin Library | @backstage/plugin-search-backend-node |
|
||||
| Backend Plugin Module | @backstage/plugin-search-backend-module-elasticsearch |
|
||||
| Backend Plugin Module | @backstage/plugin-search-backend-module-pg |
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
DefaultResultListItem,
|
||||
SearchFilter,
|
||||
} from '@backstage/plugin-search-react';
|
||||
import { CatalogResultListItem } from '@backstage/plugin-catalog';
|
||||
import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
|
||||
|
||||
export const searchPage = (
|
||||
<Page themeId="home">
|
||||
|
||||
@@ -148,271 +148,6 @@ indexBuilder.addCollator({
|
||||
As shown above, you can add a catalog entity filter to narrow down what catalog
|
||||
entities are indexed by the search engine.
|
||||
|
||||
## How to migrate from Search Alpha to Beta
|
||||
|
||||
For the purposes of this guide, Search Beta version is defined as:
|
||||
|
||||
- **Search Plugin**: At least `v0.7.2`
|
||||
- **Search Backend Plugin**: At least `v0.4.6`
|
||||
- **Search Backend Node**: At least `v0.5.0`
|
||||
- **Search Common**: At least `v0.3.0`
|
||||
|
||||
In the Beta version, the Search Platform's indexing process has been rewritten
|
||||
as a stream pipeline in order to improve efficiency and performance on large
|
||||
sets of documents.
|
||||
|
||||
If you've not yet extended the Search Platform with custom code, and have
|
||||
instead taken advantage of default collators, decorators, and search engines
|
||||
provided by existing plugins, the migration process is fairly straightforward:
|
||||
|
||||
1. Upgrade to at least version `0.5.0` of
|
||||
`@backstage/plugin-search-backend-node`, as well as any backend plugins whose
|
||||
collators you are using (e.g. at least version `0.23.0` of
|
||||
`@backstage/plugin-catalog-backend` and/or version `0.14.1` of
|
||||
`@backstage/plugin-techdocs-backend`), as well as any search-engine specific
|
||||
plugin you are using (e.g. at least version `0.3.0` of
|
||||
`@backstage/plugin-search-backend-module-pg` or version `0.1.0` of
|
||||
`@backstage/plugin-search-backend-module-elasticsearch`).
|
||||
2. Then, make the following changes to your
|
||||
`/packages/backend/src/plugins/search.ts` file:
|
||||
|
||||
```diff
|
||||
-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
|
||||
-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend';
|
||||
+import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend';
|
||||
+import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
|
||||
// ...
|
||||
const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine });
|
||||
indexBuilder.addCollator({
|
||||
defaultRefreshIntervalSeconds: 600,
|
||||
- collator: DefaultCatalogCollator.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
}),
|
||||
+ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
}),
|
||||
});
|
||||
indexBuilder.addCollator({
|
||||
defaultRefreshIntervalSeconds: 600,
|
||||
- collator: DefaultTechDocsCollator.fromConfig(env.config, {
|
||||
+ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
logger: env.logger,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Any custom collators, decorators, or search engine implementations will require
|
||||
minor refactoring. Continue on for details.
|
||||
|
||||
### Rewriting alpha-style collators for beta
|
||||
|
||||
In alpha versions of the Backstage Search Platform, collators were classes that
|
||||
implemented an `execute` method which resolved an `IndexableDocument` array.
|
||||
|
||||
In beta versions, the logic encapsulated by the aforementioned `execute` method
|
||||
is contained within an [object-mode][obj-mode] `Readable` stream where each
|
||||
object pushed onto the stream is of type `IndexableDocument`. Instances of this
|
||||
stream are instantiated by a factory class conforming to the
|
||||
`DocumentCollatorFactory` interface.
|
||||
|
||||
The optimal conversion strategy will vary depending on the collator's logic, but
|
||||
the simplest conversion can follow a process like this:
|
||||
|
||||
1. Rename your collator class to something like `YourCollatorFactory` and update
|
||||
it to implement `DocumentCollatorFactory` instead of `DocumentCollator`.
|
||||
2. Update its `execute` method so that it resolves
|
||||
`AsyncGenerator<YourIndexableDocument>` instead of `YourIndexableDocument[]`.
|
||||
3. Implement `DocumentCollatorFactory`'s `getCollator` method which resolves to
|
||||
`Readable.from(this.execute())` (which is a utility for creating [readable
|
||||
streams][read-stream] from [async generators][async-gen]).
|
||||
|
||||
```ts
|
||||
import { DocumentCollatorFactory } from '@backstage/plugin-search-backend-node';
|
||||
import { Readable } from 'stream';
|
||||
export class YourCollatorFactory implements DocumentCollatorFactory {
|
||||
public readonly type: string = 'your-type';
|
||||
async *execute(): AsyncGenerator<YourIndexableDocument> {
|
||||
const widgets = await this.client.getWidgets();
|
||||
for (const widget of widgets) {
|
||||
yield {
|
||||
title: widget.name,
|
||||
location: widget.url,
|
||||
text: widget.description,
|
||||
};
|
||||
}
|
||||
}
|
||||
async getCollator() {
|
||||
return Readable.from(this.execute());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: it may be possible to simplify your collator dramatically! If your custom
|
||||
collator was previously using streams under the hood (for example, by reading
|
||||
newline delimited JSON from a local or remote file), you could just expose the
|
||||
stream directly via a simple factory class:
|
||||
|
||||
```ts
|
||||
import { DocumentCollatorFactory } from '@backstage/plugin-search-backend-node';
|
||||
import { createReadStream } from 'fs';
|
||||
import { parse } from '@jsonlines/core';
|
||||
export class YourCollatorFactory implements DocumentCollatorFactory {
|
||||
public readonly type: string = 'your-type';
|
||||
async getCollator() {
|
||||
const parseStream = parse();
|
||||
return createReadStream('./documents.ndjson').pipe(parseStream);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Rewriting alpha-style decorators for beta
|
||||
|
||||
In alpha versions of the Backstage Search Platform, decorators were classes that
|
||||
implemented an `execute` method which took an `IndexableDocument` array as an
|
||||
argument, and resolved a modified array of the same type.
|
||||
|
||||
In beta versions, the logic encapsulated by the aforementioned `execute` method
|
||||
is contained within an object-mode `Transform` stream which reads objects of
|
||||
type `IndexableDocument`, and writes objects of a conforming type. Similar to
|
||||
collators, instances of this stream are instantiated by a factory class
|
||||
conforming to the `DocumentDecoratorFactory` interface.
|
||||
|
||||
Although you can choose to implement a `Transform` stream from scratch, the
|
||||
`@backstage/plugin-search-backend-node` package provides a `DecoratorBase` class
|
||||
in order to simplify the developer experience. With this base class, all that's
|
||||
needed is to transfer your old decorator class logic into the base class' three
|
||||
methods (`initialize`, `decorate`, and `finalize`), and implement the factory
|
||||
class that instantiates the stream:
|
||||
|
||||
```ts
|
||||
import { DecoratorBase } from '@backstage/plugin-search-backend-node';
|
||||
export class YourDecorator extends DecoratorBase {
|
||||
async initialize() {
|
||||
// Setup logic. Performed once before any documents are consumed.
|
||||
}
|
||||
async decorate(
|
||||
document: YourIndexableDocument,
|
||||
): Promise<YourIndexableDocument | YourIndexableDocument[] | undefined> {
|
||||
// Perform transformation logic here.
|
||||
return document;
|
||||
}
|
||||
async finalize() {
|
||||
// Teardown logic. Performed once after all documents have been consumed.
|
||||
}
|
||||
}
|
||||
export class YourDecoratorFactory implements DocumentDecoratorFactory {
|
||||
async getDecorator() {
|
||||
return new YourDecorator();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note the return type of the `decorate` method and how each can be used to
|
||||
different effect.
|
||||
|
||||
- By resolving a single `YourIndexableDocument` object, your decorator can be
|
||||
used to make simple transformations:
|
||||
|
||||
```ts
|
||||
class BooleanWidgetCoolnessDecorator extends DecoratorBase {
|
||||
async decorator(widget) {
|
||||
// Perform a simple, 1:1 transformation.
|
||||
widget.isCool = widget.isCool === 'true' ? true : false;
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- By resolving `undefined`, your decorator can filter out documents which
|
||||
shouldn't be in the index:
|
||||
|
||||
```ts
|
||||
class OnlyCoolWidgetsDecorator extends DecoratorBase {
|
||||
async decorator(widget) {
|
||||
// Perform a simple filter operation.
|
||||
return widget.isCool ? widget : undefined;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- By resolving an array of `YourIndexableDocument` objects, you can generate
|
||||
multiple documents based on the content of one:
|
||||
|
||||
```ts
|
||||
class WidgetByVariantDecorator extends DecoratorBase {
|
||||
async decorator(widget) {
|
||||
// Generate one widget doc per widget variant.
|
||||
return widget.variants.map(variant => {
|
||||
// Each widget doc is the given widget plus a "variant" property
|
||||
// pulled from a widget.variants string array.
|
||||
return {
|
||||
...widget,
|
||||
variant,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In alpha versions, a decorator had access to every `IndexableDocument`
|
||||
simultaneously. This is no longer possible in beta versions (precisely to make
|
||||
the indexing process more efficient and performant). You will need to modify
|
||||
your decorator's logic so that it does not need access to every document at
|
||||
once.
|
||||
|
||||
### Rewriting alpha-style search engines for beta
|
||||
|
||||
Search Engines are responsible for both querying and indexing documents to an
|
||||
underlying search engine technology. While the search engine query interface
|
||||
didn't change between alpha and beta versions, the indexing half of the
|
||||
interface _did_ change.
|
||||
|
||||
In alpha versions of the Backstage Search Platform, a search engine implemented
|
||||
an `index` method which took a `type` and an `IndexableDocument` array and was
|
||||
responsible for writing these documents to the underlying search engine.
|
||||
|
||||
In beta versions, the logic encapsulated by the aforementioned `index` method is
|
||||
contained within an object-mode `Writable` stream which expects objects of type
|
||||
`IndexableDocument`. On the search engine class itself, the `index` method is
|
||||
replaced with a `getIndexer` factory method which still takes the `type`, but
|
||||
resolves an instance of the aforementioned `Writable` stream.
|
||||
|
||||
Although you can choose to implement a `Writable` stream from scratch, the
|
||||
`@backstage/plugin-search-backend-node` package provides a
|
||||
`BatchSearchEngineIndexer` class in order to simplify the developer experience.
|
||||
With this base class, which collects documents in batches of a configurable size
|
||||
on your behalf, all that's needed is to transfer your old `index` method logic
|
||||
into the base class' three methods (`initialize`, `index`, and `finalize`), and
|
||||
implement the factory method that instantiates the stream:
|
||||
|
||||
```ts
|
||||
import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';
|
||||
import { SearchEngine } from '@backstage/plugin-search-common';
|
||||
export class YourSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
constructor({ type }: { type: string }) {
|
||||
// Customize the number of documents passed to the index method per batch.
|
||||
super({ batchSize: 500 });
|
||||
// An imaginary search engine indexing client.
|
||||
this.index = new SomeSearchEngineIndex({ indexName: type });
|
||||
}
|
||||
async initialize() {
|
||||
// Setup logic. Performed once before any documents are consumed.
|
||||
}
|
||||
async index(documents: IndexableDocument[]) {
|
||||
await this.index.batchOf(documents);
|
||||
}
|
||||
async finalize() {
|
||||
// Teardown logic. Performed once after all documents have been consumed.
|
||||
}
|
||||
}
|
||||
export class YourSearchEngine implements SearchEngine {
|
||||
async getIndexer(type: string) {
|
||||
return new YourSearchEngineIndexer({ type });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How to customize search results highlighting styling
|
||||
|
||||
The default highlighting styling for matched terms in search results is your
|
||||
|
||||
@@ -232,3 +232,23 @@ search:
|
||||
auth:
|
||||
apiKey: base64EncodedKey
|
||||
```
|
||||
|
||||
### Elastic search batch size
|
||||
|
||||
Default batch size of the elastic search engine is set to 1000. If you are using a lower spec computing resources (like AWS small instance),
|
||||
you may get an error caused by limited `thread_pool` configuration. ( `429 Too Many Requests /_bulk` )
|
||||
|
||||
In this case you need to decrease the batch size to index the resources to prevent this kind of error. You can easily decrease
|
||||
or increase the batch size in your `app-config.yaml` using the `batchSize` option provided for elasticsearch configuration.
|
||||
|
||||
#### Configuration example
|
||||
|
||||
**Set batch size to 100**
|
||||
|
||||
```yaml
|
||||
search:
|
||||
elasticsearch:
|
||||
batchSize: 100
|
||||
```
|
||||
|
||||
> You can also increase the batch size if you are using a large ES instance.
|
||||
|
||||
@@ -93,8 +93,12 @@ spec:
|
||||
|
||||
# some outputs which are saved along with the job for use in the frontend
|
||||
output:
|
||||
remoteUrl: ${{ steps.publish.output.remoteUrl }}
|
||||
entityRef: ${{ steps.register.output.entityRef }}
|
||||
links:
|
||||
- title: Repository
|
||||
url: ${{ steps.publish.output.remoteUrl }}
|
||||
- title: Open in catalog
|
||||
icon: catalog
|
||||
entityRef: ${{ steps.register.output.entityRef }}
|
||||
```
|
||||
|
||||
Let's dive in and pick apart what each of these sections do and what they are.
|
||||
@@ -495,8 +499,12 @@ The main two that are used are the following:
|
||||
|
||||
```yaml
|
||||
output:
|
||||
remoteUrl: ${{ steps.publish.output.remoteUrl }} # link to the remote repository
|
||||
entityRef: ${{ steps.register.output.entityRef }} # link to the entity that has been ingested to the catalog
|
||||
links:
|
||||
- title: Repository
|
||||
url: ${{ steps.publish.output.remoteUrl }} # link to the remote repository
|
||||
- title: Open in catalog
|
||||
icon: catalog
|
||||
entityRef: ${{ steps.register.output.entityRef }} # link to the entity that has been ingested to the catalog
|
||||
```
|
||||
|
||||
## The templating syntax
|
||||
|
||||
@@ -76,27 +76,3 @@ builder.addEntityProvider(
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
## Alternative Processor
|
||||
|
||||
As alternative to the entity provider `AwsS3EntityProvider`
|
||||
you can still use the `AwsS3DiscoveryProcessor`.
|
||||
|
||||
```yaml
|
||||
# app-config.yaml
|
||||
|
||||
catalog:
|
||||
locations:
|
||||
- type: s3-discovery
|
||||
target: https://sample-bucket.s3.us-east-2.amazonaws.com/prefix/
|
||||
```
|
||||
|
||||
```ts
|
||||
/* packages/backend/src/plugins/catalog.ts */
|
||||
|
||||
import { AwsS3DiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-aws';
|
||||
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/** ... other processors ... */
|
||||
builder.addProcessor(new AwsS3DiscoveryProcessor(env.reader));
|
||||
```
|
||||
|
||||
@@ -69,7 +69,7 @@ Alternatively you can use VSCode with the Azure extension if you install `@azure
|
||||
When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions.
|
||||
If you can't do this, you'll have to create an App Registration.
|
||||
|
||||
## App Registration
|
||||
### App Registration
|
||||
|
||||
If none of the other authentication methods work, you can create an app registration in the azure portal.
|
||||
By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph:
|
||||
|
||||
@@ -24,6 +24,8 @@ app:
|
||||
# env: 'staging'
|
||||
```
|
||||
|
||||
If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/master/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/master/packages/app/public/index.html#L64) to include the Datadog RUM `init()` section manually.
|
||||
|
||||
The `clientToken` and `applicationId` are generated from the Datadog RUM page
|
||||
following
|
||||
[these instructions](https://docs.datadoghq.com/real_user_monitoring/browser/).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: v1.4.0
|
||||
title: v1.4.0
|
||||
description: Backstage Release v1.4.0
|
||||
---
|
||||
|
||||
These are the release notes for the v1.4.0 release of [Backstage](https://backstage.io/).
|
||||
|
||||
A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done.
|
||||
|
||||
## Highlights
|
||||
|
||||
### Search is V1!
|
||||
|
||||
Backstage Search is now promoted to v1.0 with a stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users.
|
||||
|
||||
No additional features are released as part of the major release as the maintainers of the search packages focused on the following for this release:
|
||||
|
||||
- Bug fixes and clean up of deprecations.
|
||||
- Improved error handling for missing indices.
|
||||
- Separation of AWS Elastic and Open Elastic support.
|
||||
- Refactor the packages by moving reusable components from `@backstage/plugin-search` to `@backstage/plugin-search-react`.
|
||||
- Improved documentation including tutorials for how to [Integrate Search into a plugin](https://backstage.io/docs/plugins/integrating-search-into-plugins).
|
||||
|
||||
With moving to v1.0 we also recommend using Elasticsearch for production usage. Moving forward, the search maintainers will prioritize the Elasticsearch engine while we’ll rely on the community to maintain the Postgres engine.
|
||||
|
||||
### Experimental Backend System Evolution
|
||||
|
||||
This release adds the new `@backstage/backend-app-api` and `@backstage/backend-plugin-api` packages, both part of the [evolution of the backend system](https://github.com/backstage/backstage/issues/11611). These packages are highly experimental and we do not recommend using them for any purpose, yet.
|
||||
|
||||
### `@backstage/cli` Deprecation Removals
|
||||
|
||||
The `@backstage/cli` had a large number of deprecated commands removed, for example, `app:build` is now gone and should be replaced with `package build`. For a full list of commands that were removed, see the [changelog](https://github.com/backstage/backstage/blob/master/packages/cli/CHANGELOG.md#0180).
|
||||
|
||||
### `@backstage/plugin-auth-backend` Deprecation Removals
|
||||
|
||||
Many deprecated symbols have now been removed from `@backstage/plugin-auth-backend`, most notably the provider factories such as `createGithubProvider` have been removed, which should be replaced with `providers.github.create`. See the [changelog](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/CHANGELOG.md#0150) for more details.
|
||||
|
||||
### New module: `@backstage/plugin-api-docs-module-protoc-gen-doc`
|
||||
|
||||
This is a new module for `@backstage/plugin-api-docs` that exports a widget implementation for rendering gRPC docs generated by `protoc-gen-doc`. Contributed by [@kissmikijr](https://github.com/kissmikijr) [#11923](https://github.com/backstage/backstage/pull/11923).
|
||||
|
||||
### New module: `@backstage/plugin-catalog-backend-module-openapi`
|
||||
|
||||
This is a new module for `@backstage/plugin-catalog-backend` that exports a catalog processor which de-references `$ref` fields in OpenAPI definitions. Contributed by [@mfrinnstrom](https://github.com/mfrinnstrom) [#11645](https://github.com/backstage/backstage/pull/11645).
|
||||
|
||||
### New plugin: `@backstage/plugin-apollo-explorer`
|
||||
|
||||
A plugin to embed the [Apollo Explorer](https://github.com/apollographql/embeddable-explorer/tree/main/packages/explorer) inside your Backstage instance to run queries against GraphQL endpoints. Contributed by [@unredundant](https://github.com/unredundant) [#12600](https://github.com/backstage/backstage/pull/12600).
|
||||
|
||||
## Security Fixes
|
||||
|
||||
`@backstage/plugin-scaffolder-backend`, please upgrade to the latest version if you are using this module.
|
||||
|
||||
## Upgrade path
|
||||
|
||||
We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated).
|
||||
|
||||
## Links and References
|
||||
|
||||
Below you can find a list of links and references to help you learn about and start using this new release.
|
||||
|
||||
- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/)
|
||||
- [GitHub repository](https://github.com/backstage/backstage)
|
||||
- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy)
|
||||
- [Community Discord](https://discord.gg/bFESRKVt) for discussions and support
|
||||
- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.4.0-changelog.md)
|
||||
- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins)
|
||||
|
||||
Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage.
|
||||
Reference in New Issue
Block a user