romnn/helm-schema
GitHub: romnn/helm-schema
通过静态分析 Helm 模板的实际使用逻辑并结合 Kubernetes 上游 schema,为零配置场景生成准确的 values.yaml JSON Schema。
Stars: 1 | Forks: 0
# helm-schema
[
](https://github.com/romnn/helm-schema/actions/workflows/build.yaml)
[
](https://github.com/romnn/helm-schema/actions/workflows/test.yaml)
[](https://deps.rs/repo/github/romnn/helm-schema)
[
](https://docs.rs/helm-schema)
[
](https://crates.io/crates/helm-schema)
通过**分析 chart 的模板**,为 Helm chart 的 `values.yaml` 生成 **JSON Schema**:
- 发现 chart(及其在 `charts/` 下的内置依赖,包括 `.tgz`/`.tar.gz` 归档文件)。
- 解析 Helm 模板并静态提取 `.Values.*` 的用法。
- 跟踪模板控制流(如 `if`、`with`、`range` 等守卫,以及 `eq`、`not` 和 `or` 等模式)。
- 跟踪 Kubernetes 资源上下文(尽最大努力匹配 `apiVersion`/`kind`),以便将值的用途映射到 Kubernetes 的 schema 路径。
- 使用上游 Kubernetes JSON schema 和 CRD schema 来推断值的类型。
- 将这些信号合并为一个 Draft-07 JSON schema(通常保存为 `values.schema.json`)。
## 为什么它与众不同
许多 Helm schema 生成方法主要依赖于:
- 将 chart 的 `values.yaml` 作为类型的真实来源,和/或
- 嵌入在 `values.yaml` 中的手动注释/评论。
这些方法可能很有用,但它们看不到值在模板中是*如何被实际使用的*。
`helm-schema` 更进了一步:
- **感知模板**:它检查模板并从实际的渲染逻辑中提取 `.Values.*` 路径。
- **感知资源**:它试图理解一个值被用在*哪个* Kubernetes 资源中,以及在该资源的*什么位置*。
- **基于 Schema**:它使用上游 Kubernetes JSON schema(和 CRD schema)来推断真实的 object/field 类型。
换句话说:它不是孤立地记录值,而是试图从以下方面推断契约:
- 模板如何使用值。
- Kubernetes 在目标字段处期望的格式。
其结果通常是比仅从 `values.yaml` 派生出的 schema 提供更多信息。
## 安装
```
# 通过 brew
brew install --cask romnn/tap/helm-schema
# 或从源码
cargo install --locked helm-schema-cli
```
## 使用说明
### 基础用法
为 chart 目录生成 schema:
```
helm-schema ./path/to/chart > values.schema.json
```
写入文件:
```
helm-schema ./path/to/chart --output values.schema.json
```
紧凑输出:
```
helm-schema ./path/to/chart --output values.schema.json --compact
```
### 实用工作流
使用预先填充的 schema 缓存完全离线生成 schema(无需网络访问):
```
helm-schema ./path/to/chart \
--offline \
--k8s-schema-cache-dir ./k8s-schema-cache \
--output values.schema.json
```
不使用 Kubernetes schema 生成(仅提取模板 + 默认值):
```
helm-schema ./path/to/chart --no-k8s-schemas --output values.schema.json
```
### Kubernetes schema 配置
- `--k8s-version `(可重复)
- 要查询的 Kubernetes 次版本目录,按用户提供的优先级顺序。
第一个值是主要的;任何后续值都是明确的回退。
默认值:`v1.35.0`。
- `--k8s-version-fallback=auto|`
- 自动将(单个明确的)`--k8s-version` 扩展 `N` 个较早的次版本。`auto`
使用默认窗口(`15`,其大小旨在覆盖现实的 K8s 弃用时间范围——实际分发中的 chart 仍然包含 `policy/v1beta1` 和
`networking.k8s.io/v1beta1`,这两者均在 v1.25 中被移除)。对于针对已弃用 API 的 chart(例如 `PodSecurityPolicy (policy/v1beta1)`)非常有用——
查找将依次回退通过 `v1.34.0 → v1.33.0 → …` 直到找到 schema。
与 `--strict-k8s-version` 互斥;当与多个明确的 `--k8s-version` 值组合时会被拒绝(在这种情况下,正确的调节方式是明确的列表)。
- 自动回退版本仅作为 schema 查找层的泄压阀;
它们不参与 apiVersion 推断缓存扫描(因此,如果 chart 缺少 `PodDisruptionBudget` 的 `apiVersion`,它不会去获取回退版本中的 `policy/v1beta1` 从而与 `policy/v1` 产生歧义)。
- `--k8s-schema-mirror `(可重复)
- 额外的上游 K8s schema 镜像 URL。基于源的缓存命名空间确保
镜像条目不会掩盖默认目录。**在严格和松散模式下均可用**——镜像只是备用的精确版本源,而非启发式方法。
- `--k8s-schema-cache-dir `
- K8s schema 的托管缓存根目录。受下方的[缓存兼容性契约](#cache-compatibility-policy-alpha)约束。
- `--strict-k8s-version`
- 禁用 Feature B 的自动回退链。仅与
`--k8s-version-fallback` 冲突;与 `--k8s-schema-mirror` 正交(互不影响)。
- `--offline`
- 禁用所有网络访问;仅使用本地缓存。
- `--no-k8s-schemas`
- 完全禁用 Kubernetes JSON schema 查找。
### CRD schema
- `--crd-version-lookup=strict|loose`(默认:`strict`)
- `strict`:仅查询 chart 固定的确切 `(group, kind, version)`。
- `loose`:在实际的 schema 解析方面与 strict 相同(CRD 版本永远不会被替换),但额外启用了一个本地缓存交叉扫描,当请求的版本缺失但缓存中存在相同 `(group, kind)` 的其他版本时,允许工具发出 `CrdVersionAvailableAtOtherVersions` 提示。
- `--strict-crd-version`
- `--crd-version-lookup=strict` 的简短别名。保留它是为了与其他 strict 标志保持对称,并使 CI 退出标志更简短。
- `--crd-catalog-mirror `(可重复)
- 额外的上游 CRD 目录镜像 URL。基于源的缓存命名空间确保
镜像条目不会掩盖默认目录。**在严格和松散模式下均可用**。
- `--crd-catalog-cache-dir `
- CRD schema 的托管缓存根目录。受下方的[缓存兼容性契约](#cache-compatibility-policy-alpha)约束。
- `--crd-override-dir `
- 手动维护的本地 schema 覆盖层。**绝不会被清除**,也绝不受缓存失效契约的约束。尽管标志名称中带有历史遗留的 `crd-` 前缀(为兼容性而保留),但该层接受**任何**分组资源的 schema——你在本地修补过的 CRD、你想要覆盖其上游 schema 的内置 K8s 资源,以及任何以 `(group, version, kind)` 为键的内容。它位于查找链的顶端,领先于 CRD 目录和 K8s OpenAPI 提供程序,因此放置在此处的任何内容都是权威的。
- 对内置 schema 进行权威性覆盖是刻意为之(这是用于添加自定义约束、将 chart 锁定到某个历史 schema 或解决上游 Bug 的高级用户功能)。安全隐患:不要将其指向你无法控制的目录;位于 `/_.json` 处的任何 JSON 都将静默替换上游的答案。
- 如果覆盖文件不可读,该链将发出 `LocalOverrideUnreadable` 并且**不会穿透**到目录或上游——静默地用不同的 schema 替换用户固定的 schema 是错误的。
- `--crd-cache-record-source`
- 在每个 CRD 缓存条目旁边写入一个 `.json.meta` 辅助文件,记录获取的 URL 和时间戳。在调试是哪个镜像做出了响应时非常有用。
注意:之前的 `--crd-catalog-dir` 标志已被**移除**。请改用 `--crd-override-dir`(手动维护的 schema)和/或 `--crd-catalog-cache-dir`(托管缓存根目录)。传递旧标志会导致 CLI 验证失败,并给出指向替代方案的提示。
### 针对未知类型的 apiVersion 猜测
当 IR 遍历器无法静态固定清单的 `apiVersion` 时(因为它是模板化的或不存在的),查找通常会失败并报 `apiVersion unknown`。有两个标志可用于控制可选的推断:
- `--api-version-guess`
- 启用三级推断路径:(1) 针对明确的 K8s + Prometheus operator 类型的硬编码规范 `kind → apiVersion` 候选列表,(2) 跨越每个已配置的 K8s + CRD 缓存命名空间的本地缓存扫描,(3) 针对 CRD 目录镜像的类型范围 HTTP 探测(仅适用于扩展候选列表中的类型——不进行盲目的 group 扫描)。当只推断出一个 apiVersion 时,该链会发出 `InferredApiVersion`。当存在多个合理的候选时(例如 `Ingress` 同时存在于 `networking.k8s.io/v1` 和 `extensions/v1beta1` 中),该链会弃权并发出 `AmbiguousApiVersion`。
- `--strict-api-versions`
- 完全禁用推断路径。与
`--api-version-guess` 互斥。
### 诊断
- `--diag-format=text|json`(默认:`text`)
- 在 `text` 模式下,诊断信息打印到 stderr,带有 `warning:` 或 `info:` 前缀。
- 在 `json` 模式下,每个诊断信息在 stderr 上作为单行 JSON 对象发出。在成功解析 CLI 后,每一行 stderr 都是一个 `Diagnostic` JSON 对象(基于 `"type"` 字段的区分联合)。CLI 解析错误不受此契约约束——在我们的运行时启动之前,clap 会将其纯文本的使用错误写入 stderr;JSON 使用者通过退出代码检测“解析与运行时”:在出现任何 JSON 行之前的非零退出意味着解析错误。
工具发出的诊断变体:
| 变体 | 何时发出 |
| --- | --- |
| `MissingSchema` | 链中没有任何提供程序拥有该资源。包含尝试过的 K8s 版本、尝试过的文件名,以及(如果可用)缓存中*确实*拥有该资源的其他版本。 |
| `ResolvedFromFallbackVersion` | 由非主要的 K8s 版本响应(Feature B)。 |
| `InferredApiVersion` | 为模板中没有静态 apiVersion 的类型推断出了 apiVersion(Feature D)。 |
| `AmbiguousApiVersion` | 存在多个合理的 apiVersions;链弃权。 |
| `CrdVersionNotFound` | 在任何探测位置均未找到 chart 精确的 CRD 版本。 |
| `CrdVersionAvailableAtOtherVersions` | 相同的 `(group, kind)` 在本地缓存中存在其他版本;仅供参考——该链永远不会替换。 |
| `LocalOverrideUnreadable` | 手动维护的覆盖层声明了某项资源,但其文件不可读。硬错误:链不会继续向下穿透。 |
| `CacheLayoutInvalidated` | 托管缓存根目录的布局早于编译后的二进制文件;该根目录已被清除并将重新填充。 |
| `CacheLayoutForwardIncompatible` | 托管缓存根目录包含的标记比二进制文件新;二进制文件拒绝修改该根目录。 |
### 缓存兼容性策略(alpha)
helm-schema 缓存布局**在 alpha 阶段不是稳定的兼容性表面**。
每个托管缓存根目录(`--k8s-schema-cache-dir`、`--crd-catalog-cache-dir`)都带有一个磁盘上的 `CACHE_LAYOUT_VERSION` 标记。在启动时:
- 标记与二进制文件中编译的常量匹配 → 根目录按原样使用。
- 标记缺失且根目录已填充(旧布局) → 托管子树将被清除并重新填充。会发出一个 `CacheLayoutInvalidated` 诊断信息。
- 标记缺失且根目录为空 → 首次填充,并写入标记。无诊断信息。
- 标记早于二进制文件的常量 → 清除并重新填充,同上。
- 标记晚于二进制文件的常量 → 二进制文件拒绝修改该根目录(前向不兼容)。会发出一个 `CacheLayoutForwardIncompatible` 诊断信息;该根目录保持原样不动。用户应进行升级或将该标志指向不同的路径。
K8s 和 CRD 根目录是**独立**进行版本控制和失效的。前向不兼容的 K8s 缓存不会阻止 CRD 解析,反之亦然。
`--crd-override-dir` 是一个**不同的概念**——它是手动维护的内容,绝不会被清除,没有标记,也不受此契约的约束。在 CLI 解析阶段,系统会防止将这两个角色在同一个目录中。
### 缓存布局(基于源的命名空间)
两个托管缓存根目录都使用基于源的布局,因此 `--k8s-schema-mirror` / `--crd-catalog-mirror` URL 绝不会静默掩盖默认目录的内容:
```
/
├── CACHE_LAYOUT_VERSION
├── default/ # built-in yannh catalog
│ ├── v1.35.0/service-v1.json
│ └── …
└── / # per-mirror namespace
└── v1.35.0/service-v1.json
/
├── CACHE_LAYOUT_VERSION
├── default/ # built-in datreeio catalog
│ ├── monitoring.coreos.com/servicemonitor_v1.json
│ └── …
└── / # per-mirror namespace
└── monitoring.coreos.com/servicemonitor_v1.json
```
查找时的优先级:默认目录优先于镜像。镜像的缓存条目保留在其自己的命名空间中以供检查/调试,但不会被返回。
### Chart 遍历选项
- `--exclude-tests`
- 不扫描 `templates/tests/**`。
- `--no-subchart-values`
- 不在组合值中包含 `charts/` 下的内置子 chart 值。
- `--infer-required`
- 将 chart 在模板顶部无条件检查的路径(`{{- if .Values.X }}` / `{{- eq .Values.X "..." }}` 且没有封闭守卫)标记为其父 object 的 `required`。通过任何 `default .Values.X` 回退(字面量或非字面量,例如 `default .Chart.Name .Values.nameOverride`)可到达的路径将被排除,因为 chart 明确处理了它们未被设置的情况。默认关闭——生成的 schema 保持模板逻辑所允许的宽松程度。
### 默认值类型推断
当模板使用 `default .Values.X`(或管道形式 `.Values.X | default `)时,`helm-schema` 会从字面量推断 `X` 的类型——字符串、整数、数字或布尔值。结合 `values.yaml` 中的 `null`(或缺失)值,推断出的 schema 将变为可为 null 的联合:`anyOf: [{type: null}, {type: }]`。这捕获了常见的 Helm 模式“提供一个 `null` 占位符,在渲染时回退到字面量”,而不会将其暴露为 schema 验证错误。
非字面量回退(`default .Chart.Name .Values.X`、`default (printf "%s" .Y) .Values.X`)不会获得类型提示——我们无法静态推断出类型——但它们仍然会针对 `X` 抑制 `--infer-required`。
#### 库(`type: library`)子 chart 的作用域
库 chart 没有自己的值作用域——它们的辅助程序在调用者的作用域内运行,其中 `.Values.X` 会根据 `include` 该辅助程序的 chart 进行解析。`helm-schema` 跨越每个 chart 的模板构建一个基于每个辅助程序的调用图:节点是单个 `{{ define "name" }}` 块,加上每个 chart 中位于任何 define 之外的文本对应的“chart 直接”伪节点,边则是 `{{ include "callee" ... }}` / `{{ template "callee" ... }}` 引用。
当生成非库 chart 的 schema 时,从该 chart 的 chart 直接 include 可到达的辅助程序——通过该图传递性地跟随——就是其信号(类型提示和被 `--infer-required` 使用的 `default` 回退路径)适用于该 chart 值前缀的辅助程序。这种解析是辅助程序粒度的,而不是库名称粒度的:如果一个库同时定义了应用程序包含的辅助程序和应用程序从不引用的另一个辅助程序,则它将仅贡献被包含的辅助程序的信号。传递链(`app → libA.X → libB.Y`)将信号从最底层的辅助程序带回应用程序。
### 应用 schema 覆盖
你可以使用一个或多个覆盖 schema 对生成的 schema 进行后处理:
```
helm-schema ./path/to/chart \
--override-schema ../schemas/injected-keys.override.json \
--override-schema ./schema-override.json \
--output values.schema.json
```
`--override-schema` 可重复使用。文件按给定的顺序应用,每个都合并到前一个结果之上。预期的拆分是一个共享的跨 chart 覆盖(例如,外部 pipeline 在渲染时注入且 helm-schema 无法在 `values.yaml` 中看到的顶级键),随后是特定于 chart 的覆盖。
覆盖将作为递归合并应用(对联合 `required` 列表进行特殊处理),这对于收紧类型和填补推断空白非常有用。一个例外是:包含 `$ref` 的覆盖子树会完全替换相应的基础子树,而不是进行合并——JSON Schema draft-07 会忽略 `$ref` 的同级节点,否则进行合并会将基础中推断出的约束与被引用 schema 的约束并列留下,从而产生任何输入都无法满足的结构。
## 工作原理(宏观概述)
- Chart 发现读取 `Chart.yaml`(并支持 `Chart.template.yaml`),并遍历 `charts/` 下的内置依赖项。
- 它通过将根目录的 `values.yaml` 与其依赖项键下的子 chart 默认值进行合并(并将子 chart 的 `global` 合并到顶级的 `global` 中),组合出一个有效的值文档。
- 解析模板并建立命名 `define` 块的索引,以便分析辅助模板。
- 符号提取器从模板动作中收集值用途(`.Values.*`)和守卫,并尝试跟踪发出值的 YAML 路径。
- 还会扫描 chart 的 `files/` 目录以查找 YAML/TPL 片段;当模板使用字面量路径从 `.Files.Get` 加载 YAML 片段时,这些片段也可以被分析。
- 查询 Kubernetes/CRD schema 提供程序链,以推断在特定资源路径中使用的值的类型。
- 推断信号被合并为一个以 Helm 值对象为根的单一 JSON schema。
## 状态/免责声明
这个项目**非常有用,并且适用于许多 chart**,但它**尚未完全达到生产就绪状态**或 100% 可靠。
Helm 模板和 YAML 组合有许多边缘情况(空白截断、动态键、辅助程序间接、运行时特定行为等)。
预期某些 chart 需要手动覆盖,并预计会出现偶尔不正确或缺失的推断。
标签:3D图, Helm, JSON Schema, Rust, SOC Prime, 云安全监控, 可视化界面, 子域名突变, 开发工具, 网络流量审计, 通知系统, 静态分析