From d78ce33bfc7ffeaa2e1bfd8cc317d236dad3d5f1 Mon Sep 17 00:00:00 2001 From: whg517 Date: Thu, 27 Aug 2026 20:33:46 +0800 Subject: [PATCH 1/2] docs(reference): write the Kubebuilder reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces a one-line placeholder that also misspelled the title as "Kuberbuilder". Scoped to the parts of Kubebuilder this project actually uses rather than restating the upstream book. Facts come from the repositories: the PROJECT file and its domain/layout, the controller-gen and kustomize versions pinned in the Makefile, and a marker inventory taken by grepping the API packages of operator-go, zookeeper-operator and spark-k8s-operator so the tables list markers that are genuinely in use. Documents two failure modes this project has already hit. Markers are case-sensitive. `+kubebuilder:validation:optional` with a lowercase o is not a marker; controller-gen ignores it silently and the field falls back to the rule that a field without omitempty is required. Nothing warns, the build succeeds, and the field lands in the CRD's required list. zookeeper-operator has exactly this on ClusterConfigSpec.ListenerClass today, and the generated CRD shows `required: [listenerClass]` as a result. The page gives the grep that reveals it. The second is +kubebuilder:default on fields inside `config`, which breaks the role to role group fold — same reason already recorded in the development guideline, restated here because this is where someone reaches for the marker. Co-Authored-By: Claude Opus 5 --- docs/reference/kubebuilder.md | 130 +++++++++++++++++- .../current/reference/kubebuilder.md | 125 ++++++++++++++++- 2 files changed, 253 insertions(+), 2 deletions(-) diff --git a/docs/reference/kubebuilder.md b/docs/reference/kubebuilder.md index 0975766c..7deab756 100644 --- a/docs/reference/kubebuilder.md +++ b/docs/reference/kubebuilder.md @@ -1 +1,129 @@ -# Kuberbuilder +--- +title: Kubebuilder +--- + +Every Kubedoop Operator is a [Kubebuilder](https://book.kubebuilder.io/) project. This page is a +reference for the parts of Kubebuilder that Kubedoop actually relies on — the project metadata, the +marker vocabulary used in the API types, and the code generation pipeline. It is aimed at people +working on the Operators; see [Development Guideline](../developer-manual/develop-guideline.md) for +the wider workflow. + +## The PROJECT file + +Every Operator repository has a `PROJECT` file at its root recording how the project was scaffolded +and which resources it owns. It is generated — do not hand-edit it. + +```yaml +cliVersion: 4.10.1 +domain: kubedoop.dev +layout: +- go.kubebuilder.io/v4 +projectName: zookeeper-operator +repo: github.com/zncdatadev/zookeeper-operator +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: kubedoop.dev + group: zookeeper + kind: ZookeeperCluster + path: github.com/zncdatadev/zookeeper-operator/api/v1alpha1 + version: v1alpha1 +version: "3" +``` + +Two things are consistent across the whole project: + +- **`domain: kubedoop.dev`** — every API group is `.kubedoop.dev`, which is why resources + appear as `zookeeper.kubedoop.dev/v1alpha1`, `s3.kubedoop.dev/v1alpha1` and so on +- **`layout: go.kubebuilder.io/v4`** — all Operators use the v4 layout, so their directory + structures match + +## Code generation + +Two tools do the generation, both pinned in each Operator's Makefile and downloaded into `bin/`: + +| Tool | Version | Produces | +|------|---------|----------| +| `controller-gen` | v0.19.0 | CRD YAML under `config/crd/bases/`, and `zz_generated.deepcopy.go` | +| `kustomize` | v5.7.1 | The assembled install manifests | + +```bash +make manifests # CRD YAML from the Go types +make generate # DeepCopy implementations +``` + +Both outputs are **committed to the repository**. Editing an API type without regenerating leaves +the CRD and the Go types disagreeing, and shows up as a diff in CI. The Helm chart keeps yet another +copy — see [Development Guideline](../developer-manual/develop-guideline.md) for `helm-crd-sync`. + +## Markers + +Markers are `+kubebuilder:` comments above a type or field that tell `controller-gen` what to emit. +These are the ones Kubedoop uses. + +### Object markers + +| Marker | Effect | +|--------|--------| +| `+kubebuilder:object:root=true` | This type is a top-level API object, so generate a CRD for it | +| `+kubebuilder:object:generate=true` | Generate DeepCopy for every type in the package | +| `+kubebuilder:subresource:status` | Give the resource a `/status` subresource | +| `+kubebuilder:resource:path=...,scope=Cluster,shortName=...` | Plural path, scope, and `kubectl` short name | +| `+kubebuilder:printcolumn:name=...` | Extra column in `kubectl get` output | + +Most Kubedoop resources are namespaced. The cluster-scoped ones are the classes that describe +shared infrastructure — `AuthenticationClass`, `ListenerClass` — because they are referenced by name +from any namespace. + +### Validation markers + +| Marker | Effect | +|--------|--------| +| `+kubebuilder:validation:Required` / `Optional` | Whether the field must be present | +| `+kubebuilder:default=` | Default applied by the API server when the field is absent | +| `+kubebuilder:validation:Enum=a;b;c` | Restrict to a fixed set | +| `+kubebuilder:validation:Minimum` / `Maximum` | Numeric bounds | +| `+kubebuilder:validation:Pattern` | Regular expression | +| `+kubebuilder:validation:items:MinLength` / `items:Pattern` | Constraints on array elements | +| `+kubebuilder:validation:XValidation:rule="..."` | CEL expression for anything the above cannot express | + +## Two ways to get this wrong + +Both of these have already bitten this project. + +### Markers are case-sensitive + +`controller-gen` matches marker names exactly. `+kubebuilder:validation:optional` — lowercase `o` — +is not a marker; it is silently ignored, and the field falls back to the default rule that a field +without `omitempty` in its JSON tag is **required**. + +Nothing warns you. The build succeeds, the CRD generates, and the field quietly lands in the CRD's +`required` list. The only way to notice is to read the generated YAML: + +```bash +grep -A3 'required:' config/crd/bases/_.yaml +``` + +Always write `Optional` and `Required` capitalised, and give optional fields `omitempty` in the JSON +tag as well. + +### Defaults on fields inside `config` + +`+kubebuilder:default` looks harmless but must not be used on fields inside the `config` block, +which is folded from role to role group. + +Structural defaulting fills a leaf as soon as its **enclosing object** exists. So a default on a +field inside `config` is applied to any role group that wrote a `config` block for any reason at +all — which makes "the group did not set this" indistinguishable from "the group explicitly set the +default", and stops the role-level value from ever winning the merge. + +Several fields in `operator-go` carry comments explaining exactly this and deliberately omit a +default. Defaults for folded fields belong at consumption time, in the code that reads them. + +## Related + +- [Kubebuilder Book](https://book.kubebuilder.io/) +- [CRD generation reference](https://book.kubebuilder.io/reference/generating-crd) +- [Development Guideline](../developer-manual/develop-guideline.md) diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/reference/kubebuilder.md b/i18n/zh/docusaurus-plugin-content-docs/current/reference/kubebuilder.md index 0975766c..755ab71d 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/reference/kubebuilder.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/reference/kubebuilder.md @@ -1 +1,124 @@ -# Kuberbuilder +--- +title: Kubebuilder +--- + +每个 Kubedoop Operator 都是一个 [Kubebuilder](https://book.kubebuilder.io/) 项目。 +本页是 Kubedoop 实际用到的那部分 Kubebuilder 的参考——项目元数据、API 类型中使用的 marker 词汇, +以及代码生成流程。面向的是开发 Operator 的人;更完整的流程参见 +[开发指南](../developer-manual/develop-guideline.md)。 + +## PROJECT 文件 + +每个 Operator 仓库根目录都有一个 `PROJECT` 文件,记录项目的脚手架方式和它拥有哪些资源。 +它是生成的,不要手工编辑。 + +```yaml +cliVersion: 4.10.1 +domain: kubedoop.dev +layout: +- go.kubebuilder.io/v4 +projectName: zookeeper-operator +repo: github.com/zncdatadev/zookeeper-operator +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: kubedoop.dev + group: zookeeper + kind: ZookeeperCluster + path: github.com/zncdatadev/zookeeper-operator/api/v1alpha1 + version: v1alpha1 +version: "3" +``` + +其中有两项在整个项目中是统一的: + +- **`domain: kubedoop.dev`** —— 所有 API 组都是 `.kubedoop.dev`, + 因此资源表现为 `zookeeper.kubedoop.dev/v1alpha1`、`s3.kubedoop.dev/v1alpha1` 等 +- **`layout: go.kubebuilder.io/v4`** —— 所有 Operator 都使用 v4 布局,因此目录结构彼此一致 + +## 代码生成 + +生成工作由两个工具完成,版本都钉在各 Operator 的 Makefile 里,并下载到 `bin/`: + +| 工具 | 版本 | 产出 | +|------|------|------| +| `controller-gen` | v0.19.0 | `config/crd/bases/` 下的 CRD YAML,以及 `zz_generated.deepcopy.go` | +| `kustomize` | v5.7.1 | 组装后的安装清单 | + +```bash +make manifests # 从 Go 类型生成 CRD YAML +make generate # 生成 DeepCopy 实现 +``` + +两类产物都是**纳入版本控制的**。改了 API 类型却不重新生成,会让 CRD 和 Go 类型不一致, +并在 CI 中表现为 diff。Helm chart 还另存了一份副本——参见 +[开发指南](../developer-manual/develop-guideline.md)中的 `helm-crd-sync`。 + +## Marker + +Marker 是写在类型或字段上方的 `+kubebuilder:` 注释,用来告诉 `controller-gen` 该生成什么。 +以下是 Kubedoop 实际使用的这些。 + +### 对象类 marker + +| Marker | 作用 | +|--------|------| +| `+kubebuilder:object:root=true` | 该类型是顶层 API 对象,为它生成 CRD | +| `+kubebuilder:object:generate=true` | 为包内所有类型生成 DeepCopy | +| `+kubebuilder:subresource:status` | 为资源添加 `/status` 子资源 | +| `+kubebuilder:resource:path=...,scope=Cluster,shortName=...` | 复数路径、作用域、`kubectl` 简称 | +| `+kubebuilder:printcolumn:name=...` | 在 `kubectl get` 输出中增加一列 | + +Kubedoop 的资源大多是命名空间级的。集群级的是那些描述共享基础设施的 class—— +`AuthenticationClass`、`ListenerClass`——因为它们要被任意命名空间按名字引用。 + +### 校验类 marker + +| Marker | 作用 | +|--------|------| +| `+kubebuilder:validation:Required` / `Optional` | 字段是否必须存在 | +| `+kubebuilder:default=` | 字段缺失时由 API server 填充的默认值 | +| `+kubebuilder:validation:Enum=a;b;c` | 限定取值集合 | +| `+kubebuilder:validation:Minimum` / `Maximum` | 数值范围 | +| `+kubebuilder:validation:Pattern` | 正则表达式 | +| `+kubebuilder:validation:items:MinLength` / `items:Pattern` | 数组元素的约束 | +| `+kubebuilder:validation:XValidation:rule="..."` | 上述都表达不了时使用的 CEL 表达式 | + +## 两个容易写错的地方 + +这两个坑本项目都已经踩过。 + +### Marker 区分大小写 + +`controller-gen` 精确匹配 marker 名称。`+kubebuilder:validation:optional`——小写的 `o`—— +根本不是一个 marker,它会被静默忽略,字段随即回落到默认规则: +**JSON tag 中没有 `omitempty` 的字段视为必填**。 + +不会有任何提示。构建成功、CRD 正常生成,而该字段悄悄进入了 CRD 的 `required` 列表。 +唯一能发现的办法是去读生成出来的 YAML: + +```bash +grep -A3 'required:' config/crd/bases/_.yaml +``` + +`Optional` 和 `Required` 请始终大写开头,并且给可选字段的 JSON tag 也加上 `omitempty`。 + +### 不要给 `config` 内的字段加默认值 + +`+kubebuilder:default` 看着无害,但绝不能用在 `config` 块内部的字段上, +因为该块会从角色折叠到角色组。 + +结构化默认值会在其**外层对象**一存在时就填充叶子字段。于是,`config` 内字段上的默认值, +会被施加到任何因为**任何原因**写了 `config` 块的角色组上——这就让"该组没有设置这个值" +和"该组显式设成了默认值"变得无法区分,角色级的取值也就永远赢不了这次合并。 + +`operator-go` 中有若干字段的注释专门解释了这一点,并刻意不加默认值。 +折叠字段的默认值应该放在消费时施加,即在读取它们的代码里。 + +## 相关内容 + +- [Kubebuilder Book](https://book.kubebuilder.io/) +- [CRD 生成参考](https://book.kubebuilder.io/reference/generating-crd) +- [开发指南](../developer-manual/develop-guideline.md) From 400e2d5fbf4a06172eee0b8cf6d102c63057c71c Mon Sep 17 00:00:00 2001 From: whg517 Date: Thu, 27 Aug 2026 20:34:16 +0800 Subject: [PATCH 2/2] docs(developer-manual): translate first-commiter, and fix two errors in it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The English page held Chinese prose and was byte-identical to its zh counterpart — the second of the two cases AGENTS.md flags, and the last one. The English tree is now English throughout. Translating surfaced two errors that were in both copies: The upstream remote was `https://github.com/kubedoop.dev/docs`. kubedoop.dev is the documentation site's domain, not a GitHub organisation, so that command could never have worked. It is zncdatadev/docs. The merge section explained the problem in terms of "远程的 upstream (swoole/swoole-src)" — a leftover from the Swoole project this text was adapted from. While rewriting: replaced the two-entry feature/fix list with the full branch-naming table the project actually uses, switched `git checkout` to `git switch`, added the force-with-lease step that a first-time contributor hits the moment they rebase an already-pushed branch, and pointed the pre-PR check at `npm run verify`. Co-Authored-By: Claude Opus 5 --- docs/developer-manual/first-commiter.md | 92 +++++++++++++------ .../developer-manual/first-commiter.md | 83 +++++++++++------ 2 files changed, 120 insertions(+), 55 deletions(-) diff --git a/docs/developer-manual/first-commiter.md b/docs/developer-manual/first-commiter.md index 4f96437b..2c260d4a 100644 --- a/docs/developer-manual/first-commiter.md +++ b/docs/developer-manual/first-commiter.md @@ -1,84 +1,118 @@ +--- +title: First Contribution +--- -# 第一次贡献 +If this is your first time contributing on GitHub, the steps below will get you started. For the +conventions this project expects — branch names, commit messages, what to run before opening a pull +request — see [Collaboration Guide](./collaboration.md) and +[Development Guideline](./develop-guideline.md). -如果你是第一次在 Github 上贡献代码,请参考如下步骤快速开始: +## Fork the project -## Fork 项目 - -- 首先需要fork这个项目, 进入项目页面, 点击右上角的Fork按钮 -- 你的 github 帐号中会出现相应名称的项目, 例如: `/docs` -- 在本地电脑(Linux)上使用以下命令克隆项目到本地 +- Open the project page and click **Fork** in the top right +- A copy appears under your account, for example `/docs` +- Clone it locally: ```bash git clone https://github.com//docs ``` -## 获取最新源代码 +## Track the upstream repository -将本地个人仓库和上游仓库关联 +Link your local clone to the upstream repository: ```bash -git remote add upstream https://github.com/kubedoop.dev/docs +git remote add upstream https://github.com/zncdatadev/docs ``` -同步最新源代码 +Then sync: ```bash git pull upstream main ``` -现在我们在 fork 来的 `main` 分支上, 这个 `main` 留作跟踪 `upstream` 的远程代码 +You are now on the `main` branch of your fork, which is kept for tracking `upstream`. Do not commit +to it directly. -## 创建分支 +## Create a branch -现在开始在本地开发,并准备贡献代码。 +Do your work on a branch rather than on `main`. -按照国际惯例,我们一般不在 `main` 分支上开发,而是创建一个新的分支,然后在新的分支上开发,开发完成后再合并到 `main` 分支。 +Name the branch for the kind of change you are making: -首先明确我们要不贡献的代码是一个新的功能特性还是修复一个 bug 。如果是新增一个功能特性,需要创建一个基于 `feature/` 开头的 -分支,如果是修复一个 bug ,在创建一个基于 `fix/` 开头的分支。 +| Type | Format | Example | +|------|--------|---------| +| New feature | `feature/-` | `feature/kafka-rebalance` | +| Bug fix | `fix/-` | `fix/hdfs-memory-leak` | +| Documentation | `docs/` | `docs/add-trino-operator` | +| Refactor | `refactor/-` | `refactor/operator-go-api` | +| Chore, deps, CI | `chore/` | `chore/upgrade-k8s-0.36` | ```bash -git checkout -b fix/foo-error +git switch -c fix/foo-error ``` -然后在这个分支上进行代码开发,并在开发完成后提交代码。 +Make your changes, then commit. Commit messages follow +[Conventional Commits](https://www.conventionalcommits.org/): ```bash git commit -a -m "fix: foo error" ``` -## 合并修改 +## Rebase before you push -一个常见的问题是远程的 upstream (swoole/swoole-src) 有了新的更新, 从而会导致我们提交的 Pull Request 时会导致冲突, 因此我们可以在提交前先把远程其他开发者的commit和我们的commit合并. +While you were working, `upstream` probably moved. Rebasing onto the latest `main` first means your +pull request applies cleanly instead of arriving with conflicts. -首先我们需要切换到 `main` 分支, 然后同步最新的代码 +Update `main`: ```bash -git checkout main +git switch main git pull upstream main ``` -然后切换回我们的开发分支, 并合并 `main` 分支 +Then rebase your branch onto it: ```bash -git checkout fix/foo-error +git switch fix/foo-error git rebase main ``` -如果有冲突, 请解决冲突, 然后继续合并 +If there are conflicts, resolve them and continue: ```bash git add . git rebase --continue ``` -最后提交代码 +Then push to your fork: ```bash git push origin fix/foo-error ``` -## 提交 Pull Request +If you had already pushed the branch before rebasing, the push will be rejected because the history +changed. Force-push your own branch: + +```bash +git push --force-with-lease origin fix/foo-error +``` + +## Open a pull request + +Go to the project on GitHub, switch to the branch you just pushed, click **Pull Request**, and fill +in the description. + +Before you do, run the checks locally so CI does not fail on something you could have caught: + +```bash +npm run verify +``` + +All CI checks must pass, and one reviewer approval is required, before a pull request can be merged. + +## Related -在 Github 的项目中,切换到刚刚推送的分支,点击 `Pull Request` 按钮,填写相应的信息,然后提交 Pull Request。 +- [Collaboration Guide](./collaboration.md) +- [Development Guideline](./develop-guideline.md) +- [Document Writing Guidelines](./document-guideline.md) diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/developer-manual/first-commiter.md b/i18n/zh/docusaurus-plugin-content-docs/current/developer-manual/first-commiter.md index 4f96437b..bec38903 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/developer-manual/first-commiter.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/developer-manual/first-commiter.md @@ -1,84 +1,115 @@ +--- +title: 第一次贡献 +--- -# 第一次贡献 - -如果你是第一次在 Github 上贡献代码,请参考如下步骤快速开始: +如果你是第一次在 GitHub 上贡献代码,按下面的步骤就能快速开始。至于本项目期望的各项约定—— +分支命名、提交信息、提 PR 前要跑什么——参见[协作指南](./collaboration.md)和 +[开发指南](./develop-guideline.md)。 ## Fork 项目 -- 首先需要fork这个项目, 进入项目页面, 点击右上角的Fork按钮 -- 你的 github 帐号中会出现相应名称的项目, 例如: `/docs` -- 在本地电脑(Linux)上使用以下命令克隆项目到本地 +- 打开项目页面,点击右上角的 **Fork** +- 你的账号下会出现一份副本,例如 `/docs` +- 克隆到本地: ```bash git clone https://github.com//docs ``` -## 获取最新源代码 +## 关联上游仓库 -将本地个人仓库和上游仓库关联 +把本地克隆与上游仓库关联: ```bash -git remote add upstream https://github.com/kubedoop.dev/docs +git remote add upstream https://github.com/zncdatadev/docs ``` -同步最新源代码 +然后同步: ```bash git pull upstream main ``` -现在我们在 fork 来的 `main` 分支上, 这个 `main` 留作跟踪 `upstream` 的远程代码 +现在你在自己 fork 的 `main` 分支上,这个 `main` 留作跟踪 `upstream`,不要直接往上面提交。 ## 创建分支 -现在开始在本地开发,并准备贡献代码。 +请在分支上开发,而不是在 `main` 上。 -按照国际惯例,我们一般不在 `main` 分支上开发,而是创建一个新的分支,然后在新的分支上开发,开发完成后再合并到 `main` 分支。 +分支名要体现改动的类型: -首先明确我们要不贡献的代码是一个新的功能特性还是修复一个 bug 。如果是新增一个功能特性,需要创建一个基于 `feature/` 开头的 -分支,如果是修复一个 bug ,在创建一个基于 `fix/` 开头的分支。 +| 类型 | 格式 | 示例 | +|------|------|------| +| 新功能 | `feature/-` | `feature/kafka-rebalance` | +| Bug 修复 | `fix/-` | `fix/hdfs-memory-leak` | +| 文档 | `docs/` | `docs/add-trino-operator` | +| 重构 | `refactor/-` | `refactor/operator-go-api` | +| 杂项、依赖、CI | `chore/` | `chore/upgrade-k8s-0.36` | ```bash -git checkout -b fix/foo-error +git switch -c fix/foo-error ``` -然后在这个分支上进行代码开发,并在开发完成后提交代码。 +改完之后提交。提交信息遵循 +[Conventional Commits](https://www.conventionalcommits.org/): ```bash git commit -a -m "fix: foo error" ``` -## 合并修改 +## 推送前先 rebase -一个常见的问题是远程的 upstream (swoole/swoole-src) 有了新的更新, 从而会导致我们提交的 Pull Request 时会导致冲突, 因此我们可以在提交前先把远程其他开发者的commit和我们的commit合并. +在你开发期间,`upstream` 多半已经往前走了。先 rebase 到最新的 `main`, +可以让你的 PR 干净地应用上去,而不是带着冲突提交过来。 -首先我们需要切换到 `main` 分支, 然后同步最新的代码 +先更新 `main`: ```bash -git checkout main +git switch main git pull upstream main ``` -然后切换回我们的开发分支, 并合并 `main` 分支 +再把你的分支 rebase 到它上面: ```bash -git checkout fix/foo-error +git switch fix/foo-error git rebase main ``` -如果有冲突, 请解决冲突, 然后继续合并 +如果有冲突,解决后继续: ```bash git add . git rebase --continue ``` -最后提交代码 +然后推送到你的 fork: ```bash git push origin fix/foo-error ``` +如果这个分支在 rebase 之前已经推送过,这次推送会被拒绝,因为历史变了。 +对你自己的分支强制推送即可: + +```bash +git push --force-with-lease origin fix/foo-error +``` + ## 提交 Pull Request -在 Github 的项目中,切换到刚刚推送的分支,点击 `Pull Request` 按钮,填写相应的信息,然后提交 Pull Request。 +在 GitHub 上进入项目,切换到刚推送的分支,点击 **Pull Request**,填写说明。 + +提交之前先在本地跑一遍检查,免得 CI 挂在你本可以提前发现的问题上: + +```bash +npm run verify +``` + +所有 CI 检查通过、并获得一位 reviewer 批准后,PR 才能合并。 + +## 相关内容 + +- [协作指南](./collaboration.md) +- [开发指南](./develop-guideline.md) +- [文档编写规范](./document-guideline.md)