☰
Chaos Mesh UI 前后端代码同步方案:@ui/openapi 包如何将 OpenAPI 与 kubebuilder 标记自动生成 TypeScript API 客户端和 Formik 表单
2026/9/27 8:50:38 网站建设 项目流程
  • 云原生
  • 运维
  • 测试
  • 可观测性

【免费下载链接】chaos-mesh

A Chaos Engineering Platform for Kubernetes.

项目地址:https://gitcode.com/gh_mirrors/ch/chaos-mesh
点击查看免费下载

Chaos Mesh 是一个面向 Kubernetes 的混沌工程平台,其 Dashboard 前端(ui/app)需要为十余种 Chaos 类型(AWSChaos、PodChaos、NetworkChaos 等)渲染创建/编辑表单,并调用大量后端 API。本文围绕仓库中 ui/packages/openapi/README.md 讲解@ui/openapi这个代码生成包:它如何利用 OpenAPI V2 规范一次性解决"前后端代码同步"与"重复类型定义"两大痛点,把swagger.yaml自动翻译成可直接使用的 TypeScript API 客户端与 Formik 表单元数据。读完本文,你将掌握该包的两个核心命令(client与formik)的执行链路、产物结构、表单字段映射规则,以及+ui:form:*标记如何从 Go 类型注释一路传递到前端表单的完整机制。

动机:为什么需要一个代码生成包

前后端类型不同步的痛点

在引入@ui/openapi之前,Chaos Mesh Dashboard 前端面临两类典型问题:

  • 代码同步:后端 API 与 Swagger 文档、前端调用代码需要手工保持一致,任何字段变更都要跨仓库/跨目录同步,极易遗漏;
  • 重复类型定义:同一个 Chaos 类型(例如V1alpha1AWSChaosSpec)既要在 Go 侧定义,又要在 TypeScript 侧以接口形式重复书写,形成双份维护负担。

@ui/openapi的思路是:用代码生成代替手工同步——以 pkg/dashboard/swaggerdocs/swagger.yaml 为单一事实来源,通过codegen命令自动产出前端所需的全部代码,从根源上消除手写差异。

TL;DR

一句话概括:本包通过codegen命令,把后端 Swagger 描述自动转换成两样东西:

  1. TypeScript 风格的 API 客户端(供前端发请求使用);
  2. Formik 表单元数据(供前端动态渲染各类 Chaos 的配置表单)。

codegen 命令总览

@ui/openapi是一个 pnpm workspace 下的私有包(见 ui/packages/openapi/package.json,"name": "@ui/openapi","private": true),入口脚本为 codegen.js,使用yargs解析子命令。

运行以下命令查看用法:

pnpm -F @ui/openapi codegen -h

输出如下:

codegen.js [command] Commands: codegen.js client generate API client by @openapitools/openapi-generator-cli codegen.js formik convert the definitions generated by @openapitools/openapi-generator-cli to Formik form data

也就是说codegen暴露了三个子命令(见 codegen.js 的yargs注册与switch分发逻辑):

子命令作用对应函数
client由 orval 基于swagger.yaml生成 API 客户端runClient()
formik基于生成的 TypeScript 类型定义生成 Formik 表单数据runFormik()
all依次执行上面两步(先生成客户端,再生成表单)runClient()+runFormik()

说明:README 中描述client命令调用的是@openapitools/openapi-generator-cli,而当前仓库的 codegen.js 实际已改用orval(import { generate as orval } from 'orval'),并在 orval.config.ts 中通过defineConfig配置生成行为。这与 README 存在版本演进差异,本文以当前源码为准。

client:从 OpenAPI 到 TypeScript API 客户端

执行流程

