Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions .claude/skills/add-k8s-resource/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
---
name: add-k8s-resource
description: Scaffold a new Kubernetes CRD + kubebuilder controller + RBAC + helm chart entry for the vmafx-operator. Follows the VmafxJob / VmafxNode / VmafxModelTraining precedent in cmd/vmafx-operator/ (ADR-0714, ADR-0709 parent).
description: Scaffold a new Kubernetes CRD + kubebuilder controller + RBAC + helm chart entry for the vmafx-operator. The types and CRD come from api/vmafx-platform.toml (ADR-2350 D13); follows the VmafxJob / VmafxNode / VmafxModelTraining precedent in cmd/vmafx-operator/ (ADR-0714, ADR-0709 parent).
---
<!-- markdownlint-disable MD013 -->

# /add-k8s-resource

Adds new CRD under `vmafx.dev` API group.
Appends resource + spec/status stub to `api/vmafx-platform.toml`; generators
write Go types, deepcopy, CRD YAML (`deploy/helm/vmafx/crds/`, only CRD tree)
and `config/rbac/role.yaml` (ADR-2350 D13).
Generates kubebuilder-style controller stub.
Wires stub into vmafx-operator manager.
Ships matching CRD YAML in helm chart `crds/` directory.
Adds RBAC rules.
Exposes values.yaml toggle.
Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in
Expand All @@ -19,8 +21,10 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in

