KCC Direct Service-Generated Resource IDs
This skill outlines the mandatory implementation requirements for direct KCC resources whose unique identifiers (IDs) are exclusively assigned/generated by the Google Cloud Platform (GCP) service itself (i.e., resources that ONLY support service-generated IDs).
Note: Resources that support both user-specified and service-generated IDs (or have alternative selection mechanisms) should be handled differently and are not covered by this skill.
For such resources, the Kubernetes configuration uses spec.resourceID to specify a service-generated ID (if acquiring or targeting a pre-existing resource), but can also leave it empty to request that KCC let the GCP service assign an ID.
The 4 Rules for Service-Generated Resource IDs
When implementing or migrating direct controllers for resources that use service-assigned or service-generated identifiers, you must strictly follow these four architectural rules:
Rule 1: Identity Construction (getIdentityFromSpec)
- Retrieve the resource ID using
GetServiceGeneratedResourceID(obj)(orcommon.ValueOf(obj.Spec.ResourceID)). - MUST NEVER fall back to using
metadata.nameas a default resource ID. - If
spec.resourceIDis empty or unset, the parsedspecIdentity.<ResourceID>MUST be set to"".
Rule 2: Find() Pre-Check Guard
- The first line of the
Find()method MUST check if the parsed identifier is empty:if a.id.<ResourceID> == "" { return false, nil } - This guard returns
false, nilimmediately, preventing invalid GET requests to GCP prior to resource creation. - NotFound Error Clarification: Standard NotFound behavior applies: if the identifier is not empty but the GCP service returns a NotFound/404 error during the standard GET call,
Find()should returnfalse, nilas usual. There should be no special service-generated ID handling for the NotFound error itself.
Rule 3: Create() Request Payload
- The
Create()implementation MUST NEVER set thedesiredpb.Name(or equivalent identifier field) in the GCP request payload before calling the GCP Create API. The identifier must be left empty so that the GCP service knows to generate it.
Rule 4: Identity Comparison in GetIdentity()
When status.externalRef is present during reconciliation:
- If
GetServiceGeneratedResourceID(obj) == ""(Empty):- Do NOT compare
specIdentity.<ResourceID>("") againststatusIdentity.<ResourceID>(e.g.,"12345"). - Verify that immutable parent fields (such as
ProjectandLocation) match. - Return
statusIdentity(which carries the server-assigned ID).
- Do NOT compare
- If
GetServiceGeneratedResourceID(obj) != ""(Non-Empty):specIdentityMUST equalstatusIdentity(i.e.specIdentity.<ResourceID> == statusIdentity.<ResourceID>).- If
specIdentity.<ResourceID> != statusIdentity.<ResourceID>, return an error:cannot change resource identity (old=%q, new=%q)
Coding Reference Example
Below is a reference example demonstrating how these rules are mapped into typical direct controller adapter Go code.
package service
import (
"context"
"fmt"
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
)
// Rule 1: Identity Construction
func GetServiceGeneratedResourceID(obj *krm.MyResource) string {
return common.ValueOf(obj.Spec.ResourceID)
}
func (a *Adapter) Find(ctx context.Context) (bool, error) {
// Rule 2: Find() Pre-Check Guard
if a.id.<ResourceID> == "" {
return false, nil
}
// Normal Find logic (e.g., standard GET call)
// No special service-generated ID handling is needed for NotFound/404 errors.
// ...
return true, nil
}
func (a *Adapter) Create(ctx context.Context, desired *krm.MyResource) error {
// Rule 3: Create() Request Payload
// Do not set desiredpb.Name, let GCP generate the identifier.
// ...
return nil
}
// Rule 4: Identity Comparison in GetIdentity()
func GetIdentity(ctx context.Context, obj *krm.MyResource, statusIdentity *Identity) (*Identity, error) {
specID := GetServiceGeneratedResourceID(obj)
if specID == "" {
// Verify that immutable parent fields match (e.g., statusIdentity.Parent == specIdentity.Parent)
// ...
return statusIdentity, nil
}
if specID != statusIdentity.<ResourceID> {
return nil, fmt.Errorf("cannot change resource identity (old=%q, new=%q)", statusIdentity.<ResourceID>, specID)
}
// Return identity mapped from spec
return specIdentity, nil
}