client子命令的完整链路如下(对应 codegen.js 中的runClient()):

  1. 拷贝 Swagger 文件:将pkg/dashboard/swaggerdocs/swagger.yaml复制到包目录下的./swagger.yaml;
  2. 清洗模块前缀:读取该文件,用正则github_com_chaos-mesh_chaos-mesh.*_去掉 Go 包路径前缀(这类前缀会污染生成的标识符),再写回./swagger.yaml;
  3. 调用 orval 生成:执行orval('./orval.config.ts'),依据 orval.config.ts 的配置产出代码;
  4. 清理临时文件:生成结束后用rimraf删除临时./swagger.yaml——这正是 README 中"At the end of the generation, this file will be deleted"的行为。

orval 生成配置解读

orval.config.ts 中值得关注的配置项:

  • input:./swagger.yaml,即上一步清洗后的 OpenAPI 文件;
  • output.mode:split,按模块拆分输出文件;
  • output.client:react-query,生成基于 TanStack React Query 的 hooks,而不是 README 时代的typescript-axios;
  • output.httpClient:axios,底层 HTTP 库;
  • output.target:../../app/src/openapi/index.ts,输出入口;
  • mutator:指向../../app/src/api/http.ts的customInstance,即前端自定义的 axios 实例(鉴权、拦截器等逻辑集中于此);
  • query:配置retry: 1、retryDelay: 3000的查询重试策略;
  • mock:开启 mock 支持(mock: true,delay: 0,required: true)。

生成产物

输出目录为 ui/app/src/openapi,当前包含三个文件:

  • index.ts:API 客户端入口(由 orval 生成,包含各模块方法与 react-query hooks);
  • index.schemas.ts:OpenAPI 定义对应的 TypeScript 接口/类型,是下一步formik命令的输入;
  • index.msw.ts:基于 MSW(Mock Service Worker)的接口 mock。

前端业务代码直接 import 这些产物发起请求,后端接口的任何变更只需重新运行codegen client即可同步到前端。

formik:从 TypeScript 类型到 Formik 表单元数据

formik子命令的核心是 index.js 中的genForms(source)函数,输入为ui/app/src/openapi/index.schemas.ts(见 codegen.js 的runFormik())。

生成哪些文件

对以下 12 种 Chaos 类型,每种生成一个表单数据文件,输出到 ui/app/src/formik:

AWSChaos、DNSChaos、GCPChaos、HTTPChaos、IOChaos、JVMChaos、 KernelChaos、NetworkChaos、PhysicalMachineChaos、PodChaos、 StressChaos、TimeChaos

对应产物为AWSChaos.ts、PodChaos.ts等 12 个文件,外加一个汇总所有 action 的actions.ts。

查找 Spec 的方式

genForms用 TypeScript Compiler API 建立程序(ts.createProgram([source], ...)),然后:

  1. 过滤出所有InterfaceDeclaration(index.schemas.ts中的接口);
  2. 按约定命名V1alpha1${child}Spec查找对应 Spec 接口(例如V1alpha1PodChaosSpec),见 index.js;
  3. 遍历该接口的属性签名,跳过ignores列表(selector、mode、value、duration、uid,定义于 constants.js);
  4. 若属性是action,则从注释中提取所有可选动作;否则调用nodeToField生成表单字段对象。

生成的表单数据示例

以 AWSChaos 为例,ui/app/src/formik/AWSChaos.ts 生成内容形如:

/** * This file was auto-generated by @ui/openapi. * Do not make direct changes to the file. */ export const actions = ['ec2-stop', 'ec2-restart', 'detach-volume'], data = [ { field: 'text', label: 'awsRegion', value: '', helperText: 'AWSRegion defines the region of aws.', }, { field: 'text', label: 'deviceName', value: '', helperText: 'Optional. DeviceName indicates the name of the device. Needed in detach-volume.', when: "action=='detach-volume'", }, { field: 'text', label: 'ec2Instance', value: '', helperText: 'Ec2Instance indicates the ID of the ec2 instance.', }, { field: 'text', label: 'secretName', value: '', helperText: 'Optional. SecretName defines the name of kubernetes secret.', }, { field: 'text', label: 'volumeID', value: '', helperText: 'Optional. EbsVolume indicates the ID of the EBS volume. Needed in detach-volume.', when: "action=='detach-volume'", }, ]

