- 云原生
- 运维
- 测试
- 可观测性
【免费下载链接】chaos-mesh
A Chaos Engineering Platform for Kubernetes.
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 描述自动转换成两样东西:
- TypeScript 风格的 API 客户端(供前端发请求使用);
- 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()):
- 拷贝 Swagger 文件:将
pkg/dashboard/swaggerdocs/swagger.yaml复制到包目录下的./swagger.yaml; - 清洗模块前缀:读取该文件,用正则
github_com_chaos-mesh_chaos-mesh.*_去掉 Go 包路径前缀(这类前缀会污染生成的标识符),再写回./swagger.yaml; - 调用 orval 生成:执行
orval('./orval.config.ts'),依据 orval.config.ts 的配置产出代码; - 清理临时文件:生成结束后用
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], ...)),然后:
- 过滤出所有
InterfaceDeclaration(index.schemas.ts中的接口); - 按约定命名
V1alpha1${child}Spec查找对应 Spec 接口(例如V1alpha1PodChaosSpec),见 index.js; - 遍历该接口的属性签名,跳过
ignores列表(selector、mode、value、duration、uid,定义于 constants.js); - 若属性是
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 动态显示字段 |
children | ref类型的嵌套子字段(对应 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 |
|---|---|---|
string | text | '' |
number | number | 0 |
boolean | select | false(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.
相关推荐
FastEndpoints客户端生成终极指南:如何自动生成TypeScript和C客户端代码
FastEndpoints客户端生成终极指南:如何自动生成TypeScript和C 客户端代码 🚀 想要快速为你的ASP.NET Core API项目生成类型
后端Web框架API设计OpenAPI Generator 终极指南:如何快速生成API客户端与服务端代码
OpenAPI Generator 终极指南:如何快速生成API客户端与服务端代码 OpenAPI Generator 是一款强大的开源工具,能够根据OpenA
开发工具代码生成API设计CLI如何快速上手OpenAPI Generator:自动生成API客户端与服务端代码的完整指南
如何快速上手OpenAPI Generator:自动生成API客户端与服务端代码的完整指南 OpenAPI Generator是一个强大的开源工具,能够根据Op
开发工具代码生成API设计CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考