Merge branch 'backstage:master' into techdocs-config-md

This commit is contained in:
Saptarshi Mula
2026-01-31 06:51:01 +05:30
committed by GitHub
1509 changed files with 38420 additions and 13386 deletions
+2
View File
@@ -44,6 +44,7 @@ auth:
audience: ${AUTH_AUTH0_AUDIENCE}
connection: ${AUTH_AUTH0_CONNECTION}
connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE}
organization: ${AUTH_AUTH0_ORGANIZATION_ID}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
session:
@@ -69,6 +70,7 @@ Auth0 requires a session, so you need to give the session a secret key.
- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections).
- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter.
- `sessionDuration`: Lifespan of the user session.
- `organization`: Specify a specific organization ID to be targeted as part of the login flow.
### Resolvers
+2
View File
@@ -211,6 +211,8 @@ to get the existing session, which is exactly what the `ProxiedSignInPage` does.
thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this:
```tsx title="packages/app/src/App.tsx"
import { ProxiedSignInPage } from '@backstage/core-components';
const app = createApp({
components: {
SignInPage: props => <ProxiedSignInPage {...props} provider="awsalb" />,
+14 -2
View File
@@ -136,7 +136,15 @@ auth:
- resolver: emailMatchingUserEntityProfileEmail
```
If none of the built-in resolvers are suitable, you can alternatively write a custom resolver. See an example below:
If none of the built-in resolvers are suitable, you can alternatively write a custom resolver.
First, install the OIDC provider module:
```bash
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider
```
Then create a custom resolver as shown below:
```ts title="in packages/backend/src/index.ts"
/* highlight-add-start */
@@ -146,6 +154,10 @@ import {
createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import {
stringifyEntityRef,
DEFAULT_NAMESPACE,
} from '@backstage/catalog-model';
const myAuthProviderModule = createBackendModule({
// This ID must be exactly "auth" because that's the plugin it targets
@@ -168,7 +180,7 @@ const myAuthProviderModule = createBackendModule({
async signInResolver(info, ctx) {
const userRef = stringifyEntityRef({
kind: 'User',
name: info.result.userinfo.sub,
name: info.result.fullProfile.userinfo.sub,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
@@ -65,6 +65,64 @@ export const scaffolderPlugin = createBackendPlugin(
Note that we create a closure that adds to a shared `actions` structure when `addAction` is called by users of your extension point. It is safe for us to then access our `actions` in the `init` method of our plugin, since all modules that extend our plugin will be completely initialized before our plugin gets initialized. That means that at the point where our `init` method is called, all actions have been added and can be accessed.
## Factory-Based Extension Points
In some cases, you may want to be able to attribute startup failures to modules that provided an extension, rather than failing the plugin startup entirely. To do this, you can use a variant of `registerExtensionPoint` that instead of providing a direct implementation, registers a factory function that produces the implementation. This factory receives an `ExtensionPointFactoryContext` with a `reportModuleStartupFailure` method that lets you report startup failures and attribute them to the module.
Here's an example of registering an extension point using a factory:
```ts
import {
createBackendPlugin,
ExtensionPointFactoryContext,
} from '@backstage/backend-plugin-api';
import { assertError, ForwardedError } from '@backstage/errors';
import { createProviderConnection, Provider } from './internal';
type ProviderEntry = {
provider: Provider;
context: ExtensionPointFactoryContext;
};
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
const providers: ProviderEntry[] = [];
// Using the variant of registerExtensionPoint that takes an options object.
env.registerExtensionPoint({
extensionPoint: exampleProvidersExtensionPoint,
// The factory function produces a separate instance for each module.
factory: context => ({
addProvider(provider) {
// Store the context together with the provider so we can report failures later
providers.push({ provider, context });
},
}),
});
env.registerInit({
deps: { database: coreServices.database },
async init({ database }) {
for (const { provider, context } of providers) {
const connection = await createProviderConnection(provider, database);
try {
// This connects each provider that was installed by a module
await provider.connect(connection);
} catch (error: unknown) {
// If the connection fails, we can report this as a failure of the module rather than the plugin
assertError(error);
context.reportModuleStartupFailure({
error: new ForwardedError('Failed to connect provider', error),
});
}
}
},
});
},
});
```
## Module Extension Points
Just like plugins, modules can also provide their own extension points. The API for registering and using extension points is the same as for plugins. However, modules should typically only use extension points to allow for complex internal customizations by users of the plugin module. It is therefore preferred to export the extension point directly from the module package, rather than creating a separate node library for that purpose. Extension points exported by a module are used the same way as extension points exported by a plugin, you create your own separate module and declare a dependency on the extension point that you want to interact with.
@@ -660,7 +660,7 @@ depends on the appropriate extension point and interacts with it.
```ts title="packages/backend/src/index.ts"
/* highlight-add-start */
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { createBackendModule } from '@backstage/backend-plugin-api';
/* highlight-add-end */
@@ -164,7 +164,7 @@ Note that requests towards `/api/*` will never be handled by the `routes` handle
The root HTTP Router service also allows for configuration of the underlying Node.js HTTP server object. This is useful for modifying settings on the HTTP server itself, such as server [`timeout`](https://nodejs.org/api/http.html#servertimeout), [`keepAliveTimeout`](https://nodejs.org/api/http.html#serverkeepalivetimeout), and [`headersTimeout`](https://nodejs.org/api/http.html#serverheaderstimeout).
A `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration
An `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration
```ts
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
+1 -1
View File
@@ -40,7 +40,7 @@ Docs are published to [backstage.io/docs](https://backstage.io/docs). If you
contribute to the documentation, you might want to preview your changes before
submitting them. You'll find the website sources under [/microsite](https://github.com/backstage/backstage/tree/master/microsite)
with instructions for building and locally serving the website in the
[README](/microsite#readme).
[README](https://github.com/backstage/backstage/blob/master/microsite/README.md).
For additional information and helpful guidelines on how to contribute to the documentation, check out these [Documentation Guidelines](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#documentation-guidelines)!
@@ -0,0 +1,49 @@
---
id: audit-events
title: Audit Events
description: Tracking access to your Software Catalog.
---
The Catalog backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction within an operation group.
## Entity Events
- **`entity-fetch`**: Retrieves entities.
- **Note:** By default, "low" severity audit events like `entity-fetch` aren't logged because they map to the "debug" level, while Backstage defaults to "info" level logging. To see `entity-fetch` events, update your `app-config.yaml` by setting `backend.auditor.severityLogLevelMappings.low: info`. See the [Auditor Service documentation](https://backstage.io/docs/backend-system/core-services/auditor/#severity-levels-and-default-mappings) for details on severity mappings.
Filter on `queryType`.
- **`all`**: Fetching all entities. (GET `/entities`)
- **`by-id`**: Fetching a single entity using its UID. (GET `/entities/by-uid/:uid`)
- **`by-name`**: Fetching a single entity using its kind, namespace, and name. (GET `/entities/by-name/:kind/:namespace/:name`)
- **`by-query`**: Fetching multiple entities using a filter query. (GET `/entities/by-query`)
- **`by-refs`**: Fetching a batch of entities by their entity refs. (POST `/entities/by-refs`)
- **`ancestry`**: Fetching the ancestry of an entity. (GET `/entities/by-name/:kind/:namespace/:name/ancestry`)
- **`entity-mutate`**: Modifies entities.
Filter on `actionType`.
- **`delete`**: Deleting a single entity. Note: this will not be a permanent deletion and the entity will be restored if the parent location is still present in the catalog. (DELETE `/entities/by-uid/:uid`)
- **`refresh`**: Scheduling an entity refresh. (POST `/entities/refresh`)
- **`entity-validate`**: Validates an entity. (POST `/entities/validate`)
- **`entity-facets`**: Retrieves entity facets. (GET `/entity-facets`)
## Location Events
- **`location-fetch`**: Retrieves locations.
Filter on `actionType`.
- **`all`**: Fetching all locations. (GET `/locations`)
- **`by-id`**: Fetching a single location by ID. (GET `/locations/:id`)
- **`by-entity`**: Fetching locations associated with an entity ref. (GET `/locations/by-entity`)
- **`location-mutate`**: Modifies locations.
- **`create`**: Creating a new location. (POST `/locations`)
- **`delete`**: Deleting a location and its associated entities. (DELETE `/locations/:id`)
- **`location-analyze`**: Analyzes a location. (POST `/locations/analyze`)
@@ -596,7 +596,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
/* highlight-add-next-line */
import { FoobarEntitiesProcessor } from './providers';
@@ -294,7 +294,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { FrobsProvider } from './path/to/class';
export const catalogModuleFrobsProvider = createBackendModule({
@@ -737,7 +737,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { SystemXReaderProcessor } from '../path/to/class';
export const catalogModuleSystemXReaderProcessor = createBackendModule({
@@ -0,0 +1,32 @@
---
id: audit-events
title: Audit Events
description: Tracking access to your Scaffolder.
---
The Scaffolder backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction when needed.
## Template Events
- **`template-parameter-schema`**: Retrieves template parameter schemas. (GET `/v2/templates/:namespace/:kind/:name/parameter-schema`)
## Action Events
- **`action-fetch`**: Retrieves installed actions. (GET `/v2/actions`)
## Task Events
- **`task`**: Operations related to Scaffolder tasks.
Filter on `actionType`.
- **`create`**: Creates a new task. (POST `/v2/tasks`)
- **`list`**: Fetches details of all tasks. (GET `/v2/tasks`)
- **`get`**: Fetches details of a specific task. (GET `/v2/tasks/:taskId`)
- **`cancel`**: Cancels a running task. (POST `/v2/tasks/:taskId/cancel`)
- **`retry`**: Retries a failed task. (POST `/v2/tasks/:taskId/retry`)
- **`stream`**: Retrieves a stream of task logs. (GET `/v2/tasks/:taskId/eventstream`)
- **`events`**: Retrieves a snapshot of task logs. (GET `/v2/tasks/:taskId/events`)
- **`dry-run`**: Creates a dry-run task. (POST `/v2/dry-run`) All audit logs for events associated with dry runs have the `meta.isDryLog` flag set to `true`.
- **`stale-cancel`**: Automated cancellation of stale tasks.
- **`execute`**: Tracks the initiation and completion of a real scaffolder task execution. This event will not occur during dry runs.
@@ -91,8 +91,8 @@ The `createTemplateAction` takes an object which specifies the following:
may ship with the `scaffolder-backend` plugin.
- `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions`
endpoint.
- `schema.input` - A `zod` or JSON schema object for input values to your function
- `schema.output` - A `zod` or JSON schema object for values which are output from the
- `schema.input` - A `zod` schema object for input values to your function
- `schema.output` - A `zod` schema object for values which are output from the
function using `ctx.output`
- `handler` - the actual code which is run as part of the action, with a context
@@ -195,7 +195,7 @@ argument. It looks like the following:
executed successfully on the previous run.
- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action
- `ctx.workspacePath` - a string of the working directory of the template run
- `ctx.input` - an object which should match the `zod` or JSON schema provided in the
- `ctx.input` - an object which should match the `zod` schema provided in the
`schema.input` part of the action definition
- `ctx.output` - a function which you can call to set outputs that match the
`zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
@@ -174,6 +174,8 @@ const navigationExtension = createExtension({
The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can be attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly one attached extension.
Another option that can be used when creating an extension input is the `internal: true` option, which restricts the input to only accept extensions from the same plugin as the extension defining the input. Extensions from other plugins that attempt to attach to an internal input will be ignored, and a warning will be reported. This is useful when you want to limit extensibility to overrides and modules of your plugin, rather than letting it be open to any plugin.
So how can we now attach the output to the parent extension's input? If we think about a navigation component, like the Sidebar in Backstage, there might be plugins that want to attach a link to their plugin to this navigation component. In this case the plugin only needs to know the extension `id` and the name of the extension `input` to attach the extension `output` returned by the `factory` to the specified extension:
```tsx
@@ -335,23 +337,11 @@ const routableExtension = createExtension({
});
```
## Multiple attachment points
## Sharing extensions across multiple locations
For some cases it can be useful to attach extensions to multiple parents. An example of this are Scaffolder field extensions or TechDocs addons that are consumed by multiple extensions. Specifying multiple attachments is done by providing an array of attachment points to the `attachTo` property of the extension. Keep in mind that this increases the complexity of your extension tree and should only be done when necessary. The following example shows how to attach our example extension to multiple parents:
If you need to make extensions available in multiple locations throughout your app, use a Utility API that collects the extensions and allows multiple parent extensions to consume them. This pattern provides better separation of concerns and makes data flow more explicit.
```tsx
const extension = createExtension({
name: 'my-extension',
attachTo: [
{ id: 'my-first-parent', input: 'content' },
{ id: 'my-second-parent', input: 'children' }, // The input names do not need to match
],
output: [coreExtensionData.reactElement],
factory() {
return [coreExtensionData.reactElement(<div>Hello World</div>)];
},
});
```
See the [Sharing Extensions Across Multiple Locations](./27-sharing-extensions.md) guide for a complete explanation of this pattern with detailed examples.
## Relative attachment points
@@ -361,7 +351,7 @@ When creating an extension or an [extension blueprint](./23-extension-blueprints
// Parent extension with a fixed attachment point
const parentExtension = createExtension({
kind: 'section',
attachTo: [{ id: 'app/some-fixed-extension', input: 'children' }],
attachTo: { id: 'app/some-fixed-extension', input: 'children' },
inputs: {
content: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
@@ -383,7 +373,7 @@ const parentExtension = createExtension({
// Child extension with a relative attachment point
const childExtension = createExtension({
kind: 'section-content',
attachTo: [{ relative: { kind: 'section' }, input: 'content' }],
attachTo: { relative: { kind: 'section' }, input: 'content' },
output: [coreExtensionData.reactElement],
factory() {
return [coreExtensionData.reactElement(<p>Section Content</p>)];
@@ -0,0 +1,173 @@
---
id: sharing-extensions
title: Sharing Extensions Across Multiple Locations
sidebar_label: Sharing Extensions
description: Using Utility APIs to share extensions across multiple locations in your app
---
Some plugins may need to provide extensibility that can be reused in multiple locations throughout the app. For example, in the pattern demonstrated on this page, a plugin can be made extensible by allowing widgets to be contributed that are then rendered on multiple pages. To achieve this, the recommended pattern is to use a Utility API that collects the extensions and makes them available throughout the plugin or the app.
## Overview
This pattern combines a Utility API with an extension blueprint to:
1. Define the extension data types and API interface
2. Provide a blueprint for creating extensions
3. Create a Utility API extension that collects extensions as input
4. Consume the extensions via the API
This approach provides a native integration with the frontend system, allowing to further rely on features like making the extensions configurable or have further extension points.
## Basic Pattern
The following example demonstrates this pattern using widgets that can be displayed on multiple pages. However, this pattern is flexible and can be adapted for many different scenarios where you need to:
- Share the same type of extension across different pages or views
- Allow third-party plugins to contribute extensions in a decoupled way
- Aggregate similar functionality from multiple sources in a consistent way
The core concepts remain the same regardless of what type of functionality you're sharing.
### 1. Define the Extension Data Types and API Interface
First, in your plugin's `-react` package (e.g., `backstage-plugin-foo-react`), define the widget types and API interface:
```tsx title="in backstage-plugin-foo-react"
import { createApiRef } from '@backstage/frontend-plugin-api';
import { ComponentType } from 'react';
export interface FooWidgetProps {
title: string;
}
// Define what data each widget provides, prefer using lazy loading for large pieces of functionality like components
export interface FooWidget {
title: string;
size: 'small' | 'medium' | 'large';
loader: () => Promise<ComponentType<FooWidgetProps>>;
}
// Define the API interface
export interface FooWidgetsApi {
getWidgets(): FooWidget[];
}
// Create the API reference
export const fooWidgetsApiRef = createApiRef<FooWidgetsApi>({
id: 'plugin.foo.widgets',
});
```
### 2. Provide a Blueprint for Creating Extensions
Next, also in your `-react` package (e.g., `backstage-plugin-foo-react`), create a blueprint that creates extensions. The blueprint creates an internal data reference and exposes it via the `dataRefs` property. This blueprint will be exported for other plugins to use:
```tsx title="in backstage-plugin-foo-react"
import {
createExtensionBlueprint,
createExtensionDataRef,
ExtensionBoundary,
} from '@backstage/frontend-plugin-api';
const fooWidgetDataRef = createExtensionDataRef<FooWidget>().with({
id: 'foo.widget',
});
export const FooWidgetBlueprint = createExtensionBlueprint({
kind: 'foo-widget',
// Attach extensions created with this blueprint to the API extension that will be created in the next step
attachTo: { id: 'api:foo/widgets', input: 'widgets' },
output: [fooWidgetDataRef],
*factory(params: FooWidget, { node }) {
yield fooWidgetDataRef({
title: params.title,
size: params.size,
loader: ExtensionBoundary.lazyComponent(node, params.loader),
});
},
dataRefs: {
widget: fooWidgetDataRef,
},
});
```
### 3. Create a Utility API Extension that Collects Extensions
In your main plugin package (e.g., `backstage-plugin-foo`), create a Utility API extension that collects widgets as input. Note that this imports the blueprint's data reference via `FooWidgetBlueprint.dataRefs.widget`:
```tsx title="in backstage-plugin-foo"
import {
ApiBlueprint,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import {
FooWidgetBlueprint,
fooWidgetsApiRef,
} from 'backstage-plugin-foo-react';
export const FooWidgetsApiExtension = ApiBlueprint.makeWithOverrides({
name: 'widgets',
inputs: {
widgets: createExtensionInput([FooWidgetBlueprint.dataRefs.widget]),
},
factory(originalFactory, { inputs }) {
// Collect all widgets from the inputs and forward them to the API implementation
const widgets = inputs.widgets.map(w =>
w.get(FooWidgetBlueprint.dataRefs.widget),
);
return originalFactory(defineParams =>
defineParams({
api: fooWidgetsApiRef,
deps: {},
factory: () => ({
getWidgets: () => widgets,
}),
}),
);
},
});
```
Other plugins can now import the blueprint from your `-react` package and create widget extensions that will be collected by the API:
```tsx title="in a consuming plugin"
import { FooWidgetBlueprint } from 'backstage-plugin-foo-react';
const barWidgetExtension = FooWidgetBlueprint.make({
name: 'bar',
params: {
title: 'Bar Widget',
size: 'small',
loader: () => import('./components/BarWidget').then(m => m.BarWidget),
},
});
const bazWidgetExtension = FooWidgetBlueprint.make({
name: 'baz',
params: {
title: 'Baz Widget',
size: 'medium',
loader: () => import('./components/BazWidget').then(m => m.BazWidget),
},
});
```
### 4. Consume the Extensions via the API
You can now consume the widgets using any of the available methods for consuming Utility APIs. For example, this is how you would access the widgets in a component:
```tsx title="in backstage-plugin-foo"
import { useApi } from '@backstage/frontend-plugin-api';
import { fooWidgetsApiRef } from 'backstage-plugin-foo-react';
import { Suspense, lazy } from 'react';
export function FooPageContent() {
const widgetsApi = useApi(fooWidgetsApiRef);
const widgets = widgetsApi.getWidgets();
return; // load and render widgets ...
}
```
For more information on consuming Utility APIs, see the [Consuming Utility APIs](../utility-apis/03-consuming.md) page.
+82
View File
@@ -106,6 +106,88 @@ When filling these out, you have 2 choices,
If you opt for the second option of replacing the entire string, take care to not commit your `app-config.yaml` to source control. It may contain passwords that you don't want leaked.
## Passwordless PostgreSQL in the Cloud
If you want to host your PostgreSQL server in the cloud with passwordless authentication, you can use Azure Database for PostgreSQL with Microsoft Entra authentication or Google Cloud SQL for PostgreSQL with Cloud IAM.
### Azure with Entra authentication
Remove `password` from the connection configuration and set `type` to `azure`.
Optionally set `tokenCredential` with the following properties. If no credential information is provided, it will default to using Default Azure Credential and a tokenRenewalOffsetTime of 5 minutes.
#### Credential Selection
The credential type is automatically inferred based on the fields you provide:
- Client Secret Credential is used when all three are provided:
- `tenantId`
- `clientId`
- `clientSecret`
- Managed Identity Credential is used when only `clientId` is provided. This enables user-assigned managed identity.
- Default Azure Credential is used when no credential fields are provided. Default Azure Credential supports [many credential types](https://learn.microsoft.com/azure/developer/javascript/sdk/authentication/credential-chains#use-defaultazurecredential-for-flexibility), choosing one based on the runtime environment.
#### Token Renewal
Set `tokenRenewalOffsetTime` to control how early OAuth tokens should be refreshed.
The value may be:
- A human-readable string such as '1d', '2 hours', '30 seconds'
- A duration object, e.g. { minutes: 3, seconds: 30 }
Azure PostgreSQL uses short-lived Entra ID access tokens.
By default, the database connector refreshes tokens 5 minutes before they expire.
#### User Configuration
Set `user` to the display name of your Entra ID group, service principal, or managed identity. Set it to the user principal name if you're authenticating with a user's credentials.
#### Example
```yaml title="app-config.yaml"
backend:
database:
client: pg
connection:
# highlight-add-start
type: azure
tokenCredential:
tokenRenewalOffsetTime: 5 minutes
# highlight-add-end
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
# highlight-remove-start
password: ${POSTGRES_PASSWORD}
# highlight-remove-end
```
### Google with Cloud IAM
Remove `password` from the connection configuration and set `type` to `cloudsql`.
Under the hood, this implements [Automatic IAM Database Authentication](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector?tab=readme-ov-file#automatic-iam-database-authentication).
For an IAM user account, set `user` to the user's email address. For a service account, set `user` to the service account's email without the .gserviceaccount.com domain suffix.
```yaml title="app-config.yaml"
backend:
database:
client: pg
connection:
# highlight-add-start
type: cloudsql
instance: my-project:region:my-instance
# highlight-add-end
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
# highlight-remove-start
password: ${POSTGRES_PASSWORD}
# highlight-remove-end
```
:::
[Start the Backstage app](../index.md#2-run-the-backstage-app):
+1 -1
View File
@@ -81,7 +81,7 @@ This guide also assumes a basic understanding of working on a Linux based operat
- Using `nvm` (recommended)
- [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script)
- [Install and change Node version with nvm](https://nodejs.org/en/download/package-manager/#nvm)
- Node 24 is a good starting point, this can be installed using `nvm install lts/krypton`
- Node 22 or 24 are recommended, these can be installed using `nvm install 22` or `nvm install 24`
- [Binary Download](https://nodejs.org/en/download/)
- [Package manager](https://nodejs.org/en/download/package-manager/)
- [Using NodeSource packages](https://github.com/nodesource/distributions/blob/master/README.md)
+1 -1
View File
@@ -285,7 +285,7 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import {
GitlabOrgDiscoveryEntityProvider,
-3
View File
@@ -147,9 +147,6 @@ add the `UserNotificationSettingsCard` to your frontend.
```tsx title="packages/app/src/App.tsx"
<Route path="/settings" element={<UserSettingsPage />}>
<SettingsLayout.Route path="/advanced" title="Advanced">
<AdvancedSettings />
</SettingsLayout.Route>
<SettingsLayout.Route path="/notifications" title="Notifications">
<UserNotificationSettingsCard
originNames={{ 'plugin:scaffolder': 'Scaffolder' }}
+1 -1
View File
@@ -10,7 +10,7 @@ Backstage is an open source framework for building developer portals that was cr
Backstage is powered by a centralized [software catalog](#software-catalog-system-model) and utilizes an abstraction layer that sits on top of all of your infrastructure and developer tooling, allowing you to manage all of your software, services, tooling, and testing in one place.
Backstage uses a [plugin-architecture](#plugin-architecture-overview) which allows you customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software.
Backstage uses a [plugin-architecture](#plugin-architecture-overview) which allows you to customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software.
Backstage is now a [CNCF incubation project](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation).
+1 -1
View File
@@ -19,7 +19,7 @@ An **operator** is a user responsible for configuring and maintaining an instanc
A **builder** is an internal or external code contributor and end up having a similar level of access as operators. When installing Backstage plugins you should vet them just like any other package from an external source. While its possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins.
An **external user** is a user that does not belong to the other two groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case.
An **external user** is a user that does not belong to the other three groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case.
## Operator Responsibilities
+1 -1
View File
@@ -158,7 +158,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer
```typescript title="packages/backend/src/modules/catalogPermissionRules.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogPermissionExtensionPoint } from '@backstage/plugin-catalog-node';
import { isInSystemRule } from './permissionsPolicyExtension';
export default createBackendModule({
+14 -6
View File
@@ -6,10 +6,7 @@ description: Documentation on Adding Plugin to Plugin Directory
## Adding a Plugin to the Directory
To add a new plugin to the [plugin directory](https://backstage.io/plugins)
create a file in
[`microsite/data/plugins`](https://github.com/backstage/backstage/tree/master/microsite/data/plugins)
with your plugin's information. Example:
To add a new plugin to the [plugin directory](https://backstage.io/plugins) create a file with the following pattern `<plugin-name>.yaml` where `<plugin-name>` is the name of your plugin. This file will go in [`microsite/data/plugins`](https://github.com/backstage/backstage/tree/master/microsite/data/plugins) with your plugin's information. Example:
```yaml
---
@@ -26,13 +23,24 @@ npmPackageName: # Your npm package name E.g. '@backstage/plugin-<etc>' quotes ar
addedDate: # The date plugin added to directory E.g. '2022-10-01' quotes are required
```
:::tip
You can validate your YAML file is correct by running the following from the root of the repo:
1. First run `yarn install`
2. Then run `node ./scripts/verify-plugin-directory.js`
If there are any errors they will be listed and you will need to correct them. We run this same check as part of the CI.
:::
## Submission Tips
Here are a few tips to help speed up the review process when you submit your plugin:
- For any icon that you use make sure you have the proper rights to use it.
- For any icon that you use make sure you have the proper rights to use it. If you don't have an icon then it will default to `iconUrl: '/img/logo-gradient-on-dark.svg'`.
- Make sure that your package had been published on the NPM registry and that it's public.
- Make sure your package on NPM has a link back to your code repo, this helps provide confidence that it's the right package.
- Where possible, please use an [NPM scope](https://docs.npmjs.com/about-scopes) that matches either your Organization name or user name, this provides trust in the plugin
- Where possible, please use an [NPM scope](https://docs.npmjs.com/about-scopes) that matches either your Organization name or user name, this provides trust in the plugin.
- If your plugin has both a frontend and backend link the documentation to the frontend package but make sure it mentioned needing to install the backend package.
- Where possible include a screenshot of the features in you plugin documentation, it really does help when deciding to use a plugin.
+3
View File
@@ -141,6 +141,9 @@ registered in this manner.
Example:
```ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node';
backend.add(
createBackendModule({
pluginId: 'proxy',
+7
View File
@@ -0,0 +1,7 @@
---
id: 'index'
title: 'Package Index'
description: 'Index of all Backstage Packages'
---
Please use [the new API documentation](https://backstage.io/api/stable) for more information.
+2 -2
View File
@@ -94,13 +94,13 @@ Contributed by [@imod](https://github.com/imod) in [#31410](https://github.com/b
Widget configuration changes are now only saved when the Save button is explicitly clicked. A new Cancel button has been added to enable users to discard unsaved changes
Contributed by [@kmikko](https://github.com/kmikko) in [#31198](https://github.com/pull/31198)
Contributed by [@kmikko](https://github.com/kmikko) in [#31198](https://github.com/backstage/backstage/pull/31198)
### Improved performance for `GithubOrgEntityProvider`
`GithubOrgEntityProvider` membership event handling and edit team has been improved. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions.
Contributed by [@angeliski](https://github.com/angeliski) in [#32184](https://github.com/pull/32184)
Contributed by [@angeliski](https://github.com/angeliski) in [#32184](https://github.com/backstage/backstage/pull/32184)
### `Link` component now uses React Routers navigation system
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -286,7 +286,7 @@ it is possible to pre-select what you want to create using the `--select` flag,
and provide options using `--option`, for example:
```bash
backstage-cli new --select plugin --option pluginId=foo
backstage-cli new --select frontend-plugin --option pluginId=foo
```
This command is typically added as script in the root `package.json` to be
+2 -2
View File
@@ -48,11 +48,11 @@ The main entry point of the package, as defined by [NPM](https://docs.npmjs.com/
The exports of the package, as defined by [Node.js](https://nodejs.org/api/packages.html#exports). This field is used to define the entry points of the package. As with other entry point fields, the exports should point to entry points for local development. They will the be rewritten when packaging the package for distribution. You can read more about this in the [sub-path exports](./cli/02-build-system.md#subpath-exports) section.
### `typeVersions`
### `typesVersions`
This field is used to specify versioned type entry points for the package, as defined by [TypeScript](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions), and is used as the equivalent of the `exports` field. TypeScript does support type declarations in the `exports` field, but that requires that the `moduleResolution` option in `tsconfig.json` is set to `node16` or `bundler`, which the Backstage ecosystem currently does not support.
This field can be generated by the `backstage-cli repo fix` command. First fill out the `exports` field to point to source fields, which will then be used to generate `typeVersions`.
This field can be generated by the `backstage-cli repo fix` command. First fill out the `exports` field to point to source fields, which will then be used to generate `typesVersions`.
### `sideEffects`