每个字段对象包含:

属性含义
field渲染控件类型:text/number/select/label/numbers/text-text/text-label/ref
label字段名(对应 Spec 的属性名)
value初始值(由类型推导,见下文)
helperText来自 Go 侧 jsdoc 注释的帮助文案
items下拉选项(枚举或布尔值的true/false)
when条件表达式,例如action=='detach-volume',用于按 action 动态显示字段
childrenref类型的嵌套子字段(对应 Go 中的嵌套结构体)

actions与data两个导出值共同驱动前端表单渲染(README 原话:"We will use theactionsanddatafields to render the form in the interface")。

再看一个实际产物 ui/app/src/formik/PodChaos.ts:

export const actions = ['pod-kill', 'pod-failure', 'container-kill'], data = [ { field: 'label', label: 'containerNames', value: [], helperText: 'Optional. ContainerNames indicates list of the name of affected container. If not set, the first container will be injected', }, { field: 'number', label: 'gracePeriod', value: 0, helperText: 'Optional. GracePeriod is used in pod-kill action. It represents the duration in seconds before the pod should be deleted. Value must be non-negative integer. The default value is zero that indicates delete immediately.', }, { field: 'text', label: 'remoteCluster', value: '', helperText: 'Optional. RemoteCluster represents the remote cluster where the chaos will be deployed', }, ]

TypeScript 类型到表单字段的映射规则

映射规则集中在 factory.js 的typeTextToFieldType与typeTextToInitialValue两个函数中,并被 factory.test.js 的单元测试逐条验证:

TypeScript 类型field初始值value
stringtext''
numbernumber0
booleanselectfalse(items为[true, false])
string[]label[]
number[]numbers[]
string[][]text-label{}
{ [key: string]: string }text-text{}
{ [key: string]: string[] }text-label{}
V1alpha1XXX[]label[]
枚举类型select''(items为枚举值列表)
结构体引用ref递归生成children子字段

对于结构体引用(TypeReference),typeReferenceToObjectLiteralExpression(factory.js)会递归展开其成员;若成员类型本身又是类型引用,则继续嵌套;V1alpha1Frame[]这类"非原始类型数组"会被标记为multiple: true的ref字段,用于渲染可增删的重复子结构。

注释标记的解析规则

生成器会读取 Go 侧 jsdoc 注释中的+ui:form:*系列标记,解析逻辑位于 utils.js,并被 utils.test.js 覆盖:

标记正则作用
+ui:form:enum=a;b;c\+ui:form:enum=(.+)\s提取 action 枚举值(用;分隔)
+kubebuilder:validation:Enum=a;b;c\+kubebuilder:validation:Enum=(.+)\s?兼容 kubebuilder 枚举标记,同样可提取枚举
+ui:form:when=action=='detach-volume'\+ui:form:when=(.+)\s生成字段的when条件表达式(换行会被替换为空格)
+ui:form:ignore\+ui:form:ignore\s跳过该字段,不生成表单项

getUIFormEnum会优先匹配+ui:form:enum,其次回退到+kubebuilder:validation:Enum;cleanMarkers则负责把+ui:form:when、+kubebuilder...等标记从注释中剥离,并把+optional改写成 "Optional. " 前缀(对应测试cleanMarkers('DeviceName ... +ui:form:when=... +optional')输出为'Optional. DeviceName indicates the name of the device.')。

这些标记的真实来源是 API 类型定义,例如 api/v1alpha1/awschaos_types.go 中:

// +ui:form:ignore ... // +ui:form:when=action=='detach-volume' // DeviceName indicates the name of the device. Needed in detach-volume. // +optional DeviceName string `json:"deviceName,omitempty"`

类似的标记还出现在 gcpchaos_types.go、iochaos_types.go、physical_machine_chaos_types.go 等多个类型文件中——可以说,表单的"智能"(哪些字段随 action 动态出现、哪些字段被隐藏、下拉选项是什么)完全由 Go 类型注释驱动,前端无需再维护一份规则。