- Add new CRD reconciled by vmafx-operator (e.g. `VmafxBenchmarkRun`,
`VmafxCorpusSync`, `VmafxModelDeployment`).
- NOT for adding new sub-resource or field to existing CRD -> schema migration;
use `controller-gen` directly, follow CRD-versioning ADRs.
- NOT for adding field to existing CRD -> edit its `[[messages]]` entry in
`api/vmafx-platform.toml`, regenerate
([API generation](../../../docs/development/api-generation.md#change-a-custom-resource));
within v1 resource only grows (`test_crd_compat`).
- NOT for adding non-CRD Kubernetes resource (Deployment, Service, etc.) -> goes
directly into `deploy/helm/vmafx/templates/`.

Expand All @@ -38,10 +42,9 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in

| Path | Purpose |
|---------------------------------------------------------------------------------------|----------------------------------------------------|
| `api/vmafx/v1/vmafx<kind>_types.go` | Go types (Spec, Status, list type) |
| `api/vmafx-platform.toml` (appended) | `[[resources]]` + spec / status `[[messages]]` |
| `cmd/vmafx-operator/internal/controller/vmafx<kind>_controller.go` | Controller reconciler stub |
| `cmd/vmafx-operator/internal/controller/vmafx<kind>_controller_test.go` | envtest-style controller smoke test |
| `deploy/helm/vmafx/crds/vmafx.dev_vmafx<plural>.yaml` | CRD manifest (controller-gen output) |
| `deploy/helm/vmafx/templates/operator-rbac-<kind>.yaml` | Per-kind ClusterRole rule additions |
| `docs/k8s/crds/vmafx<kind>.md` | Human-readable CRD reference |
| `changelog.d/added/k8s-crd-vmafx<kind>.md` | Changelog fragment |
Expand All @@ -59,7 +62,8 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in
## Workflow

1. Validate `<KindName>` matches `^[A-Z][A-Za-z0-9]+$`, not already present
(grep `api/vmafx/v1/`, `deploy/helm/vmafx/crds/`).
(`kind = "Vmafx<KindName>"` in `api/vmafx-platform.toml`, no
`api/vmafx/v1/` or `deploy/helm/vmafx/crds/` file).
2. Compute derived names:
- `kind` = `Vmafx<KindName>` (Go type, CRD kind).
- `kind_lower` = lowercased (file paths).
Expand All @@ -68,9 +72,11 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in
- `short` = `vm<first-4-chars-of-kind>` (e.g. `vmbench`).
3. Copy templates with placeholder substitution (`@KIND@`, `@KIND_LOWER@`,
`@PLURAL@`, `@SHORT@`, `@COPYRIGHT@`).
4. Apply patches to `main.go`, `values.yaml`, `operator.md`, `AGENTS.md`.
5. Regenerate CRD bundle: `make manifests` (delegates to `controller-gen`) ->
keeps helm `crds/` YAML byte-identical to kubebuilder output.
4. Fill spec / status fields of the stub in `api/vmafx-platform.toml`, then
generate types, deepcopy, CRD, RBAC role:
`python3 scripts/codegen/vmafx-api.py --write` then
`python3 scripts/codegen/crd_generate.py --write`. Never hand-edit output.
5. Apply patches to `main.go`, `values.yaml`, `operator.md`, `AGENTS.md`.
6. Run `go build ./cmd/vmafx-operator/...` -> confirm manager compiles.
7. Run `go test ./cmd/vmafx-operator/...` -> confirm new controller test passes
(stub reconcile only -> returns success without side effects).
Expand All @@ -81,14 +87,15 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in
(`get,list,watch,update,patch` by default; no `delete` without
justification).
- Helm chart smoke (`helm template deploy/helm/vmafx | yq` -> verify new CRD
lands).
lands); `scripts/ci/tests/test_helm_operator_rbac.py` -> chart grants
every rule of generated `config/rbac/role.yaml`.

## Guardrails

- **Never** activate controller by default. `values.yaml` ships `enabled: false`
(users opt in per cluster -> matches Stage 1 posture in ADR-0714).
- **Never** add CRD without helm `crds/` YAML -> operators installing via helm
rely on chart `crds/` directory pre-creation.
- **Never** hand-write types, deepcopy or CRD YAML -> generated from
`api/vmafx-platform.toml`; `test_crd_generated_current` fails on drift.
- **Never** add `delete` or `*` verbs to RBAC without ADR justifying it. CRDs
operator owns default to `get,list,watch,update,patch` plus `create` only when
controller materialises sub-resources.
Expand All @@ -107,4 +114,8 @@ Follows conventions from `VmafxJob`, `VmafxNode`, `VmafxModelTraining` in
—
reference controllers (Node, Job, ModelTraining)
- [`deploy/helm/vmafx/crds/`](../../../deploy/helm/vmafx/crds/) — shipped CRD
manifests
manifests (generated)
- [ADR-2350](../../../docs/adr/2350-cloud-native-platform.md) D13 — platform
definition generates types, CRDs, RBAC role
- [API generation](../../../docs/development/api-generation.md#kubernetes-resources)
— definition tables, regeneration, gates
47 changes: 30 additions & 17 deletions .claude/skills/add-k8s-resource/scaffold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
#
# scaffold.sh — materialize the add-k8s-resource templates for a named CRD.
#
# The resource's types and CRD are generated (ADR-2350 D13): the scaffold
# appends a stub to api/vmafx-platform.toml; scripts/codegen/vmafx-api.py and
# scripts/codegen/crd_generate.py then write the Go types, the deepcopy code,
# the CRD under deploy/helm/vmafx/crds/ and config/rbac/role.yaml.
#
# Usage: bash .claude/skills/add-k8s-resource/scaffold.sh <KindName>
# where <KindName> is PascalCase WITHOUT the Vmafx prefix.
#
Expand Down Expand Up @@ -41,18 +46,21 @@ short="vm${short_suffix}"
repo_root=$(git rev-parse --show-toplevel)
tpl="$repo_root/.claude/skills/add-k8s-resource/templates"

api_dir="$repo_root/api/vmafx/v1"
platform="$repo_root/api/vmafx-platform.toml"
ctl_dir="$repo_root/cmd/vmafx-operator/internal/controller"
crd_dir="$repo_root/deploy/helm/vmafx/crds"
rbac_dir="$repo_root/deploy/helm/vmafx/templates"
doc_dir="$repo_root/docs/k8s/crds"
changelog_dir="$repo_root/changelog.d/added"

if grep -q "^kind = \"${kind}\"\$" "$platform"; then
echo "error: ${kind} is already a resource in $platform" >&2
exit 3
fi
for f in \
"$api_dir/${kind_lower}_types.go" \
"$repo_root/api/vmafx/v1/${kind_lower}_types.go" \
"$ctl_dir/${kind_lower}_controller.go" \
"$ctl_dir/${kind_lower}_controller_test.go" \
"$crd_dir/vmafx.dev_${plural}.yaml" \
"$repo_root/deploy/helm/vmafx/crds/vmafx.dev_${plural}.yaml" \
"$rbac_dir/operator-rbac-${kind_lower}.yaml" \
"$doc_dir/${kind_lower}.md" \
"$changelog_dir/k8s-crd-${kind_lower}.md"; do
Expand All @@ -62,7 +70,7 @@ for f in \
fi
done

mkdir -p "$api_dir" "$ctl_dir" "$crd_dir" "$rbac_dir" "$doc_dir" "$changelog_dir"
mkdir -p "$ctl_dir" "$rbac_dir" "$doc_dir" "$changelog_dir"

subst() {
sed -e "s/@KIND@/$kind/g" \
Expand All @@ -72,10 +80,9 @@ subst() {
"$@"
}

subst "$tpl/types.go.template" >"$api_dir/${kind_lower}_types.go"
subst "$tpl/platform.toml.template" >>"$platform"
subst "$tpl/controller.go.template" >"$ctl_dir/${kind_lower}_controller.go"
subst "$tpl/controller_test.go.template" >"$ctl_dir/${kind_lower}_controller_test.go"
subst "$tpl/crd.yaml.template" >"$crd_dir/vmafx.dev_${plural}.yaml"
subst "$tpl/rbac.yaml.template" >"$rbac_dir/operator-rbac-${kind_lower}.yaml"
subst "$tpl/doc.md.template" >"$doc_dir/${kind_lower}.md"

Expand All @@ -84,19 +91,25 @@ cat >"$changelog_dir/k8s-crd-${kind_lower}.md" <<EOF
EOF

echo "scaffolded CRD '$kind':"
echo " types : $api_dir/${kind_lower}_types.go"
echo " definition : $platform (resource, spec and status appended)"
echo " controller : $ctl_dir/${kind_lower}_controller.go (+ test)"
echo " crd manifest: $crd_dir/vmafx.dev_${plural}.yaml"
echo " rbac : $rbac_dir/operator-rbac-${kind_lower}.yaml"
echo " doc : $doc_dir/${kind_lower}.md"
echo " changelog : $changelog_dir/k8s-crd-${kind_lower}.md"
echo
echo "next steps:"
echo " 1. wire SetupWithManager into cmd/vmafx-operator/main.go"
echo " 2. extend the --enable-controllers flag whitelist in main.go"
echo " 3. add operator.controllers.${kind_lower}.enabled=false to deploy/helm/vmafx/values.yaml"
echo " 4. append a row to docs/development/operator.md controller table"
echo " 5. note ${kind} in cmd/vmafx-operator/AGENTS.md 'controllers shipped' invariant"
echo " 6. fill the TODO blocks in types.go / controller.go / crd.yaml / doc.md"
echo " 7. run: make manifests && go build ./cmd/vmafx-operator/... && go test ./cmd/vmafx-operator/..."
echo " 8. verify helm renders: helm template deploy/helm/vmafx --set operator.enabled=true --set operator.controllers.${kind_lower}.enabled=true | grep -A2 '${kind}'"
echo " 1. fill the TODO entries of ${kind} in api/vmafx-platform.toml, then generate the"
echo " types, deepcopy, CRD and RBAC role:"
echo " python3 scripts/codegen/vmafx-api.py --write"
echo " python3 scripts/codegen/crd_generate.py --write"
echo " 2. wire SetupWithManager into cmd/vmafx-operator/main.go"
echo " 3. extend the --enable-controllers flag whitelist in main.go"
echo " 4. add operator.controllers.${kind_lower}.enabled=false to deploy/helm/vmafx/values.yaml"
echo " 5. append a row to docs/development/operator.md controller table"
echo " 6. note ${kind} in cmd/vmafx-operator/AGENTS.md 'controllers shipped' invariant"
echo " 7. fill the TODO blocks in controller.go / doc.md"
echo " 8. run: go build ./cmd/vmafx-operator/... && go test ./cmd/vmafx-operator/..."
echo " and python3 scripts/codegen/crd_generate.py --check --compat-against origin/master"
echo " 9. verify helm renders and grants the generated role:"
echo " helm template deploy/helm/vmafx --set operator.enabled=true --set operator.controllers.${kind_lower}.enabled=true | grep -A2 '${kind}'"
echo " python3 -m unittest discover -s scripts/ci/tests -p test_helm_operator_rbac.py"
62 changes: 0 additions & 62 deletions .claude/skills/add-k8s-resource/templates/crd.yaml.template

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# @KIND@ (scaffolded by /add-k8s-resource). Describe the resource, add the
# spec and status fields, then run scripts/codegen/vmafx-api.py --write and
# scripts/codegen/crd_generate.py --write.
[[resources]]
kind = "@KIND@"
group = "vmafx"
doc = "@KIND@ is the Schema for the @PLURAL@ API. TODO(@KIND@): say what one object stands for."
short_names = ["@SHORT@"]
spec = "@KIND@Spec"
status = "@KIND@Status"
printer_columns = [
{ name = "Age", type = "date", json_path = ".metadata.creationTimestamp" },
]

[[messages]]
name = "@KIND@Spec"
group = "vmafx"
doc = "@KIND@Spec defines the desired state of a @KIND@."
fields = [
# TODO(@KIND@): the desired-state fields, e.g.
# { name = "target", type = "string", min_length = 1, doc = "Target is ..." },
]

[[messages]]
name = "@KIND@Status"
group = "vmafx"
doc = "@KIND@Status defines the observed state of a @KIND@."
fields = [
{ name = "observedGeneration", type = "int64", optional = true, doc = "ObservedGeneration is the metadata.generation this status describes." },
]
99 changes: 0 additions & 99 deletions .claude/skills/add-k8s-resource/templates/types.go.template

This file was deleted.

Loading
Loading