Backstage Scaffolder 模板 Dry-run 测试指南:命令行工具用法与源码级原理解析
2026/9/11 11:18:15 网站建设 项目流程

Backstage Scaffolder 模板 Dry-run 测试指南:命令行工具用法与源码级原理解析

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

导读

Backstage 的 Scaffolder 软件模板几乎可以调用 backstage.io 内置的一切能力以及自定义 action,因此如果不实际运行一套 Backstage 实例,很难验证模板的渲染与执行逻辑是否正确。本指南围绕仓库 contrib/scaffolder 提供的 dry-run 测试思路展开:讲解如何使用命令行脚本,借助 Template Editor 同款 dry-run API,在不产生任何真实副作用的前提下,把一个模板目录、一份输入参数 YAML 渲染成一份可检查的输出目录;同时深入到 scaffolder-backend 与 createDryRunner.ts 的源码,讲清 dry-run 的请求格式、权限模型、执行引擎与 action 层的支持机制。读完后,你将能够快速搭建一套"改模板 → 本地 dry-run → 检查产物"的开发闭环,并能为自定义 action 接入 dry-run 支持。

为什么模板测试如此困难

Scaffolder 模板(Software Template)的渲染结果依赖大量外部因素:

  • 模板本身会执行 fetch、publish 等内置 action,也可能执行团队自研的自定义 action;
  • 每个 action 的真实副作用(创建仓库、注册 Catalog 条目、触发 Webhook 等)在本地难以低成本复现;
  • 参数校验、模板语法(如 Nunjucks 表达式)错误只有在实际执行时才会暴露。

正如 contrib/scaffolder/README.md 所指出:模板可以做到 backstage.io 和自定义 action 能做的任何事情,所以不实际运行目标 Backstage 实例,测试就无从谈起。为此,仓库提供了一个轻量方案——复用 Template Editor 的 dry-run API,在命令行中对模板做一次"预演",输出渲染后的目录结构与执行日志,而不真正落库、不调用外部系统。

快速上手:scaffolder-dry 命令行用法

将 template-testing-dry-run.md 中的脚本保存为可执行脚本后,对着一台正在运行的 Backstage 实例执行:

scaffolder-dry http://localhost:7007/ template-directory values.yml output-directory

四个位置参数依次为:

参数含义说明
urlBackstage 实例地址可以是本地http://localhost:7007/,也可以是远程实例;脚本会向{url}/api/scaffolder/v2/dry-run发起请求
template-path模板源目录包含template.yaml的模板根目录,目录中的文件会被整体打包上传
data-path输入参数 YAML提供渲染模板所需的parameters值的 YAML 文件
target输出目录dry-run 渲染结果写入的目标目录

如果实例启用了后端权限(Backend Permissions),需要通过--token传入当前浏览器会话的前端认证 token:

scaffolder-dry http://localhost:7007/ template-directory values.yml output-directory \ --token $FRONTEND_TOKEN

脚本的命令行参数在源码中定义如下(见 template-testing-dry-run.md):

  • 程序名backstage-scaffolder,功能描述为 "Creates a dry-run output of a given template";
  • -t, --token <value>:用于认证的 JWT(即上面所说的前端 token);
  • 四个必填位置参数urltemplate-pathdata-pathtarget

脚本工作流程:从模板目录到输出目录

从脚本源码(mainhandleloadDirectoryContents/writeResultContents/api的调用链)可以看出,一次 dry-run 分为四步:

  1. 打包模板目录loadDirectoryContents使用recursive-readdir递归读取模板目录(跳过.git),把每个文件的路径与 base64 内容组装成directoryContents数组;
  2. 解析输入:用yaml.parse读取参数文件得到values,并读取{template-path}/template.yaml得到模板定义template;同时初始化空的secrets = {}
  3. 调用 dry-run API:将{ directoryContents, values, secrets, template }作为请求体,向POST /api/scaffolder/v2/dry-run发起请求;
  4. 落盘结果writeResultContents根据响应中的directoryContents在目标目录重建文件结构——自动创建嵌套目录,并按executable标志设置文件权限(可执行文件0o755,普通文件0o644),同时把响应中的执行日志逐条打印到控制台。

值得注意的两个实现细节:

  • 请求体使用 gzip 压缩:脚本用gzipSync(JSON.stringify(bodyObj))压缩请求体,并通过Content-Encoding: gzip头告知服务端。这与大模板目录的传输效率直接相关;
  • 错误处理:当响应非 2xx 时,脚本会优先解析 JSON 中的error/errors字段抛出,否则抛出原始文本;响应为 2xx 时,解析{ log, directoryContents },日志逐条输出到 stdout。

