Skip to main content

Hooks and Custom Code

The code generator produces controller logic that handles the common patterns in AWS APIs — creating resources, reading their state, updating them, and deleting them. But AWS APIs are inconsistent, and many resources need custom logic that can't be expressed through declarative configuration alone.

Hooks let you inject Go code at specific points in the generated controller logic, bridging the gap between imperative AWS APIs and Kubernetes' declarative reconciliation model. Common uses include:

  • Setting fields that the code generator can't automatically map
  • Adding custom validation or defaulting logic
  • Implementing partial updates (only sending changed fields)
  • Setting custom status conditions based on resource state
  • Wiring in fields that live outside an API wrapper struct

How Hooks Work

Hooks are Go template files (.go.tpl) placed in templates/hooks/<resource>/ in the service controller repository. You reference them in generator.yaml:

resources:
BackupPlan:
hooks:
sdk_update_post_build_request:
template_path: hooks/backup_plan/sdk_update_post_build_request.go.tpl

Alternatively, for short snippets, use inline code:

resources:
User:
hooks:
sdk_create_post_set_output:
code: "rm.setSyncedCondition(resp.Status, &resource{ko})"

After adding or modifying hooks, run make build-controller to regenerate the controller code. Custom templates are picked up automatically.

Use renamed field names in hooks

If you renamed fields in generator.yaml, use the new names in your hook templates (e.g., r.ko.Spec.Name not r.ko.Spec.BackupVaultName).

Hook Variable Names

Different hook points provide different variable names for the resource. Using the wrong variable is a common source of bugs:

Hook pointResource variableNotes
sdk_create_post_build_requestdesiredInput resource
sdk_read_one_post_set_outputkoOutput resource being built
sdk_delete_post_build_requestrResource to delete
sdk_update_*desired, latestDesired and current state
Avoid latest in delete hooks

In sdkDelete, latest is the return variable (initialized to nil), not the input resource. Using it causes a nil pointer panic. Use r instead.

SDK Hook Points

These hooks inject code into the generated sdk.go file at specific points in the CRUD lifecycle. As a developer, you care about these when the auto-generated SDK call needs adjustment — for example, when you need to set a field that the code generator missed, or when you need to transform the API response before it's stored in the CR.

Create hooks

HookWhen it runs
sdk_create_pre_build_requestBefore building the Create API input
sdk_create_post_build_requestAfter building the Create API input, before calling AWS
sdk_create_post_set_outputAfter setting output fields from the Create response

Read hooks

HookWhen it runs
sdk_read_one_post_set_outputAfter setting output fields from the Describe/Get response
sdk_read_many_post_set_outputAfter setting output fields from a List response

Update hooks

HookWhen it runs
sdk_update_pre_build_requestBefore building the Update API input
sdk_update_post_build_requestAfter building the Update API input, before calling AWS
sdk_update_post_set_outputAfter setting output fields from the Update response

Delete hooks

HookWhen it runs
sdk_delete_pre_build_requestBefore building the Delete API input
sdk_delete_post_build_requestAfter building the Delete API input, before calling AWS

File-level hooks

HookWhen it runs
sdk_file_endAppended to the end of sdk.go (useful for helper functions)

Comparison Hook Points

When the controller reconciles a resource, it compares the desired state (from the CR's Spec) with the latest observed state (from the AWS API). The result is a Delta struct listing which fields differ. Sometimes the auto-generated comparison produces false positives — for example, when AWS returns a field in a different format than the user specified, or when an unordered list causes reflect.DeepEqual to fail non-deterministically.

Comparison hooks let you filter or adjust the delta:

HookWhen it runs
delta_pre_compareBefore the auto-generated field comparisons
delta_post_compareAfter the auto-generated field comparisons

Example — filtering out a non-meaningful difference:

resources:
User:
hooks:
delta_post_compare:
code: "filterDelta(delta, a, b)"

Late Initialization Hook Points

Late initialization handles fields where AWS assigns a default value after resource creation (e.g., RDS choosing an availability zone when the user didn't specify one). The controller needs to recognize that the nil desired value and the AWS-assigned value aren't actually a conflict.

HookWhen it runs
late_initialize_pre_read_oneBefore reading the resource during late initialization
late_initialize_post_read_oneAfter reading the resource during late initialization

Common Hook Patterns

Setting fields outside a wrapper

When using input_wrapper_field_path, fields outside the wrapper need manual wiring:

// templates/hooks/backup_plan/sdk_update_post_build_request.go.tpl
if desired.ko.Status.BackupPlanID != nil {
input.BackupPlanId = desired.ko.Status.BackupPlanID
}

Custom synced condition

Setting ACK.ResourceSynced based on the resource's AWS status:

if resp.Status != nil && *resp.Status == "active" {
syncedCondition.Status = corev1.ConditionTrue
} else {
syncedCondition.Status = corev1.ConditionFalse
}

Custom update logic

When the Update API requires only changed fields (not the full spec), use a hook to build a selective payload from the delta:

resources:
User:
hooks:
sdk_update_post_build_request:
code: "rm.populateUpdatePayload(input, desired, delta)"

Next Steps

Built with by AWS