Skill: Add Missing Field
This skill guides an automated agent through adding a missing field to a GCP resource managed by KCC using the "direct" controller approach.
Pre-requisites
- Know the KCC API Group and Resource name.
- Identify the missing field from the issue description or fuzzer output (e.g.
f.Unimplemented_NotYetTriaged(".cross_instance_replication_config")).
Steps
Add the field to the
_types.gofile- Locate the
_types.gofile for the resource (e.g.,apis/group/version/resource_types.go). - Add the field to the
Spec(for inputs) orObservedState(for outputs) struct as appropriate. - If the type relies on autogeneration, you can use
types.generated.goas a reference. Note thattypes.generated.goincludes generated code for all types if--include-skipped-outputis passed to the generate commands. - Important: If the field represents a GCP URI or URI fragment (e.g., the path to another resource), you should use a KCC reference field (e.g.,
InstanceRef *refs.MemorystoreInstanceRef) instead of a simple string. - Follow KRM naming conventions (camelCase in Go, etc.).
- Locate the
Update the Fuzzer
- Find the fuzzer file (e.g.,
pkg/controller/direct/group/resource_fuzzer.go). - Remove the missing field from
f.UnimplementedFieldsorf.Unimplemented_NotYetTriaged. - Register the field as a spec or status field in the fuzzer (e.g.,
f.SpecField(".cross_instance_replication_config")orf.StatusField(".psc_attachment_details")). - If there are subfields you aren't implementing yet, use
f.Unimplemented_NotYetTriagedto ignore them for now.
- Find the fuzzer file (e.g.,
Update the Mappers
- If the resource uses hand-written mappers (e.g.,
mapper.go), add the appropriate logic to convert between the Kubernetes resource (KRM) and the GCP API (API). - Note that if the root
SpecorObservedStatehas a handwritten mapping function (e.g.,_FromProtoand_ToProto), you'll need to manually add the mapping for the new field there, even if it's just a top-level field. - Check
mapper.generated.gofor blocks starting with/* found existing non-generated mapping function ... */. If the generator skipped a parent mapper, it may also skip nested types and leave comments like// MISSING: <Type>. You will need to manually write_FromProtoand_ToProtofunctions for these missing nested types. - Important: We almost always want to update the
ToProtoandFromProtomethods "symmetrically", so that they round trip. If you map a field inSpec_ToProto, make sure to also map it inSpec_FromProto. - Again,
mapper.generated.gocan provide a good reference ifgenerate.sh --include-skipped-outputis used.
- If the resource uses hand-written mappers (e.g.,
Run
generate.sh- Run the code generator script (e.g.,
./apis/group/vX/generate.shor the globaldev/tasks/generate-types-and-mappers) to regenerate CRDs and other boilerplate.
- Run the code generator script (e.g.,
4b. Verify Fuzzer with Focused Tests
- To quickly verify that your mappers and fuzzer mapping logic are correct and round-trip successfully, run focused fuzzer tests using the
FOCUSenvironment variable:FOCUS=MyResourceKind go test -v ./pkg/fuzztesting/fuzztests/... -run TestFocusedMappers(For example:FOCUS=ComputeNetwork go test -v ./pkg/fuzztesting/fuzztests/... -run TestFocusedMappers)
- Create a Test
- Find existing tests under
pkg/test/resourcefixture/testdata/basic/group/version/kind/. - If the new field can be added to an existing test without conflict, do so.
- If the new field requires a specific setup (e.g., cross-instance replication requires two instances), create a new test folder with
create.yaml(anddependencies.yamlif needed).
- Find existing tests under
5b. Remove from Ratcheting Exclusions (MANDATORY)
- Before running the test cases against real or mock GCP, you MUST ensure the target resource is removed from the ratcheting exclusion list in
tests/e2e/ratcheting.go. This enables the re-reconciliation test step, which is a fundamental use case KCC resources must support.
Open
tests/e2e/ratcheting.go.Locate the function
ShouldTestRereconiliation.Locate the
switchstatement that checksprimaryResource.GroupVersionKind().If there is a
caseblock for your target resource'sGroupKind, remove thatcaseline from the switch statement.Regenerate Golden Output
- Run the mock compare script to generate new golden output (you must set
WRITE_GOLDEN_OUTPUT=1):WRITE_GOLDEN_OUTPUT=1 hack/compare-mock pkg/test/resourcefixture/testdata/basic/group/version/kind/[testname]/ - Note that test paths may use the full kind name (e.g.,
memorystoreinstanceinstead ofinstance). - Also note that the test will still report a failure if the golden files were modified (this is expected behavior to highlight the diff).
- Review the generated
_http.logand_generated_object...golden.yamlfor correctness.
- Run the mock compare script to generate new golden output (you must set
Update mockgcp (If Needed)
- If the tests fail because mockgcp doesn't support the field, you might need to implement a stub in mockgcp.
- This stub just needs to behave reasonably so tests pass; full realgcp parity will be checked in separate E2E testing.
Run Pre-PR Checks
- Please run
make ready-pr.
- Please run
Reference Mapping Tips
- Use capital KMS: If you add a reference like
KMSKeyRef, naming it all-capsKMSKeyRef *refs.KMSCryptoKeyRefinstead ofKmsKeyRefallows the controller builder to automatically identify it as a reference field and generate its mapping logic. - Annotate reference fields: Always add the
// +kcc:proto:field=<GCP_PROTO_PATH>annotation to reference fields in_types.go(e.g.// +kcc:proto:field=google.cloud.redis.cluster.v1.Cluster.kms_key) so the generator knows exactly how to map it to the underlying protobuf schema. - Pointers in Proto Binding: In proto3, optional string fields generated by protoc map to
*stringGo pointers. Since KCC's generator has a hardcoded list of services that use pointers (usesPointersInProtoBinding), if the service you are updating is not on that list, the generator will attempt to map the reference field as a direct string value (resulting inout.KmsKey = in.KMSKeyRef.External, which fails to compile because it expects a*string). To resolve this, you must write a custom handwritten spec mapper (Spec_ToProtoandSpec_FromProto) insidecluster_mapper.goor similar to handle the pointer assignment (out.KmsKey = &in.KMSKeyRef.External) correctly. - Resolve references in Controller: Remember to call
refs.ResolveKMSCryptoKeyRefinside the controller'sAdapterForObjectmethod (before mapping to proto) to fully resolve the reference to its external GCP URI.
- Use capital KMS: If you add a reference like