dry-run API 的请求与响应格式

脚本请求体的四要素与后端路由 router.ts 中的 zod schema 一一对应:

const bodySchema = z.object({ template: z.unknown(), values: z.record(z.unknown()), secrets: z.record(z.string()).optional(), directoryContents: z.array( z.object({ path: z.string(), base64Content: z.string() }), ), });
字段类型说明
templateTemplateEntityV1beta3完整的模板定义(含spec.parametersspec.stepsspec.output);服务端会调用templateEntityV1beta3Validator.check校验其必须是合法模板
values对象渲染时使用的参数值,服务端会用模板spec.parameters中的 schema 逐一validate,失败则返回400errors
secrets对象(可选)模板用到的 secrets,会被注入到执行环境
directoryContents数组模板目录中每个文件{ path, base64Content }的列表,path为相对模板根目录的路径

响应体为ScaffolderDryRunResponse,核心字段包括:

  • log:按执行顺序排列的日志条目数组(每条含body.message,可能带stepId/status),脚本会原样打印;
  • directoryContents:渲染完成后工作区目录的{ path, base64Content, executable }列表,脚本据此重建输出目录;
  • output:模板spec.output定义的输出对象;
  • steps:服务端补充了idname后的执行步骤列表。

该接口在 openapi.yaml 中定义为POST /v2/dry-run,operationId 为DryRun,描述为 "Perform a dry-run of a template";前端 ScaffolderClient.dryRun 正是通过该端点实现 Template Editor 的预览功能。

后端执行原理:createDryRunner 与 NunjucksWorkflowRunner

路由收到合法请求后,会生成dryRunId,在工作目录下创建隔离的dry-run-content-${dryRunId}目录,并构造templateInfo(entityRef 固定为template:default/dry-run,baseUrl 指向临时目录中的template.yaml),随后把specdirectoryContentssecretscredentials交给 dry runner 执行(见 router.ts)。

真正的执行引擎位于 createDryRunner.ts:

  • 工作区还原deserializeDirectoryContents(contentsPath, input.directoryContents)把上传的 base64 文件还原到临时目录,作为所有相对文件路径(如 fetch 的targetPath)的基准;
  • 复用真实执行器:dry-run 并不是一套独立的迷你实现,而是把同一个NunjucksWorkflowRunnerisDryRun: true模式跑起来,因此模板中能用到的过滤器、全局函数、action 能力与真实任务一致;
  • 注入收尾 action:执行步骤末尾会被追加一个内部 actiondry-run:extractsupportsDryRun: true),它的 handler 通过serializeDirectoryContents(ctx.workspacePath)在渲染完成后把工作区内容序列化回directoryContents,这正是响应中产物目录的来源;
  • 无副作用落地done: false表示不写任务状态;complete直接抛 "Not implemented",禁止走真实任务的完成流程;执行结束后finallyfs.remove(contentsPath)清理临时目录;
  • 结果组装:返回{ log, directoryContents, output },其中logemitLog收集(内部dry-run:extract步骤自身的日志会被跳过)。

路由在权限检查时同时要求taskCreatePermissiontemplateDryRunPermission(见 router.ts),且每个被执行的 action 仍会经过actionExecutePermission授权——这与真实任务保持一致,也是为什么文档要求带--token的原因:没有有效的用户身份,dry-run 请求会被权限层拒绝。

为自定义 action 添加 dry-run 支持

dry-run 能够"只渲染、不生效",离不开 action 层对 dry-run 场景的显式适配。官方文档 dry-run-testing.md 给出了两层机制:

第一层:声明支持 dry-run。createTemplateAction配置对象中加入supportsDryRun: true