生成的表单数据如何被前端消费

生成的formik/*.ts文件并非静态死代码,而是被 Dashboard 的表单组件动态加载。以 ui/app/src/components/AutoForm/index.tsx 为例:

const { data }: { data: AtomFormData[] } = await import(`../../formik/${kind}.ts`) const form = action ? data.filter((d) => { if (kind === 'NetworkChaos' && d.label === 'target') { return false // 某些 selector 暂不支持,忽略 target } if (d.when) { const parsed = parse(d.when) return expEval(parsed, { action }) // 根据当前 action 求值 when 表达式 } return true }) : data setInitialValues((oldValues) => _.merge({}, oldValues, formikProps.initialValues || formToRecords(form))) setForm(form)

关键点:

  • 通过import(\../../formik/${kind}.ts`)`按 Chaos 类型动态加载对应的表单元数据;
  • 当用户选择了某个action后,组件用表达式求值器对d.when(如action=='detach-volume')求值,只保留当前动作相关的字段;
  • formToRecords会把data数组转换为 Formik 的initialValues对象(ref类型递归展开,multiple字段转成数组);
  • 渲染阶段(同文件 index.tsx 的renderForm)根据field值分发到不同控件:text/number渲染TextField,select渲染SelectField,label渲染多选AutocompleteField,text-text/text-label渲染TextTextField,ref渲染带Chip删除能力的嵌套区块。

另外,ui/app/src/components/NewWorkflowNext/Elements/Kubernetes.tsx 与 PhysicalNodes.tsx 会直接import _actions from '@/formik/actions',用actions.ts中汇总的各类 Chaos 动作列表来渲染工作流编排页面的节点选项。

端到端数据流小结

把整条链路串起来:

api/v1alpha1/*.go(含 +ui:form:* 注释) │ controller-gen 生成 CRD 与 OpenAPI 描述 ▼ pkg/dashboard/swaggerdocs/swagger.yaml │ codegen client(拷贝 → 清洗模块前缀 → orval) ▼ ui/app/src/openapi/index.schemas.ts(TypeScript 类型)+ index.ts(API 客户端) │ codegen formik(TS Compiler API 解析 + 字段映射) ▼ ui/app/src/formik/{AWSChaos,...}.ts(表单元数据)+ actions.ts │ AutoForm/index.tsx 动态 import + when 表达式求值 ▼ Dashboard 上的 Chaos 创建/编辑表单

这条流水线的收益是:改 Go 类型注释 → 重新生成 → 前端表单与 API 客户端自动更新,任何一次codegen都能同时刷新 ui/app/src/openapi 与 ui/app/src/formik 两套产物,从机制上消灭手写类型不一致。

如何参与贡献

  • 通过 Pull Request 提交改动,或在 Issue 中描述问题;
  • 推荐的 PR/Issue 标题格式:xxx(@ui/openapi): xxx(便于在 changelog 与评审流中快速识别该包的变更,与 ui/packages/openapi/package.json 的包名对应);
  • 若修改了 factory.js 或 utils.js 等生成逻辑,请同步运行pnpm -F @ui/openapi test(jest)验证 factory.test.js 与 utils.test.js 中的映射规则与标记解析行为。

License

与 Chaos Mesh 主体一致,遵循 Apache License 2.0(源码文件头均含 Copyright 与 License 声明)。

  • 云原生
  • 运维
  • 测试
  • 可观测性

【免费下载链接】chaos-mesh

A Chaos Engineering Platform for Kubernetes.

项目地址:https://gitcode.com/gh_mirrors/ch/chaos-mesh
点击查看免费下载

相关推荐

上一篇:LKMPG认证讲师:成为内核培训师的路径
下一篇:MEV-template-rs错误处理与监控:构建稳定运行的MEV机器人系统

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

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

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

立即咨询