Backstage Actions Service 实战指南:Action 的发现、过滤、鉴权与远程调用原理

发布时间:2026/9/11 7:24:58
Backstage Actions Service 实战指南:Action 的发现、过滤、鉴权与远程调用原理 Backstage Actions Service 实战指南Action 的发现、过滤、鉴权与远程调用原理【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstageActions Service 是 Backstage 后端系统Backend System中的一项核心服务alpha 阶段它为后端插件提供了一套统一的接口用于**发现list与执行invoke**已注册的 Action。本文围绕docs/backend-system/core-services/actions.md展开结合仓库中 ActionsService 接口定义、DefaultActionsService 实现 与 actionsServiceFactory 测试讲清 Action 的 ID 规范、pluginSources与 include/exclude 过滤配置、权限集成、Secrets 传递机制以及它在多插件、分布式场景下的底层远程调用链路。读完你可以在自己的插件中安全、精准地列出并执行 Action并掌握如何通过配置对暴露的 Action 做细粒度治理。Actions Service 是什么Actions Service 是 Backstage 后端插件中用于发现与执行已注册 Action的核心服务。它承担的是消费方 API角色Action 由插件通过 Actions Registry Service 注册而本服务负责把这些 Action 暴露给其他插件与调用方并且自带身份认证credentials与输入校验能力。在 ActionsService 接口定义 中服务只提供两个方法list({ credentials })返回全部可用 Action 及其完整元数据ActionsServiceAction[]包括声明的输入/输出/Secrets JSON Schema 与行为属性attributesinvoke({ id, input, secrets, credentials })按 Action ID 执行指定的 Action返回{ output }。两者都需要传入BackstageCredentials用于认证与鉴权。接口定义中可以看到每个 Action 的元数据结构ActionsServiceActionexport type ActionsServiceAction { id: string; pluginId: string; name: string; title: string; description: string; schema: { input: JSONSchema7; output: JSONSchema7; secrets?: JSONSchema7; }; examples?: Array{ title: string; description?: string; input: JsonObject; output?: JsonObject; }; attributes: { readOnly: boolean; destructive: boolean; idempotent: boolean; }; };注意schema中的input/output/secrets在注册时使用 Zod schema 描述但在经服务对外暴露时统一转换为 JSON Schemadraft-07格式这一点在 actionsServiceFactory.test.ts 的集成测试断言中可以看到具体形态。Action 的 ID 规范Action 使用全局唯一的 ID 标识格式固定为pluginId:actionName所有 Action ID 都以注册它的插件 ID为前缀例如catalog插件注册的fetch-user-info其完整 ID 为catalog:fetch-user-info使用actionsRegistryServiceMock测试 Mock注册时插件前缀固定为test:这种命名约定保证了 Action 名称在全部插件之间全局唯一同时让每个 Action 的归属插件一目了然。从实现看ID 前缀还承担了路由定位职责DefaultActionsService.invoke()会通过pluginIdFromActionId()解析出:之前的插件 IDDefaultActionsService.ts然后只向该插件发起远程调用如果 ID 中没有:会直接抛出Invalid action id错误。配置 Actions ServiceActions Service 的默认实现DefaultActionsService通过createServiceFactory装配依赖discovery、rootConfig、logger、auth四个核心服务见 actionsServiceFactory.ts并读取backend.actions配置段。以下配置都在app-config.yaml的backend.actions下完成。按插件限制 Action 来源pluginSourcespluginSources配置用于限制哪些插件的 Action 会被纳入发现范围backend: actions: pluginSources: - catalog实现上list()会读取backend.actions.pluginSources字符串数组未配置时默认为空数组然后对每个来源插件发起 HTTP 请求获取其 Action 列表DefaultActionsService.ts。某插件请求失败时只会记录warn日志并返回空数组不会让整个list()失败——这一优雅降级行为在测试should list all plugins in config to find actions and handle failures gracefully中有明确覆盖actionsServiceFactory.test.ts。用 include / exclude 过滤 Action除了插件级限制Actions Service 还支持基于include包含与exclude排除规则的细粒度过滤精确控制 Backstage 实例中暴露或可运行的 Action。过滤维度有两个id使用 glob 模式如catalog:*、*:fetch-*attributes按行为属性过滤可取destructive破坏性、readOnly只读、idempotent幂等三个布尔值。规则求值逻辑来自文档原文并在 applyFilters 实现中得到印证单条规则内部id与attributes之间是AND关系matchesRule()中id 不匹配直接返回 falseattributes 有任一不一致也返回 false同一个include或exclude数组内的多条规则之间是OR关系exclude优先于include且始终生效先判 exclude命中即剔除无 include 规则时默认全量放行有 include 规则时至少命中一条才保留。包含指定 Actionbackend: actions: filter: include: # Include all catalog actions that are non-destructive - id: catalog:* attributes: destructive: false # OR include all fetch actions from any plugin - id: *:fetch-*排除指定 Actionbackend: actions: filter: exclude: # Exclude all delete actions from any plugin - id: *:delete-* # OR exclude all destructive actions - attributes: destructive: true上述语义在 actionsServiceFactory.test.ts 中有成体系的测试覆盖可作为理解源码级真相的参考should filter actions based on include patterns第 118-190 行include 只保留my-plugin:*should filter actions based on exclude patterns第 192-242 行exclude*:delete-*后只保留get-entityshould have exclude take precedence over include第 244-295 行即使命中 include被 exclude 命中也会被剔除should always apply exclude rules even when action matches include第 297-363 行注释明确写到 exclude is checked FIRST and always wins——destructive 的 action 即便匹配my-plugin:*也会被过滤should filter actions based on attribute constraints第 365-431 行按attributes.readOnly: true过滤should combine pattern and attribute filtering with AND logic第 433-525 行id与attributes同时满足才保留should return all actions when no filter config is provided第 527-578 行未配置 filter 时返回全部。实现细节上glob 匹配使用minimatch库规则解析见 parseFilterRulesid编译为Minimatch实例attributes只读取destructive、readOnly、idempotent三个键。与权限框架集成Permissions注册时带visibilityPermission字段的 Action 会自动接入权限框架列出时被权限策略permission policy拒绝的 Action 会从list()结果中过滤掉执行时对被拒绝的 Action 调用invoke()返回404 Not Found错误——与被删除的 Action 表现一致避免暴露存在性信息。关于如何在 Action 上配置权限例如用createPermission定义my-plugin.actions.deleteEntity并赋值给visibilityPermission参见 Actions Registry 的 Permissions 文档。使用 Actions Service 列出 Actionlist()返回的每个 Action 都带有id、title、description、attributes以及可选的schema.input/schema.output/schema.secrets。下面是一个完整的列出示例沿用文档原文可直接作为插件代码参考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; } }注意credentials参数服务内部会用auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId })换取调用目标插件的服务令牌再携带Authorization: Bearer token请求远程插件DefaultActionsService.ts。因此调用方必须提供代表当前用户或服务的凭证不能匿名调用。执行一个 Actioninvoke()以id定位 Action并传入input可选与secrets可选import { ActionsService } from backstage/backend-plugin-api; export async function executeAction( actionsService: ActionsService, actionId: string, input: JsonObject, credentials: BackstageCredentials, secrets?: JsonObject, ) { try { const { output } await actionsService.invoke({ id: actionId, input, secrets, 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; }从实现看invoke()会根据目标插件 ID 把请求 POST 到该插件的/.backstage/actions/v1/actions/编码后的完整ID/invoke端点DefaultActionsService.ts。测试should invoke the action and return the output与集成测试/api/test-harness/invoke分别验证了客户端 HTTP 调用与服务端全链路返回{ output: { ok: true, string: hello world } }的行为actionsServiceFactory.test.ts。错误语义invoke()对 HTTP 非 2xx 响应会统一转为ResponseError抛出测试覆盖了两类典型场景目标 Action 不存在或权限被拒 →404should throw a 404 if the action does not exist输入校验失败 →400should throw a 400 if the action returns an invalid input。携带 Secrets 执行 Action部分 Action 会为外部凭证如 GitHub Token、个人访问令牌等不属于 Backstage 自身认证体系的敏感值声明secretsschema。你可以通过list()返回元数据中的schema.secrets判断某 Action 是否需要 Secrets需要时在调用时一并传入const { actions } await actionsService.list({ credentials }); const action actions.find(a a.id my-plugin:create-issue); if (action?.schema.secrets) { // This action needs secrets — collect them from the user first const { output } await actionsService.invoke({ id: action.id, input: { repo: backstage/backstage, title: My issue }, secrets: { githubToken: collectedToken }, credentials, }); }两条硬性约束违反都会得到InputError向未声明secrets schema 的 Action 传入 secrets → 拒绝遗漏必需的 secrets → 拒绝。实现层面invoke()在传入secrets时会自动切换到v2调用协议请求体为{ input, secrets }未传 secrets 时保持v1协议请求体为input本身并在代码中标注了待所有 registry 升级后移除 v1 回退的弃用说明DefaultActionsService.ts服务端则由 Actions Registry 在/.backstage/actions/v1/actions/:actionId/invoke旧与/.backstage/actions/v2/actions/:actionId/invoke新支持 wrapped secrets两条路由上分别处理。关于如何在注册端声明 secrets schemaschema.secrets: z z.object({...})参见 Actions Registry 的 Secrets 文档。特别地secrets 与 input 分离的设计保证了它们永远不会出现在暴露为 MCP 工具的 tool definitions 或 LLM 上下文中。源码视角一次 list/invoke 的完整链路把文档描述与实现代码对照可以还原 Actions Service 的完整工作链路注册端插件在registerInit中注入actionsRegistryServiceRef服务 ref ID 为alpha.core.actionsRegistry见 refs.ts调用actionsRegistry.register({...})注册 Action并挂载/.backstage/actions/v1/actions与 invoke 路由见 DefaultActionsRegistryService.ts 中的路由注册重复注册同一 ID 会抛出Action with id ... is already registered消费端调用方注入actionsServiceRefref ID 为alpha.core.actions见 refs.ts调用list()/invoke()路由与认证DefaultActionsService通过discovery.getBaseUrl(pluginId)定位目标插件用auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId })换取服务令牌向{baseUrl}/.backstage/actions/v1/actionslist或/.backstage/actions/v1|v2/actions/{id}/invokeinvoke发起带 Bearer 令牌的请求过滤与返回list()汇总各插件返回的 Action 后经applyFilters()应用 include/exclude 规则exclude 优先、include 缺省放行最终返回过滤后的列表。这套设计使 Actions Service 成为一个分布式的、跨插件的远程调用门面Action 注册在各自插件进程内但可以通过统一的服务接口被任意插件发现和调用且天然带认证、鉴权与输入校验。最佳实践与错误处理指引Action 设计规范命名、schema 设计等最佳实践见 Actions Registry 的 Best Practices——Action 名应使用 kebab-case、以动词开头如fetch、create、delete、避免在名称中重复插件名错误类型Action 内部应抛出backstage/errors中的错误类如NotFoundError、NotAllowedError这些错误能被 Actions Service 及 MCP Actions Backend 等消费方识别并透传给调用者未被识别的错误类型可能退化为通用的500 Server Error。小结Actions Service 是 Backstage 后端系统中连接Action 注册方与Action 消费方的桥梁pluginSources划定插件边界include/exclude 过滤提供基于 IDglob与行为属性的细粒度治理visibilityPermission打通权限框架secrets 机制安全传递外部凭证而底层基于 Discovery Auth 的远程调用让 Action 可以跨插件分布式执行。配合 Actions Registry Service 一起使用即可在 Backstage 中构建一套可发现、可治理、可安全执行的可复用 Action 生态。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考