chore: added some basic docs for actions registry and actions service

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-06-17 09:16:13 +02:00
parent 19774b2a55
commit 54725d199e
3 changed files with 309 additions and 0 deletions
@@ -0,0 +1,176 @@
---
id: actions-registry
title: Actions Registry (alpha)
sidebar_label: Actions Registry (alpha)
description: Documentation for the Actions Registry Service
---
## Overview
The Actions Registry Service is a core service designed to provide a distributed registry for actions that can be executed within Backstage backend plugins. This service allows plugins to register reusable actions with well-defined schemas and execution logic, promoting consistency and reusability across the Backstage ecosystem.
## Action Structure
Each action registered with the service must conform to the `ActionsRegistryActionOptions` type, which includes:
### Required Properties
- **`name`:** A unique identifier for the action (string)
- **`title`:** A human-readable title for the action (string)
- **`description`:** A detailed description of what the action does (string)
- **`schema`:** Object containing input and output schema definitions
- **`input`:** Function that returns a Zod schema for validating input
- **`output`:** Function that returns a Zod schema for validating output
- **`action`:** The async function that executes the action logic
### Optional Properties
- **`attributes`:** Object containing behavioral flags:
- **`destructive`:** Boolean indicating if the action modifies or deletes data
- **`idempotent`:** Boolean indicating if running the action multiple times produces the same result
- **`readOnly`:** Boolean indicating if the action only reads data without modifications
### Action Context
When an action is executed, it receives a context object (`ActionsRegistryActionContext`) containing:
- **`input`:** The validated input data matching the defined input schema
- **`logger`:** A LoggerService instance for logging within the action
- **`credentials`:** BackstageCredentials for authentication and authorization
## Using the Service
### Registering an Action
Here's an example of how to register an action with the Actions Registry Service:
```typescript
import { ActionsRegistryService } from '@backstage/backend-plugin-api';
export function registerMyActions(actionsRegistry: ActionsRegistryService) {
// Register a simple read-only action
actionsRegistry.register({
name: 'fetch-user-info',
title: 'Fetch User Information',
description: 'Retrieves user information from the catalog',
schema: {
input: z =>
z.object({
userRef: z.string(),
includeGroups: z.boolean().optional(),
}),
output: z =>
z.object({
user: z.object({
name: z.string(),
email: z.string(),
groups: z.array(z.string()).optional(),
}),
}),
},
attributes: {
readOnly: true,
idempotent: true,
},
action: async ({ input, logger, credentials }) => {
logger.info(`Fetching user info for ${input.userRef}`);
// Perform the action logic here
const user = await fetchUserFromCatalog(input.userRef, credentials);
return {
output: {
user: {
name: user.name,
email: user.email,
groups: input.includeGroups ? user.groups : undefined,
},
},
};
},
});
// Register a destructive action
actionsRegistry.register({
name: 'delete-entity',
title: 'Delete Entity',
description: 'Removes an entity from the catalog',
schema: {
input: z =>
z.object({
entityRef: z.string(),
force: z.boolean().optional(),
}),
output: z =>
z.object({
deletedEntities: z.array(z.string()),
}),
},
attributes: {
destructive: true,
idempotent: false,
},
action: async ({ input, logger, credentials }) => {
logger.warn(`Deleting entity ${input.entityRef}`);
// Perform the deletion logic here
const { deletedEntities } = await deleteEntityFromCatalog(
input.entityRef,
input.force,
credentials,
);
return {
output: deletedEntities,
};
},
});
}
```
### Accessing the Service in a Plugin
To use the Actions Registry Service in your plugin, access it through dependency injection:
```typescript
import {
createBackendPlugin,
coreServices,
} from '@backstage/backend-plugin-api';
export const myPlugin = createBackendPlugin({
pluginId: 'my-plugin',
register(env) {
env.registerInit({
deps: {
actionsRegistry: coreServices.actionsRegistry,
logger: coreServices.logger,
},
async init({ actionsRegistry, logger }) {
logger.info('Registering actions...');
registerMyActions(actionsRegistry);
logger.info('Actions registered successfully');
},
});
},
});
```
## Best Practices
### Naming Conventions
- **Use kebab-case:** Action names should be in kebab-case (e.g., `fetch-user-info`, `create-repository`)
- **Be Descriptive:** Choose names that clearly describe what the action does
- **Avoid Redundancy:** Don't include plugin names in action names since the plugin context is separate
- **Use Verbs:** Start action names with verbs that describe the operation (e.g., `fetch`, `create`, `delete`, `update`)
## Action Attributes Reference
| Attribute | Type | Default | Description |
| ------------- | ------- | ------- | ------------------------------------------------------------------- |
| `destructive` | boolean | `true` | Indicates the action modifies or deletes data. Use with caution. |
| `idempotent` | boolean | `false` | Indicates the action can be run multiple times with the same result |
| `readOnly` | boolean | `false` | Indicates the action only reads data without making modifications |
These attributes help consumers of actions understand their behavior and implement appropriate safeguards, retries, or optimizations based on the action's characteristics.
@@ -0,0 +1,131 @@
---
id: actions
title: Actions (alpha)
sidebar_label: Actions (alpha)
description: Documentation for the Actions Service
---
## Overview
The Actions Service is a core service that provides a standardized interface for discovering and executing registered actions within Backstage backend plugins. This service acts as the consumer-facing API for actions that have been registered through the Actions Registry Service, allowing plugins to list available actions and invoke them with proper authentication and input validation.
## How it Works
The Actions Service implements the `ActionsService` interface, which provides two primary methods:
- **`list()`:** Retrieves all available actions with their complete metadata
- **`invoke()`:** Executes a specific action by ID with provided input data
The service works in conjunction with the [Actions Registry Service](./actions-registry.md), where actions are registered by plugins and then made available for discovery and execution through this service.
## Action Identification
Actions are identified using a unique ID that follows a specific format:
- All action IDs are prefixed with the plugin ID that registered them, following the pattern `pluginId:actionName`
- An action named `fetch-user-info` registered by the `catalog` plugin would have the ID `catalog:fetch-user-info`
- When using the `actionsRegistryServiceMock`, the plugin ID prefix will be `test:`
This naming convention ensures that action names are globally unique across all plugins and provides clear ownership identification.
## Configuration
The Actions Service can be configured to control which plugins' actions are available:
```yaml
backend:
actions:
pluginSources:
- catalog
```
## Using the Service
### Listing Available Actions
Here's an example of how to list all available actions:
```typescript
import { ActionsService } from '@backstage/backend-plugin-api';
export async function listAvailableActions(
actionsService: ActionsService,
credentials: BackstageCredentials,
) {
try {
const { actions } = await actionsService.list({ credentials });
console.log(`Found ${actions.length} available actions:`);
actions.forEach(action => {
console.log(`- ${action.id}: ${action.title}`);
console.log(` Description: ${action.description}`);
console.log(` Attributes: ${JSON.stringify(action.attributes)}`);
if (action.schema.input) {
console.log(
` Input Schema: ${JSON.stringify(action.schema.input, null, 2)}`,
);
}
});
return actions;
} catch (error) {
console.error('Failed to list actions:', error);
throw error;
}
}
```
### Invoking an Action
Here's an example of how to execute a specific action:
```typescript
import { ActionsService } from '@backstage/backend-plugin-api';
export async function executeAction(
actionsService: ActionsService,
actionId: string,
input: JsonObject,
credentials: BackstageCredentials,
) {
try {
const { output } = await actionsService.invoke({
id: actionId,
input,
credentials,
});
console.log(`Action ${actionId} executed successfully`);
console.log('Output:', JSON.stringify(output, null, 2));
return output;
} catch (error) {
console.error(`Failed to execute action ${actionId}:`, error);
throw error;
}
}
// Example usage
async function fetchUserInfo(
actionsService: ActionsService,
credentials: BackstageCredentials,
) {
const output = await executeAction(
actionsService,
'catalog:fetch-user-info', // Note: Action ID includes plugin prefix
{
userRef: 'user:default/john.doe',
includeGroups: true,
},
credentials,
);
return output;
}
```
## Best Practices
For comprehensive guidance on action design, naming conventions, and schema design, see the [Actions Registry Best Practices](./actions-registry.md#best-practices) documentation.