ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Backstage Scaffolder 模板参数内嵌模板语法:`parameters` Schema 的动态化设计提案与实现剖析

Backstage Scaffolder 模板参数内嵌模板语法:`parameters` Schema 的动态化设计提案与实现剖析 Backstage Scaffolder 模板参数内嵌模板语法parametersSchema 的动态化设计提案与实现剖析【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage导读本文围绕 Backstage 软件模板Scaffolder Template中一个高频诉求展开让parameters的 JSON Schema 字段值尤其是default默认值可以根据用户已填写的内容动态计算例如在description字段的默认值里引用name字段的值。文章以 BEP-0010Supporting templating syntax inparametersschema 为核心骨架完整解析其服务端装饰 Schema、扩展/parameter-schema端点的设计思路并结合当前仓库的源码实现路由、API 接口、模板渲染器与过滤器说明该提案的落点与边界。读完本文你将理解 Backstage 参数表单动态化的设计取舍、react-jsonschema-form对default字段的固有限制以及模板字符串在错误消息与自定义字段扩展中的安全传递方式。提案背景为什么parameters需要模板语法在 Backstage 的软件模板中parameters定义了一组或多组输入步骤用户在前端向导中填写后其值会被后续步骤的${{ parameters.xxx }}表达式消费。但长期以来parameters自身的 JSON Schema 是静态的——它无法引用用户已经填写过的值哪怕这些值就在同一个表单的另一个字段里。这意味着模板作者无法写出如下这种自然的默认值联动apiVersion: scaffolder.backstage.io/v1beta3 kind: Template metadata: name: my-template spec: parameters: - title: Some input description: Get some info from the user properties: name: type: string default: Test description: type: string default: ${{ parameters.name or unknown }}-description在这个示例中description的默认值试图复用name的取值并在其为空时回退为unknown。这正是 BEP-0010 想要解决的问题。从动机看该功能并非空想仓库内文档列出了多个请求此能力的 issue 与 PR如 #16275、#19597、#20533以及 PR #23283、#17746。由于社区存在两种彼此竞争的思路——一种是把模板语法直接嵌入parametersSchema另一种是把模板字符串透传给底层字段扩展field extensions——BEP 的目标是统一实现方式建立非冲突的标准化机制。需要说明的是该 BEP 在文档中的状态为provisional暂定。截至当前仓库代码/parameter-schema端点仍以GET方式提供静态 Schema本文所述的服务端装饰逻辑属于提案设计文中会明确区分已实现与提案中的内容。提案核心服务端装饰 Schema客户端驱动渲染整体思路BEP 的提案非常简洁在服务端用当前表单状态formData作为上下文去装饰decorate模板 Schema再用装饰后的 Schema 驱动客户端表单渲染。这样模板语法只需要在服务端执行一次前端拿到的是已经渲染好的、可直接使用的 JSON Schema。具体到接口层面方案分两步扩展/parameter-schema端点使其接受一个formData上下文查询参数即当前表单状态的 JSON 对象。由于要携带表单数据端点需要从GET升级为POST同时保留GET版本以维持向后兼容。前端反复调用该端点每当用户修改表单时Scaffolder 前端重新请求一次拿到基于最新 formData 渲染出的新 Schema从而让字段间的默认值、选项等实现动态联动。接口签名变化BEP 给出了ScaffolderApi接口的演进示例export interface ScaffolderApi { getTemplateParameterSchema( templateRef: string, formData?: JsonObject, ): PromiseTemplateParameterSchema; }对照当前仓库中 ScaffolderApi 接口 的实际情况export interface ScaffolderApi { getTemplateParameterSchema( templateRef: string, options?: ScaffolderRequestOptions, ): PromiseTemplateParameterSchema; }可见当前版本尚未携带formData这与 BEP 的provisional状态一致。该接口的客户端实现位于 ScaffolderClient.ts而前端脚手架侧用于拉取参数清单的 Hook useTemplateParameterSchema 目前也是直接以templateRef调用该方法、仅在依赖变化时重新请求——即静态 Schema模式。BEP 描述的每次 formData 更新都重新请求属于提案目标。服务端装饰流程BEP 用一段 diff 展示了服务端路由改造的核心逻辑端点从GET改为POST在原有的鉴权与模板解析之后追加模板渲染步骤router - .get( .post( /v2/templates/:namespace/:kind/:name/parameter-schema, async (req, res) { const credentials await httpAuth.credentials(req); const { token } await auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId: catalog, }); const template await authorizeTemplate( req.params, token, credentials, ); const parameters [template.spec.parameters ?? []].flat(); const secureTemplater await SecureTemplater.loadRenderer({ templateFilters: { ...createDefaultFilters({ integrations }), ...additionalTemplateFilters, }, templateGlobals: additionalTemplateGlobals, }); const templatedParameters parameters.map(parameter renderTemplateString( parameter, { parameters: req.body.formData, }, secureTemplater, logger, ), );这段设计的几个关键点值得展开渲染上下文renderTemplateString的上下文只注入了parameters: req.body.formData也就是当前用户已填写的值。这意味着模板作者在parametersSchema 里可以使用${{ parameters.字段名 }}表达式与steps模板中的语法保持一致。过滤器体系复用createDefaultFilters({ integrations })与additionalTemplateFilters的合并说明 Schema 装饰使用的过滤器与任务执行模板完全一致。从源码看默认过滤器定义在 createDefaultFilters.ts而integrations参数表明parseRepoUrl这类依赖 SCM 集成的过滤器同样可用。仓库中默认过滤器的具体实现包括parseRepoUrl、pick、parseEntityRef、projectSlug等见 lib/templating/filters。鉴权与审计链路保留改造后的端点仍然执行authorizeTemplate模板级权限校验与httpAuth.credentials(req)请求凭证校验。对照当前路由实现 router.ts现有的GET处理器同样会先做凭证获取与authorizeTemplate并发出template-parameter-schema审计事件——提案是在这条既有链路上增加渲染步骤。需要强调的是模板渲染器在仓库中是真实存在的只是它服务于任务步骤渲染而非 Schema 装饰。从 v1.51.0 发布说明 可以看到针对SecureTemplater使用的显式内存管理补丁说明该渲染器是 Scaffolder 后端运行时的核心组件当前后端源码中模板能力基于nunjitsu渲染引擎封装见 util/templating.ts。BEP 中的SecureTemplater.loadRenderer即对应这类服务端安全渲染器——它默认不注入任意全局对象只暴露白名单内的过滤器与全局函数。设计难点default字段的动态更新问题BEP 明确指出整个提案中最棘手的部分来自底层表单库react-jsonschema-form的行为限制首次渲染时default值会被填充并存入formData或当前状态此后default不会再被重新求值。也就是说即便服务端已经为 Schema 注入了模板渲染能力如果用户修改了被引用的字段比如上例中的name依赖它的default字段并不会自动跟着刷新——因为该默认值早已被固化进表单状态。BEP 给出了两条现实约束跨步骤使用是安全的如果引用默认值的字段与被引用字段位于表单的不同步骤step那么当用户进入新步骤时表单会重新渲染默认值会被重新求值。因此提案建议模板作者在需要${{ parameters.xxx }}作为默认值时把两个字段放在不同的步骤。同步骤内不做实时重渲染出于性能考虑不能对每次formData更新都重渲染整个表单。对于同步骤的联动BEP 提出了一种 workaround在parameter-schema更新时如果被更新的字段恰好被某个default: *字段引用则自动用新值替换formData中对应的旧值。作者自己也承认这是相当丑陋的补丁且不确定是否会影响 JSON Schema 中的其他字段类型如果存在则需要为这些字段一并实现。这也是设计文档中如实呈现的开放问题——它解释了为什么该 BEP 停留在 provisional 状态。错误消息与字段扩展的模板化封装优于裸字符串除了default之外模板语法还需要覆盖两类场景错误消息与自定义字段扩展。错误消息的模板化errorMessages的模板化已经通过ajv-errors库解决对应 PR #25624。ajv-errors允许为 JSON Schema 的验证规则定义自定义错误文本配合backrefs与 JSON Pointer 语法可以引用当前数据中的其他字段。在parametersSchema 中直接声明即可errorMessages: properties: name: required: 请填写名称当前值为 {{#data}}/name{{/data}}这样校验失败时用户看到的错误信息就能携带具体上下文值。字段扩展用ui:options封装不泄漏模板语法对于底层的自定义字段扩展field extensionBEP 提出的原则是不要向组件传递未经处理的模板字符串而应把意图封装进ui:options由组件自己决定如何拼装。示例parameters: properties: ... description: type: string default: Test-description ui:field: CustomDisplayField ui:options: format: entityAndName这里ui:field: CustomDisplayField声明了自定义渲染组件而ui:options.format: entityAndName是一种语义化格式。该字段扩展在内部可能等价于${{ parameters.entity }} - ${{ parameters.name }}的拼接但这种模板表达式永远不会泄漏到模板语言层面——组件在拿到数据后自行格式化。这种封装方式避免了模板语法在两个层面Schema 渲染与组件内部互相竞争、产生冲突。发布计划与依赖BEP 明确该改动向后兼容可以随一个 minor 版本发布不存在破坏性变更。理由很直接GET /parameter-schema保留新增的POST只是能力扩展ScaffolderApi.getTemplateParameterSchema新增可选参数不改变既有调用服务端装饰只在端点层面发生模板文件格式v1beta3无需变化。依赖一节为空说明该提案不依赖其他 BEP 或特性的落地。备选方案与取舍BEP 认真评估了两种备选路线并解释了为何不采用备选一客户端侧模板化把模板渲染放到前端执行而不是服务端。BEP 给出两条否决理由过滤器能力不对称parseRepoUrl、pick等过滤器以及模板作者在后端注册的自定义过滤器无法在客户端使用会导致模板行为前后不一致。性能收益消失由于default只在首次渲染时求值、之后不再重新评估客户端渲染在实时联动上同样受制于react-jsonschema-form的限制既然如此就没有必要把渲染放在客户端增加复杂度。备选二接受default字段的限制与其为default的动态更新实现 workaround不如直接把它当作已知限制记录下来。BEP 明确否定了这一方案对default字段做模板化是非常普遍的使用场景比如用前一个字段推导后一个字段的默认值接受限制会显著损害模板编写体验。与当前仓库实现的对照为了准确理解该提案的落地现状这里将提案设计与当前仓库源码做一个快速对照维度BEP-0010 提案当前仓库状态/parameter-schema方法POST保留GET仍为GET见 router.tsScaffolderApi.getTemplateParameterSchema增加formData参数仅接收templateRef与请求选项见 api.tsOpenAPI 描述需要扩展请求体当前仅定义GET见 openapi.yaml服务端模板渲染器SecureTemplater.loadRenderer装饰 Schema渲染器已存在基于 nunjitsu见 util/templating.ts但用于任务模板而非 Schema默认过滤器复用createDefaultFiltersparseRepoUrl、pick、parseEntityRef、projectSlug等已实现见 lib/templating/filterserrorMessages模板化基于ajv-errors解决已通过 PR #25624 落地可以看到BEP 所依托的基础设施安全模板渲染器、默认过滤器、端点鉴权链路在仓库中均已就绪真正待落地的是把渲染环节接入参数 Schema 端点这一层。这也是为什么该 BEP 篇幅虽短却精准定位了唯一需要设计的关键路径服务端渲染的注入点、default的求值语义、以及模板字符串向组件层的传递边界。总结BEP-0010 为 Backstage Scaffolder 的parameters表单注入模板能力提出了一个清晰、向后兼容的方案服务端以当前formData为上下文渲染 Schema前端通过反复请求装饰后的 Schema 驱动表单联动default字段的动态更新受react-jsonschema-form语义限制需要通过跨步骤组织或 formData 替换 workaround 解决错误消息模板化已由ajv-errors支撑自定义字段扩展则应通过ui:options封装意图而非泄漏模板字符串。无论你是模板作者想实现字段默认值联动还是插件开发者准备扩展表单能力理解这份设计都能帮你把握 Backstage 参数表单动态化的能力边界与演进方向。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表