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四个位置参数依次为:
| 参数 | 含义 | 说明 |
|---|---|---|
url | Backstage 实例地址 | 可以是本地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);- 四个必填位置参数
url、template-path、data-path、target。
脚本工作流程:从模板目录到输出目录
从脚本源码(main→handle→loadDirectoryContents/writeResultContents/api的调用链)可以看出,一次 dry-run 分为四步:
- 打包模板目录:
loadDirectoryContents使用recursive-readdir递归读取模板目录(跳过.git),把每个文件的路径与 base64 内容组装成directoryContents数组; - 解析输入:用
yaml.parse读取参数文件得到values,并读取{template-path}/template.yaml得到模板定义template;同时初始化空的secrets = {}; - 调用 dry-run API:将
{ directoryContents, values, secrets, template }作为请求体,向POST /api/scaffolder/v2/dry-run发起请求; - 落盘结果:
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() }), ), });| 字段 | 类型 | 说明 |
|---|---|---|
template | TemplateEntityV1beta3 | 完整的模板定义(含spec.parameters、spec.steps、spec.output);服务端会调用templateEntityV1beta3Validator.check校验其必须是合法模板 |
values | 对象 | 渲染时使用的参数值,服务端会用模板spec.parameters中的 schema 逐一validate,失败则返回400与errors |
secrets | 对象(可选) | 模板用到的 secrets,会被注入到执行环境 |
directoryContents | 数组 | 模板目录中每个文件{ path, base64Content }的列表,path为相对模板根目录的路径 |
响应体为ScaffolderDryRunResponse,核心字段包括:
log:按执行顺序排列的日志条目数组(每条含body.message,可能带stepId/status),脚本会原样打印;directoryContents:渲染完成后工作区目录的{ path, base64Content, executable }列表,脚本据此重建输出目录;output:模板spec.output定义的输出对象;steps:服务端补充了id与name后的执行步骤列表。
该接口在 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),随后把spec、directoryContents、secrets、credentials交给 dry runner 执行(见 router.ts)。
真正的执行引擎位于 createDryRunner.ts:
- 工作区还原:
deserializeDirectoryContents(contentsPath, input.directoryContents)把上传的 base64 文件还原到临时目录,作为所有相对文件路径(如 fetch 的targetPath)的基准; - 复用真实执行器:dry-run 并不是一套独立的迷你实现,而是把同一个
NunjucksWorkflowRunner以isDryRun: true模式跑起来,因此模板中能用到的过滤器、全局函数、action 能力与真实任务一致; - 注入收尾 action:执行步骤末尾会被追加一个内部 action
dry-run:extract(supportsDryRun: true),它的 handler 通过serializeDirectoryContents(ctx.workspacePath)在渲染完成后把工作区内容序列化回directoryContents,这正是响应中产物目录的来源; - 无副作用落地:
done: false表示不写任务状态;complete直接抛 "Not implemented",禁止走真实任务的完成流程;执行结束后finally中fs.remove(contentsPath)清理临时目录; - 结果组装:返回
{ log, directoryContents, output },其中log由emitLog收集(内部dry-run:extract步骤自身的日志会被跳过)。
路由在权限检查时同时要求taskCreatePermission与templateDryRunPermission(见 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 中不会出现——这与createDryRunner中complete抛错的实现相互印证。
与其他入口的关系:Template Editor 与后端 action
dry-run 能力并非只有命令行脚本在用,仓库内至少存在三个消费方,理解它们有助于把握该 API 的定位:
- Template Editor(模板编辑器):前端 DryRunContext.tsx 在编辑器里点击预览时,通过
scaffolderApi.dryRun({ template, values, secrets, directoryContents })调用同一端点,并把结果渲染在 DryRunResults 面板中。它还会把大于 64KB 的文件内容标记为<file too large>并分块编码,避免浏览器 base64 溢出; - scaffolder:dry-run-template 后端 action:
createDryRunTemplateAction(见 createDryRunTemplateAction.ts)把 dry-run 包装成另一个 action,供其他模板或编排逻辑调用; - 本指南的命令行脚本:面向开发者的离线验证场景,输出可检查的目录产物。
三者共享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),仅供参考