ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Backstage Actions Registry 服务深度指南:分布式 Action 注册、权限与 Secrets 管理

Backstage Actions Registry 服务深度指南:分布式 Action 注册、权限与 Secrets 管理 Backstage Actions Registry 服务深度指南分布式 Action 注册、权限与 Secrets 管理【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstageActions Registry 是 Backstage 新后端系统中面向插件开发者的核心服务当前标记为 alpha它为可执行 Action 提供了统一的注册、schema 校验、权限过滤与 HTTP 调用入口。本文以 docs/backend-system/core-services/actions-registry.md 为主体结合 packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts 与 packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts 等源码系统讲解 Action 的结构定义、注册方式、依赖注入接入、权限控制、Secrets 机制与错误处理读完即可在自己的插件中落地一套规范、可复用且安全可控的 Action 注册与调用方案。一、Actions Registry 是什么Actions Registry Service 是 Backstage 新后端系统New Backend System中的一个核心服务core service定位是为可以在 Backstage 后端插件内部执行的 Action提供分布式注册表distributed registry。它允许插件以统一的格式注册带有明确定义 schema 与执行逻辑的 Action从而在整个 Backstage 生态中提升一致性consistency与可复用性reusability。它与另一项核心服务Actions Service见 packages/backend-plugin-api/src/alpha/ActionsService.ts是分工互补的关系服务Service Ref职责Actions Registry ServiceactionsRegistryServiceRefid 为alpha.core.actionsRegistry插件的注册端声明 Action 的 name/schema/属性与执行函数并挂在插件自己的 HTTP 路由上Actions ServiceactionsServiceRefid 为alpha.core.actions消费端的调用与发现提供list()与invoke()两个方法供调用方如 MCP Actions Backend枚举和调用已注册的 Action两个 Service Ref 都定义在 packages/backend-plugin-api/src/alpha/refs.ts 中并统一从backstage/backend-plugin-api/alpha导出alpha/index.ts。由于两者目前都是 alpha 能力API 在未来版本中仍可能演进。从源码结构可以推断Actions Registry 的默认实现DefaultActionsRegistryService不仅负责注册还会为每个插件自动挂载一组/.backstage/actions/v1/...的 HTTP 端点因此注册进来的 Action 天然可以通过 HTTP 暴露给外部消费者这正是它被称为分布式注册表的原因——Action 分散在各插件中注册却能被统一发现和调用。二、Action 的结构定义每个注册到服务中的 Action 都必须符合ActionsRegistryActionOptions类型定义见 ActionsRegistryService.ts。该类型是泛型的会依据你声明的 input/output/secrets schema 自动推导出执行函数中的类型。2.1 必填属性nameAction 的唯一标识string。注册时实际生成的内部 id 为pluginId:name见下文内部 id 与命名空间。title人类可读的 Action 标题string。description对 Action 做什么的详细描述string。schema包含 schema 定义的对象其中input一个接收 Zod 实例、返回 Zod object schema 的函数用于校验输入output同样返回 Zod object schema 的函数用于校验输出secrets可选返回 Zod object schema 的函数用于校验 secrets详见 Secrets 一节。action执行 Action 逻辑的异步函数。一个值得注意的细节ActionsRegistryActionOptions还支持可选的examples字段ActionsRegistryService.ts可以给 Action 提供示例输入/输出便于调用方尤其是 LLM 类消费者理解用法。2.2 可选属性visibilityPermission一个BasicPermission通过权限框架控制 Action 的可见性与访问权详见 Permissions。attributes包含行为标志behavioral flags的对象destructive布尔值指示该 Action 是否会修改或删除数据idempotent布尔值指示多次执行该 Action 是否产生相同结果readOnly布尔值指示该 Action 是否只读、不修改任何数据。2.3 Action Context执行上下文当 Action 被执行时会收到一个上下文对象ActionsRegistryActionContext其中包含input已经通过 input schema 校验的输入数据secrets已经通过 secrets schema 校验的 secrets 数据如果未声明 secrets schema则为undefinedlogger一个LoggerService实例用于在 Action 内记录日志credentialsBackstageCredentials用于认证与授权。该上下文类型的定义ActionsRegistryService.ts同样利用泛型做了类型推导secrets的类型取决于TSecretsSchema泛型参数声明了 secrets schema 的 Action 会获得完全类型化的 secrets 对象。2.4 属性默认值解析destructive、readOnly、idempotent三个属性都有默认值最终暴露给消费方之前会做一次归一化。从 DefaultActionsRegistryService.ts 的toActionAttributes方法可以看到解析规则destructive默认为false当readOnly为true时否则默认为trueidempotent默认为falsereadOnly默认为false。也就是说一个既不声明readOnly也不声明destructive的 Action在消费方看到的默认行为是非只读、可破坏、非幂等——这在语义上是一个偏保守但安全的默认值需要作者根据真实行为显式声明。2.5 内部 id 与命名空间register()方法在内部会把 Action 的name与插件 id 拼接成全局唯一 idpluginId:name见 DefaultActionsRegistryService.ts。如果同一 id 被重复注册会直接抛出Action with id id is already registered错误。这意味着两个插件可以各自注册名为create-issue的 Action 而互不冲突调用方HTTP 端点、Actions Service在引用 Action 时必须使用完整的pluginId:nameid。三、注册一个 Action3.1 完整示例下面这段代码演示如何在插件中注册两个 Action一个只读幂等读取用户信息、一个破坏性删除实体。它完整覆盖了必填属性、可选属性与执行函数的典型写法import { ActionsRegistryService } from backstage/backend-plugin-api/alpha; 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, }; }, }); }3.2 执行函数返回值说明从 ActionsRegistryService.ts 的类型定义看action函数的返回值有两种形态若 output schema 推断出的类型是void可以返回void否则必须返回{ output: 与 output schema 匹配的数据 }包装形态。上述示例中delete-entity返回{ output: deletedEntities }是合法的——deletedEntities是一个数组与 output schemaz.array(z.string())一致包装对象{ output: ... }中的output键对应的是整个数组值。3.3 schema 使用要点从 actionsRegistryServiceFactory.test.ts 的测试用例可以提炼出以下约束input 与 output 必须是 object schema测试should forces registration of input and output schema as objects表明z.string()、z.undefined()等非 object schema 无法通过类型检查ts-expect-error注册后对应 JSON Schema 会被解析为空对象{}类型是端到端推导的测试should properly infer the input types验证了input: { test: z.string() }之后执行函数参数input.test被推导为string赋给boolean变量会触发ts-expect-erroroutput、secrets 以及examples中的示例输入输出同样有类型约束未声明 secrets 的 Action 也可以正常注册其执行函数中secrets为undefined。四、在插件中接入服务依赖注入在 Backstage 新后端系统中所有核心服务都通过依赖注入dependency injection获取。下面是在插件中注入actionsRegistryServiceRef并完成注册的标准写法import { createBackendPlugin, coreServices, } from backstage/backend-plugin-api; import { actionsRegistryServiceRef } from backstage/backend-plugin-api/alpha; export const myPlugin createBackendPlugin({ pluginId: my-plugin, register(env) { env.registerInit({ deps: { actionsRegistry: actionsRegistryServiceRef, logger: coreServices.logger, }, async init({ actionsRegistry, logger }) { logger.info(Registering actions...); registerMyActions(actionsRegistry); logger.info(Actions registered successfully); }, }); }, });服务工厂actionsRegistryServiceFactory的定义actionsRegistryServiceFactory.ts揭示了这个服务的底层依赖关系——它由以下核心服务组合而成依赖用途coreServices.pluginMetadata读取当前插件 id用于生成 Action 的pluginId:name命名空间 idcoreServices.httpRouter将/.backstage/actions/v1/...路由挂载到插件自身路由上coreServices.httpAuth从 HTTP 请求中解析调用者 credentialscoreServices.logger日志记录coreServices.auth认证与 principal 判断coreServices.rootConfig读取backend.actions.pluginSources、backend.actions.filter等配置coreServices.permissions权限决策visibilityPermission 评估coreServices.permissionsRegistry自动注册 Action 声明的权限4.1 HTTP 端点默认实现通过createRouter()DefaultActionsRegistryService.ts为每个插件挂载以下端点完整路径前缀为/api/pluginId方法路径说明GET/.backstage/actions/v1/actions列出当前插件注册的 Action含 name/title/description/pluginId/attributes/examples 以及由 Zod schema 转换而来的 JSON SchemaPOST/.backstage/actions/v1/actions/:actionId/invoke调用 Action请求体为裸输入格式{ ...input 字段 }v1 已标记为 deprecated仅供向后兼容POST/.backstage/actions/v2/actions/:actionId/invoke调用 Action请求体为包裹格式{ input: {...}, secrets: {...} }推荐使用关于 v1 与 v2 的差异测试should still accept raw body format for backward compatibility与should pass secrets to the action handler when using wrapped body formatactionsRegistryServiceFactory.test.ts给出了明确验证v1 把整个请求体当作input不支持 secretsv2 则显式区分input与secrets两个字段。凡是需要 secrets 的 Action必须使用 v2 端点。4.2 列表端点返回的元数据GET /.backstage/actions/v1/actions返回的每个 Action 对象包含id即pluginId:name、name、title、description、pluginId、attributes已按默认值解析后的三个布尔值、examples以及schema.input/schema.output/schema.secrets均为zod-to-json-schema转换出的 JSON Schema。secrets schema只有在声明时才会出现在返回结果中见测试should return secrets schema in action list when declared与should not include secrets schema when not declared。五、权限控制5.1 visibilityPermission 的作用Action 可以可选地声明visibilityPermission必须是BasicPermission不能是 resource permission用于通过 Backstage 权限框架控制 Action 的可见性与访问权。当设置后该 Action 只对获得授权的调用者可见、可调用。当通过 Actions Service 或/.backstage/actions/v1/...HTTP 端点访问时被权限策略拒绝的 Action 会从列表结果中被过滤掉并且在调用时返回404 Not Found表现得就像它们根本不存在一样。这一伪装不存在的设计避免了向未授权调用者泄露 Action 的存在性。此外声明在 Action 上的权限会被自动注册到PermissionsRegistryService见 DefaultActionsRegistryService.ts 的register方法因此它们会自动出现在权限策略系统中便于运维人员统一管理。5.2 添加权限的示例import { createPermission } from backstage/plugin-permission-common; // Define a permission for your action const myDeletePermission createPermission({ name: my-plugin.actions.deleteEntity, attributes: { action: delete }, }); actionsRegistry.register({ name: delete-entity, title: Delete Entity, description: Removes an entity from the catalog, visibilityPermission: myDeletePermission, schema: { input: z z.object({ entityRef: z.string() }), output: z z.object({ deleted: z.boolean() }), }, action: async ({ input }) { // action logic return { output: { deleted: true } }; }, });5.3 未声明权限的行为未声明visibilityPermission的 Action 保持对所有调用者可见、可访问以维持向后兼容性。5.4 权限评估的源码实现从 DefaultActionsRegistryService.ts 的filterByPermissions方法可以看到实际评估流程列表请求会先收集所有声明了visibilityPermission的 Action批量调用permissions.authorize()获取决策然后剔除所有未获得ALLOW结果的条目调用请求则在执行前单独对目标 Action 的 permission 做一次authorize非ALLOW一律抛NotFoundError。六、Secrets 机制6.1 为什么需要 Secrets有些 Action 需要调用外部系统而这些外部系统要求使用不属于 Backstage 自身认证体系的凭据——比如 API Token、Personal Access Token 等敏感值。为此Action 可以声明一个secretsschema 来向最终用户请求这些外部凭据。关键设计secrets 与 input schema 严格分离因此当 Action 被暴露为 MCP 工具tool时secrets永远不会出现在工具定义或 LLM 上下文中避免敏感信息被泄露给模型。这是 Actions Registry 在 AI 集成场景下的核心安全设计。6.2 声明 Secrets Schema在schema对象中与input、output并列增加一个secrets函数即可。它的工作方式与 input schema 完全一致接收 Zod 实例返回 Zod object schemaactionsRegistry.register({ name: create-issue, title: Create GitHub Issue, description: Creates an issue in a GitHub repository, schema: { input: z z.object({ repo: z.string(), title: z.string(), body: z.string().optional(), }), output: z z.object({ issueUrl: z.string(), }), secrets: z z.object({ githubToken: z .string() .describe(GitHub Personal Access Token with repo scope), }), }, attributes: { destructive: false, }, action: async ({ input, secrets, credentials }) { const octokit new Octokit({ auth: secrets.githubToken }); const { data } await octokit.issues.create({ owner: input.repo.split(/)[0], repo: input.repo.split(/)[1], title: input.title, body: input.body, }); return { output: { issueUrl: data.html_url } }; }, });secrets在 Action context 中是完全基于所声明 schema 进行类型推导的。未声明 secrets schema 的 Action其 context 中的secrets字段类型为undefined这由 ActionsRegistryService.ts 上下文类型定义中的条件类型保证测试should properly infer the secrets types也对此做了类型级验证。6.3 Secrets 在系统中的流转Secrets 与 input 一样会先经过 Zod schema 校验。由 DefaultActionsRegistryService.ts 与 MockActionsRegistry.ts 中的校验逻辑以及 actionsRegistryServiceFactory.test.ts 中的对应测试可以归纳出以下行为规则场景行为声明了 secrets schema 但调用时未提供 secrets返回InputErrorHTTP 400提示requires secrets but none were provided声明了 secrets schema 但 secrets 校验失败返回InputErrorHTTP 400提示Invalid secrets for action未声明 secrets schema 却收到 secrets请求被拒绝返回InputErrorHTTP 400提示does not accept secrets校验通过secrets 原样传入 action context 的secrets字段另外secrets schema 会包含在列表端点返回的 Action 元数据中因此调用方可以在真正调用之前发现某个 Action 需要哪些 secrets。6.4 测试中的验证actionsRegistryServiceFactory.test.ts的 invoke 测试组专门验证了 secrets 相关行为should pass secrets to the action handler when using wrapped body format通过 v2 端点发送{ input: {...}, secrets: { token: my-secret } }并断言 action handler 收到完整的input与secretsshould return 400 when action requires secrets but none provided、should validate secrets against the schema、should return 400 when secrets are sent to an action that does not accept them则分别覆盖了上面表格中的三种失败路径。这些测试可以直接作为自己插件编写同类校验逻辑时的参照。七、错误处理7.1 使用 backstage/errors 的错误类当 Action 遇到问题时必须使用 packages/errors 包中的错误类。这些错误类能够被 Actions Service 以及像 MCP Actions Backend 这样的消费方正确识别并把错误消息原样透传给调用者。如果抛出的是无法识别的错误类型调用方最终可能只会收到一个笼统的500 Server Error。import { NotFoundError, NotAllowedError } from backstage/errors; actionsRegistry.register({ name: update-resource, title: Update Resource, description: Updates a resource by ID, schema: { input: z z.object({ id: z.string() }), output: z z.object({ updated: z.boolean() }), }, attributes: { destructive: false, readOnly: false, idempotent: true }, action: async ({ input, credentials }) { const resource await getResource(input.id); if (!resource) { throw new NotFoundError(Resource ${input.id} not found); } if (!hasPermission(credentials, resource)) { throw new NotAllowedError( Insufficient permissions for resource ${input.id}, ); } await updateResource(resource); return { output: { updated: true } }; }, });7.2 错误透传的验证actionsRegistryServiceFactory.test.ts中有两个测试直接验证了错误透传语义should forward the original error when the action throws a known erroraction 抛InputError(test)时HTTP 返回 400响应体包含{ error: { name: InputError, message: test } }should forward a NotFoundError from the action with 404 statusaction 抛NotFoundError时HTTP 返回 404错误名与消息同样原样透传。这说明选择正确的错误类型不仅影响内部日志质量更直接决定下游调用者拿到的 HTTP 状态码与错误信息。八、Action Attributes 参考表以下表格总结了三个行为属性的类型、默认值与含义AttributeTypeDefaultDescriptiondestructivebooleanfalseif read-only, otherwisetrue指示该 Action 是否修改或删除数据使用需谨慎idempotentbooleanfalse指示该 Action 是否可多次运行且结果一致readOnlybooleanfalse指示该 Action 是否只读、不产生任何修改这些属性帮助消费方理解 Action 的行为特征并据此实现合理的保护机制safeguards、重试retries或优化optimizations。例如readOnly: true的 Action 可以被放心地反复调用idempotent: true的 Action 可以在网络超时后安全重试而destructive: true的 Action 在暴露给外部调用者时可能需要额外的确认或权限门槛。8.1 属性在过滤与发现中的作用属性不仅在元数据中展示还会参与 Action 的过滤逻辑。源码中存在两个层次的过滤见 DefaultActionsRegistryService.ts 与 actionFilters.tsbackend.actions.pluginSources配置了该数组时只有列出的插件 id 注册的 Action 才可见backend.actions.filter支持include/exclude规则每条规则可按 action id 的 glob 模式id或按attributesdestructive/readOnly/idempotent任一布尔值匹配exclude 优先级高于 include未配置 include 时默认放行所有未被排除的 Action。例如要只保留my-plugin中幂等且只读的 Action可以这样配置app-config.yamlbackend: actions: pluginSources: - my-plugin filter: include: - id: my-plugin:* attributes: idempotent: true readOnly: true exclude: - id: my-plugin:delete-*需要注意的是backend.actions.filter的规则也应用于 Actions Service 的列表过滤见 DefaultActionsService.ts 中的调用以及 actionsServiceFactory.test.ts 中should filter actions based on attribute constraints等测试用例。九、命名约定Best Practices为了让生态中的 Action 更易发现、更一致文档建议遵守以下命名约定使用 kebab-caseAction 名称使用中划线小写风格例如fetch-user-info、create-repository描述性命名名称要能清楚表达 Action 做什么避免冗余不要在名称中带上插件名——插件上下文是独立的Action id 本身已经由pluginId:name组成了命名空间动词开头用描述操作的动词作为名称开头例如fetch、create、delete、update。十、测试与 Mock 支持对于想测试自己 Action 的插件作者packages/backend-test-utils 提供了现成的 Mock 实现mockServices.actionsRegistry()见 MockActionsRegistry.ts。该 Mock 同时实现了ActionsRegistryService与ActionsService行为与默认实现保持一致注册时以test:作为插件 id 前缀test:nameinvoke()会执行完整的 input/secrets/output 校验未找到 Action 时抛出NotFoundError并附上当前已注册的全部 id。一个最小测试示例const actionsRegistry mockServices.actionsRegistry(); actionsRegistry.register({ name: test, title: Test, description: Test, schema: { input: z.object({ name: z.string() }), output: z.object({ name: z.string() }), }, action: async ({ input }) ({ output: { name: input.name } }), }); const result await actionsRegistry.invoke({ id: testing:test, input: { name: test }, }); expect(result).toEqual({ output: { name: test } });十一、与 MCP Actions Backend 的协作Actions Registry 的一个典型消费场景是 MCP Actions Backend。该插件同时依赖actionsServiceRef与actionsRegistryServiceRef见 plugins/mcp-actions-backend/src/plugin.ts前者用于枚举和调用已注册的 ActionMcpService.ts 中的this.actions.invoke(...)即为调用路径后者用于在开发环境dev 模式直接注册演示用 Actionplugins/mcp-actions-backend/dev/index.ts。从源码结构可以推断出这一协作模式的价值插件 A 通过 Actions Registry 注册 Action 并提供 schema 与描述MCP Actions Backend 通过 Actions Service 将这些 Action 暴露为 MCP 工具给 LLMLLM 依据 JSON Schema 生成参数并触发调用。这正是 Actions Registry 所强调的一致性consistency与可复用性reusability在 AI 集成场景中的落地形态。同时由于 secrets 与 input 分离即便 Action 需要外部凭据这些敏感值也永远不会进入 LLM 的工具上下文。十二、小结与最佳实践清单综合文档与源码落地一个高质量 Action 时建议遵循以下检查清单结构完整name/title/description/schema.input/schema.output/action六要素齐全名称遵循 kebab-case、动词开头、不带插件名的约定行为声明准确显式声明attributes尤其是readOnly与destructive确保消费方看到的默认值与真实行为一致敏感凭据走 secrets凡是需要外部 token 的 Action 一律声明secretsschema并使用 v2 invoke 端点绝不要把 secrets 塞进 input需要管控就加权限visibilityPermission让 Action 自动接入权限策略系统被拒后表现为不存在列表过滤 调用 404错误一律用 backstage/errorsNotFoundError、NotAllowedError、InputError等会被正确透传给调用方未识别错误会退化为 500善用配置过滤通过backend.actions.pluginSources与backend.actions.filterinclude/exclude attributes 匹配在部署层控制可见性测试用 MockmockServices.actionsRegistry()提供与默认实现一致的校验与调用语义可直接用于单测或startTestBackend集成测试。需要提醒的是Actions Registry 目前仍是alpha阶段的 APIalpha导出接口在未来版本中可能演进在升级 Backstage 时请留意 packages/backend-plugin-api/CHANGELOG.md 与 packages/backend-defaults/CHANGELOG.md 中的相关变更说明。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表