chore: added some basic docs for actions registry and actions service
Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user