- name
- cr-field
- description
- Use when adding a new field to an existing Custom Resource. Guides the full workflow: API types, code generation, CRD examples, client investigation, handler mapping, and tests.
- allowed-tools
- Bash(make *), Read, Grep, Glob
## Your task
Add a new field to an existing Custom Resource following the project's established conventions.
Complete every step. The order matters: Step 2 generates code from the types added in Step 1, and Step 5 maps onto the representation type found in Step 4.
---
## Step 1 — Add field to API types
File: `api/v1/{kind}_types.go` (or `api/v1alpha1/` for alpha resources).
Add the struct field with a godoc comment and kubebuilder markers. Choose markers that match the field's semantics:
- `// +required` / `// +optional`
- `// +nullable` — for pointer slices or maps that can be explicitly null
- `// +kubebuilder:validation:Enum=val1;val2` — restrict to allowed values
- `// +kubebuilder:default=value` — set a default when field is omitted
- `// +kubebuilder:example=value` — example shown in generated docs
- `// +kubebuilder:validation:XValidation:rule=...,message=...` — CEL validation (e.g. immutability)
JSON tag: `json:"fieldName,omitempty"` for optional fields, `json:"fieldName"` for required.
See `api/v1/keycloakclient_types.go` for the full variety of marker patterns in use.
---
## Step 2 — Run code generation
```
make generate && make manifests
```
This regenerates DeepCopy methods and CRD YAMLs in `config/crd/bases/` and `deploy-templates/crds/`.
---
## Step 3 — Update CRD examples
Add the new field with a meaningful example value to:
- `config/samples/v1_v1_{kind}.yaml` (`v1_v1alpha1_{kind}.yaml` for alpha kinds)
- `deploy-templates/_crd_examples/{kind}.yaml`
---
## Step 4 — Identify the client and find the Keycloak representation type
Do this before touching handler code: the generated client may not expose the field, and Step 5 maps onto the representation type found here.
### 4a. Confirm the controller uses keycloakapi
All controllers use `pkg/client/keycloakapi/`. Confirm the handler imports this package.
### 4b. Find the Keycloak representation struct
Check `pkg/client/keycloakapi/contracts.go` for the relevant client interface
(`GroupsClient`, `ClientsClient`, `RolesClient`, etc.).
Representation types are type aliases — grep `pkg/client/keycloakapi/generated/client_generated.go`
for the struct name (e.g. `GroupRepresentation`, `ClientRepresentation`, `RoleRepresentation`)
to see which fields are available.
### 4c. Confirm the field exists — then follow this decision tree
- **Field exists in keycloakapi representation** → proceed to Step 5.
- **Field missing from keycloakapi representation** → stop and report it. The spec `pkg/client/keycloakapi/openapi/openapi.yaml` is downloaded from the Keycloak release pinned by `KEYCLOAK_VERSION` in the Makefile, so the field needs a newer release: bump the version and run `make generate-keycloak-go-client`. That is a separate change; ask the user before going further.
---
## Step 5 — Map the field in the chain handler
In `internal/controller/{resource}/chain/` find the handler that creates/updates the resource
(usually `create_or_update_{resource}.go` or `put_{resource}.go`).
Map the field in **both** paths:
- **Create path**: include the field when building the representation struct before the Create call.
- **Update path**: assign the field on the fetched existing representation before the Update call.
Reference: `internal/controller/keycloakrealmgroup/chain/create_or_update_group.go`
**Destination guard.** If the field holds a remote address (host or URL), call
`guard.RequireHost` on it before any Secret tied to it is resolved — inject the guard and
copy the pattern from `internal/controller/keycloakrealm/chain/configure_email.go`.
---
## Step 6 — Update unit tests
File: `internal/controller/{resource}/chain/*_test.go`
Add the field to the spec setup and to the mock expectations for all test cases:
create path, update path, and error paths.
Reference: `internal/controller/keycloakrealmgroup/chain/create_or_update_group_test.go`
---
## Step 7 — Update integration tests
File: `internal/controller/{resource}/*_controller_integration_test.go`
Add the field to the CR creation spec. Assert the correct value is persisted in Keycloak
using `Eventually()` + `g.Expect()`.
Reference: `internal/controller/keycloakrealmgroup/keycloakrealmgroup_controller_integration_test.go`
---
## Step 8 — Validate
Invoke the `run-golangci-lint` skill, then the `run-tests` skill.
GitHub에서 보기