export function exampleAction() { return createTemplateAction<{ example: string }>({ id: 'action:example', description: 'Example action', schema: { input: { type: 'object', properties: { example: { title: 'example', type: 'string' }, }, }, }, supportsDryRun: true, async handler(ctx) { // ... }, }); }

第二层:在 handler 中区分 dry-run 分支。通过ctx.isDryRun判断当前是否处于预演模式,若是则只打印信息并提前返回,不发起真实副作用:

async handler(ctx) { // ... // If this is a dry run, log and return if (ctx.isDryRun) { ctx.logger.info(`Dry run complete`); return; } // ...真实副作用逻辑 }

测试配套。官方还建议为 dry-run 分支编写单元测试,通过构造{ ...mockContext, isDryRun: true }的上下文调用 handler 并断言未执行真实操作,例如:

it('should not perform action during dry run', async () => { const ctx = { ...mockContext, isDryRun: true, input: { /* ... */ }, }; await action.handler(ctx); expect(/* 无副作用发生 */); });

仓库内置 action 中也能看到这种适配的实例:例如 catalog 注册 action 在重试时调用catalog.addLocation({ dryRun: true, ... })(见 register.ts),用 dry-run 方式探测 location 是否已存在而不抛错,与模板测试使用同一套设计思想。

权限模型与认证细节

dry-run 不是一个"免认证"的调试后门,它走的是与真实任务一致的权限体系。相关依据可参见 authorizing-scaffolder-template-details.md:

  • templateDryRunPermission控制谁可以提交内联模板 dry-run,同时作用于POST /v2/dry-run端点与scaffolder:dry-run-template后端 action;
  • 直接调用 dry-run 端点还额外要求taskCreatePermission
  • 每次 dry-run 实际执行的 action 仍受actionExecutePermission约束。

这解释了脚本的--token参数:脚本以Authorization: Bearer <token>头携带凭证(见脚本源码api函数),token 应取自当前浏览器会话的前端认证 token。服务端通过httpAuth.credentials(req)解析请求方身份,并据此决定授权结果与执行上下文(如getInitiatorCredentials返回的用户身份)。

另外,dry-run 也会产生审计事件:在 audit-events.md 中记录到,所有与 dry-run 相关的审计事件都带meta.isDryLog标志,execute事件(真实任务的开始与完成追踪)在 dry-run 中不会出现——这与createDryRunnercomplete抛错的实现相互印证。

与其他入口的关系:Template Editor 与后端 action

dry-run 能力并非只有命令行脚本在用,仓库内至少存在三个消费方,理解它们有助于把握该 API 的定位:

  1. Template Editor(模板编辑器):前端 DryRunContext.tsx 在编辑器里点击预览时,通过scaffolderApi.dryRun({ template, values, secrets, directoryContents })调用同一端点,并把结果渲染在 DryRunResults 面板中。它还会把大于 64KB 的文件内容标记为<file too large>并分块编码,避免浏览器 base64 溢出;
  2. scaffolder:dry-run-template 后端 actioncreateDryRunTemplateAction(见 createDryRunTemplateAction.ts)把 dry-run 包装成另一个 action,供其他模板或编排逻辑调用;
  3. 本指南的命令行脚本:面向开发者的离线验证场景,输出可检查的目录产物。

三者共享POST /v2/dry-run端点与同一套执行引擎,因此用脚本验证通过的结果,与在 Template Editor 中预览的结果是一致的。

实践建议与局限

基于脚本源码与后端实现,可以总结出以下几点使用建议:

  • 本地优先scaffolder-dry http://localhost:7007/ ...针对本地实例设计,配合yarn dev启动的 Backstage 即可实现"改模板、跑一遍、看产物"的快速迭代;远程实例同样可用,但需注意权限与网络可达性;
  • 模板目录需包含 template.yaml:脚本固定读取{template-path}/template.yaml作为模板定义,目录内其余文件按相对路径上传,因此模板引用的file://相对路径资源必须位于该目录内才能被解析;
  • 产物即真相:输出目录中的文件、目录结构与可执行位(0o755/0o644)都模拟真实执行结果,可直接用来核对渲染是否正确,但请注意它不会真正调用外部系统,凡是未做ctx.isDryRun分支的 action 副作用不会被触发;
  • 认证别忘:启用了后端权限的实例必须传--token,否则会因缺少templateDryRunPermission/taskCreatePermission被拒绝。

需要明确的是:dry-run 验证的是模板渲染、参数校验、action 编排与文件产物的正确性,它无法替代针对真实副作用的集成测试(如发布到真实 Git 仓库后的收尾验证)。将 dry-run 脚本、Template Editor 预览与文档化的 action 单元测试结合使用,才能覆盖模板开发的主要验证需求。

相关资源

  • 贡献工具入口:contrib/scaffolder/README.md
  • 命令行脚本源码:contrib/scaffolder/template-testing-dry-run.md
  • dry-run 路由实现:plugins/scaffolder-backend/src/service/router.ts
  • dry-run 执行引擎:plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts
  • OpenAPI 定义:plugins/scaffolder-backend/src/schema/openapi.yaml
  • 前端客户端实现:plugins/scaffolder-common/src/ScaffolderClient.ts
  • Template Editor 预览实现:plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunContext.tsx
  • Action dry-run 支持指南:docs/features/software-templates/dry-run-testing.md
  • dry-run 权限说明:docs/features/software-templates/authorizing-scaffolder-template-details.md

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询