From d822add591aa1f6359a9de75fabaef115d890307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86gir=20M=C3=A1ni=20Hauksson?= <54936225+sourcehawk@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:08:26 +0200 Subject: [PATCH] docs(primitives): document declared data extraction for remaining kinds PR #165 added ExtractInto to every primitive package but documented it in only 11 of the 22 per-kind pages. Add a Data Extraction section to the remaining kinds, matching the structure used by the pages updated in #165: the kind-specific ExtractInto signature with a realistic extraction target, and the WithDataGuard / WithOptionalData read declarations on the builder. Every snippet was compile checked against the primitive packages. Regenerated the plugin skill references with make sync-plugin. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PhRYyAS9tcQcg6iZXMoVfd --- docs/primitives/configmap.md | 20 ++++++++++++++++ docs/primitives/cronjob.md | 23 +++++++++++++++++++ docs/primitives/daemonset.md | 19 +++++++++++++++ docs/primitives/deployment.md | 19 +++++++++++++++ docs/primitives/hpa.md | 20 ++++++++++++++++ docs/primitives/job.md | 22 ++++++++++++++++++ docs/primitives/pod.md | 19 +++++++++++++++ docs/primitives/pv.md | 22 ++++++++++++++++++ docs/primitives/replicaset.md | 19 +++++++++++++++ docs/primitives/secret.md | 22 ++++++++++++++++++ docs/primitives/statefulset.md | 20 ++++++++++++++++ .../references/primitives/configmap.md | 20 ++++++++++++++++ .../references/primitives/cronjob.md | 23 +++++++++++++++++++ .../references/primitives/daemonset.md | 19 +++++++++++++++ .../references/primitives/deployment.md | 19 +++++++++++++++ .../references/primitives/hpa.md | 20 ++++++++++++++++ .../references/primitives/job.md | 22 ++++++++++++++++++ .../references/primitives/pod.md | 19 +++++++++++++++ .../references/primitives/pv.md | 22 ++++++++++++++++++ .../references/primitives/replicaset.md | 19 +++++++++++++++ .../references/primitives/secret.md | 22 ++++++++++++++++++ .../references/primitives/statefulset.md | 20 ++++++++++++++++ 22 files changed, 450 insertions(+) diff --git a/docs/primitives/configmap.md b/docs/primitives/configmap.md index 0b38fde1..89889c2c 100644 --- a/docs/primitives/configmap.md +++ b/docs/primitives/configmap.md @@ -266,6 +266,26 @@ func ChecksumAnnotationMutation(version, configHash string) deployment.Mutation When the ConfigMap mutations change (version upgrade, feature toggle), `DesiredHash` returns a different value on the same reconcile cycle, the pod template annotation changes, and Kubernetes triggers a rolling restart. +## Data Extraction + +`configmap.ExtractInto` declares that this ConfigMap produces the value of a data cell, which is how a rendered +configuration value reaches the resources that consume it. The function receives a value copy of the reconciled +ConfigMap after each sync cycle: + +```go +bootstrapServers := concepts.NewData[string]("bootstrap-servers") + +builder := configmap.NewBuilder(base) +configmap.ExtractInto(builder, bootstrapServers, func(cm corev1.ConfigMap) (string, error) { + return cm.Data["bootstrap-servers"], nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(bootstrapServers)` or read it +opportunistically with `WithOptionalData(bootstrapServers)`. See [Declared Data](../component.md#declared-data). + ## Full Example ```go diff --git a/docs/primitives/cronjob.md b/docs/primitives/cronjob.md index 8dab0ea0..742dd6df 100644 --- a/docs/primitives/cronjob.md +++ b/docs/primitives/cronjob.md @@ -194,6 +194,29 @@ a plain function accepting `*cronjob.Mutator` and call it directly. See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`cronjob.ExtractInto` declares that this CronJob produces the value of a data cell, which is how scheduling state +observed on the object reaches the resources that consume it. The function receives a value copy of the reconciled +CronJob after each sync cycle: + +```go +lastSchedule := concepts.NewData[metav1.Time]("cleanup-last-schedule") + +builder := cronjob.NewBuilder(base) +cronjob.ExtractInto(builder, lastSchedule, func(cj batchv1.CronJob) (metav1.Time, error) { + if cj.Status.LastScheduleTime == nil { + return metav1.Time{}, nil + } + return *cj.Status.LastScheduleTime, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(lastSchedule)` or read it +opportunistically with `WithOptionalData(lastSchedule)`. See [Declared Data](../component.md#declared-data). + ## Operational Status `DefaultOperationalStatusHandler` always reports `Operational`. A CronJob is a passive scheduler: once it exists in the diff --git a/docs/primitives/daemonset.md b/docs/primitives/daemonset.md index b2abf38e..bd482d97 100644 --- a/docs/primitives/daemonset.md +++ b/docs/primitives/daemonset.md @@ -195,6 +195,25 @@ agent.WithMutation(daemonset.LiftMutation(sharedAuthMutation())) See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the full pattern. +## Data Extraction + +`daemonset.ExtractInto` declares that this DaemonSet produces the value of a data cell, such as how many nodes are +running a ready pod. The function receives a value copy of the reconciled DaemonSet after each sync cycle: + +```go +readyNodes := concepts.NewData[int32]("collector-ready-nodes") + +builder := daemonset.NewBuilder(base) +daemonset.ExtractInto(builder, readyNodes, func(ds appsv1.DaemonSet) (int32, error) { + return ds.Status.NumberReady, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(readyNodes)` or read it +opportunistically with `WithOptionalData(readyNodes)`. See [Declared Data](../component.md#declared-data). + ## Suspension DaemonSets have no replicas field, so there is no clean in-place pause mechanism. By default, the DaemonSet is diff --git a/docs/primitives/deployment.md b/docs/primitives/deployment.md index 8dbf06b1..4b229ac1 100644 --- a/docs/primitives/deployment.md +++ b/docs/primitives/deployment.md @@ -201,6 +201,25 @@ frontend.WithMutation(deployment.LiftMutation(sharedAuthMutation())) See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the full pattern. +## Data Extraction + +`deployment.ExtractInto` declares that this Deployment produces the value of a data cell, such as the revision the +rollout landed on. The function receives a value copy of the reconciled Deployment after each sync cycle: + +```go +revision := concepts.NewData[string]("web-server-revision") + +builder := deployment.NewBuilder(base) +deployment.ExtractInto(builder, revision, func(d appsv1.Deployment) (string, error) { + return d.Annotations["deployment.kubernetes.io/revision"], nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(revision)` or read it +opportunistically with `WithOptionalData(revision)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the Deployment is scaled to zero replicas. The resource is not deleted. diff --git a/docs/primitives/hpa.md b/docs/primitives/hpa.md index 1a26473a..85133ad2 100644 --- a/docs/primitives/hpa.md +++ b/docs/primitives/hpa.md @@ -175,6 +175,26 @@ m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error { }) ``` +## Data Extraction + +`hpa.ExtractInto` declares that this HPA produces the value of a data cell, which is how the replica count the +autoscaler settled on reaches the resources that consume it. The function receives a value copy of the reconciled HPA +after each sync cycle: + +```go +currentReplicas := concepts.NewData[int32]("backend-current-replicas") + +builder := hpa.NewBuilder(base) +hpa.ExtractInto(builder, currentReplicas, func(h autoscalingv2.HorizontalPodAutoscaler) (int32, error) { + return h.Status.CurrentReplicas, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(currentReplicas)` or read it +opportunistically with `WithOptionalData(currentReplicas)`. See [Declared Data](../component.md#declared-data). + ## Operational Status The default handler inspects `Status.Conditions`: diff --git a/docs/primitives/job.md b/docs/primitives/job.md index 2e40c4d1..04aec209 100644 --- a/docs/primitives/job.md +++ b/docs/primitives/job.md @@ -177,6 +177,28 @@ function accepting `*job.Mutator` and call it directly. See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`job.ExtractInto` declares that this Job produces the value of a data cell, such as how many pods have run to +completion. The function receives a value copy of the reconciled Job after each sync cycle: + +```go +succeeded := concepts.NewData[int32]("migration-succeeded-pods") + +builder := job.NewBuilder(base) +job.ExtractInto(builder, succeeded, func(j batchv1.Job) (int32, error) { + return j.Status.Succeeded, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(succeeded)` or read it +opportunistically with `WithOptionalData(succeeded)`. See [Declared Data](../component.md#declared-data). + +Guard on the Job's `Completable` status when a later resource must wait for the Job to finish. Data cells carry values +between resources; they are not a substitute for the completion condition. + ## Suspension Jobs use the `Completable` lifecycle rather than `Alive`. The suspension behavior differs from Workload primitives: diff --git a/docs/primitives/pod.md b/docs/primitives/pod.md index 63c855e7..2d19f91d 100644 --- a/docs/primitives/pod.md +++ b/docs/primitives/pod.md @@ -161,6 +161,25 @@ m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error { | `EnsureContainerArg(arg)` | `EditContainers(AllContainers(), ...)` → `EnsureArg(arg)` | | `RemoveContainerArg(arg)` | `EditContainers(AllContainers(), ...)` → `RemoveArg(arg)` | +## Data Extraction + +`pod.ExtractInto` declares that this Pod produces the value of a data cell, such as the address assigned to it by the +cluster. The function receives a value copy of the reconciled Pod after each sync cycle: + +```go +agentIP := concepts.NewData[string]("agent-pod-ip") + +builder := pod.NewBuilder(base) +pod.ExtractInto(builder, agentIP, func(p corev1.Pod) (string, error) { + return p.Status.PodIP, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(agentIP)` or read it +opportunistically with `WithOptionalData(agentIP)`. See [Declared Data](../component.md#declared-data). + ## Suspension Pods cannot be paused. The default behavior deletes the pod when the component is suspended. diff --git a/docs/primitives/pv.md b/docs/primitives/pv.md index cbc054ce..0579d6b1 100644 --- a/docs/primitives/pv.md +++ b/docs/primitives/pv.md @@ -150,6 +150,28 @@ The `Mutator` exposes convenience wrappers for the most common PV spec operation Use these for simple, single-operation mutations. Use `EditPVSpec` when you need multiple operations or raw access in a single edit block. +## Data Extraction + +`pv.ExtractInto` declares that this PersistentVolume produces the value of a data cell, such as the claim the volume was +bound to. The function receives a value copy of the reconciled PersistentVolume after each sync cycle: + +```go +boundClaim := concepts.NewData[string]("data-volume-claim") + +builder := pv.NewBuilder(base) +pv.ExtractInto(builder, boundClaim, func(v corev1.PersistentVolume) (string, error) { + if v.Spec.ClaimRef == nil { + return "", nil + } + return v.Spec.ClaimRef.Name, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(boundClaim)` or read it +opportunistically with `WithOptionalData(boundClaim)`. See [Declared Data](../component.md#declared-data). + ## Operational Status The PV primitive implements `concepts.Operational`. The default handler maps PV phase to operational status: diff --git a/docs/primitives/replicaset.md b/docs/primitives/replicaset.md index 2d7cb9b2..e16f55bc 100644 --- a/docs/primitives/replicaset.md +++ b/docs/primitives/replicaset.md @@ -169,6 +169,25 @@ that accepts `*replicaset.Mutator` and call it directly from a `replicaset.Mutat See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`replicaset.ExtractInto` declares that this ReplicaSet produces the value of a data cell, such as the number of pods +reporting ready. The function receives a value copy of the reconciled ReplicaSet after each sync cycle: + +```go +readyReplicas := concepts.NewData[int32]("worker-ready-replicas") + +builder := replicaset.NewBuilder(base) +replicaset.ExtractInto(builder, readyReplicas, func(rs appsv1.ReplicaSet) (int32, error) { + return rs.Status.ReadyReplicas, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(readyReplicas)` or read it +opportunistically with `WithOptionalData(readyReplicas)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the ReplicaSet is scaled to zero replicas. The resource is not deleted. diff --git a/docs/primitives/secret.md b/docs/primitives/secret.md index 7c7aee84..50c51df5 100644 --- a/docs/primitives/secret.md +++ b/docs/primitives/secret.md @@ -245,6 +245,28 @@ func ChecksumAnnotationMutation(version, secretHash string) deployment.Mutation When the Secret mutations change (version upgrade, feature toggle), `DesiredHash` returns a different value on the same reconcile cycle, the pod template annotation changes, and Kubernetes triggers a rolling restart. +## Data Extraction + +`secret.ExtractInto` declares that this Secret produces the value of a data cell, which is how a generated credential +reaches the resources that need it. The function receives a value copy of the reconciled Secret after each sync cycle: + +```go +password := concepts.NewData[string]("app-password") + +builder := secret.NewBuilder(base) +secret.ExtractInto(builder, password, func(s corev1.Secret) (string, error) { + return string(s.Data["password"]), nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(password)` or read it +opportunistically with `WithOptionalData(password)`. See [Declared Data](../component.md#declared-data). + +A cell extracted from a Secret holds the decoded plaintext for the rest of the reconcile. Use it to build the object you +are applying, and keep it out of conditions, events, and log lines. + ## Full Example ```go diff --git a/docs/primitives/statefulset.md b/docs/primitives/statefulset.md index bfa3ae0b..5c3e50c7 100644 --- a/docs/primitives/statefulset.md +++ b/docs/primitives/statefulset.md @@ -225,6 +225,26 @@ m.EnsureVolumeClaimTemplate(corev1.PersistentVolumeClaim{ such updates. The mutator silently skips these operations on existing StatefulSets (identified by a non-empty `ResourceVersion`). Plan your storage layout before the first creation. +## Data Extraction + +`statefulset.ExtractInto` declares that this StatefulSet produces the value of a data cell, such as the controller +revision the pods are currently running. The function receives a value copy of the reconciled StatefulSet after each +sync cycle: + +```go +currentRevision := concepts.NewData[string]("db-current-revision") + +builder := statefulset.NewBuilder(base) +statefulset.ExtractInto(builder, currentRevision, func(sts appsv1.StatefulSet) (string, error) { + return sts.Status.CurrentRevision, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(currentRevision)` or read it +opportunistically with `WithOptionalData(currentRevision)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the StatefulSet is scaled to zero replicas. The resource is not deleted. diff --git a/plugin/skills/using-primitives/references/primitives/configmap.md b/plugin/skills/using-primitives/references/primitives/configmap.md index 0b38fde1..89889c2c 100644 --- a/plugin/skills/using-primitives/references/primitives/configmap.md +++ b/plugin/skills/using-primitives/references/primitives/configmap.md @@ -266,6 +266,26 @@ func ChecksumAnnotationMutation(version, configHash string) deployment.Mutation When the ConfigMap mutations change (version upgrade, feature toggle), `DesiredHash` returns a different value on the same reconcile cycle, the pod template annotation changes, and Kubernetes triggers a rolling restart. +## Data Extraction + +`configmap.ExtractInto` declares that this ConfigMap produces the value of a data cell, which is how a rendered +configuration value reaches the resources that consume it. The function receives a value copy of the reconciled +ConfigMap after each sync cycle: + +```go +bootstrapServers := concepts.NewData[string]("bootstrap-servers") + +builder := configmap.NewBuilder(base) +configmap.ExtractInto(builder, bootstrapServers, func(cm corev1.ConfigMap) (string, error) { + return cm.Data["bootstrap-servers"], nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(bootstrapServers)` or read it +opportunistically with `WithOptionalData(bootstrapServers)`. See [Declared Data](../component.md#declared-data). + ## Full Example ```go diff --git a/plugin/skills/using-primitives/references/primitives/cronjob.md b/plugin/skills/using-primitives/references/primitives/cronjob.md index 8dab0ea0..742dd6df 100644 --- a/plugin/skills/using-primitives/references/primitives/cronjob.md +++ b/plugin/skills/using-primitives/references/primitives/cronjob.md @@ -194,6 +194,29 @@ a plain function accepting `*cronjob.Mutator` and call it directly. See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`cronjob.ExtractInto` declares that this CronJob produces the value of a data cell, which is how scheduling state +observed on the object reaches the resources that consume it. The function receives a value copy of the reconciled +CronJob after each sync cycle: + +```go +lastSchedule := concepts.NewData[metav1.Time]("cleanup-last-schedule") + +builder := cronjob.NewBuilder(base) +cronjob.ExtractInto(builder, lastSchedule, func(cj batchv1.CronJob) (metav1.Time, error) { + if cj.Status.LastScheduleTime == nil { + return metav1.Time{}, nil + } + return *cj.Status.LastScheduleTime, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(lastSchedule)` or read it +opportunistically with `WithOptionalData(lastSchedule)`. See [Declared Data](../component.md#declared-data). + ## Operational Status `DefaultOperationalStatusHandler` always reports `Operational`. A CronJob is a passive scheduler: once it exists in the diff --git a/plugin/skills/using-primitives/references/primitives/daemonset.md b/plugin/skills/using-primitives/references/primitives/daemonset.md index b2abf38e..bd482d97 100644 --- a/plugin/skills/using-primitives/references/primitives/daemonset.md +++ b/plugin/skills/using-primitives/references/primitives/daemonset.md @@ -195,6 +195,25 @@ agent.WithMutation(daemonset.LiftMutation(sharedAuthMutation())) See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the full pattern. +## Data Extraction + +`daemonset.ExtractInto` declares that this DaemonSet produces the value of a data cell, such as how many nodes are +running a ready pod. The function receives a value copy of the reconciled DaemonSet after each sync cycle: + +```go +readyNodes := concepts.NewData[int32]("collector-ready-nodes") + +builder := daemonset.NewBuilder(base) +daemonset.ExtractInto(builder, readyNodes, func(ds appsv1.DaemonSet) (int32, error) { + return ds.Status.NumberReady, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(readyNodes)` or read it +opportunistically with `WithOptionalData(readyNodes)`. See [Declared Data](../component.md#declared-data). + ## Suspension DaemonSets have no replicas field, so there is no clean in-place pause mechanism. By default, the DaemonSet is diff --git a/plugin/skills/using-primitives/references/primitives/deployment.md b/plugin/skills/using-primitives/references/primitives/deployment.md index 8dbf06b1..4b229ac1 100644 --- a/plugin/skills/using-primitives/references/primitives/deployment.md +++ b/plugin/skills/using-primitives/references/primitives/deployment.md @@ -201,6 +201,25 @@ frontend.WithMutation(deployment.LiftMutation(sharedAuthMutation())) See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the full pattern. +## Data Extraction + +`deployment.ExtractInto` declares that this Deployment produces the value of a data cell, such as the revision the +rollout landed on. The function receives a value copy of the reconciled Deployment after each sync cycle: + +```go +revision := concepts.NewData[string]("web-server-revision") + +builder := deployment.NewBuilder(base) +deployment.ExtractInto(builder, revision, func(d appsv1.Deployment) (string, error) { + return d.Annotations["deployment.kubernetes.io/revision"], nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(revision)` or read it +opportunistically with `WithOptionalData(revision)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the Deployment is scaled to zero replicas. The resource is not deleted. diff --git a/plugin/skills/using-primitives/references/primitives/hpa.md b/plugin/skills/using-primitives/references/primitives/hpa.md index 1a26473a..85133ad2 100644 --- a/plugin/skills/using-primitives/references/primitives/hpa.md +++ b/plugin/skills/using-primitives/references/primitives/hpa.md @@ -175,6 +175,26 @@ m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error { }) ``` +## Data Extraction + +`hpa.ExtractInto` declares that this HPA produces the value of a data cell, which is how the replica count the +autoscaler settled on reaches the resources that consume it. The function receives a value copy of the reconciled HPA +after each sync cycle: + +```go +currentReplicas := concepts.NewData[int32]("backend-current-replicas") + +builder := hpa.NewBuilder(base) +hpa.ExtractInto(builder, currentReplicas, func(h autoscalingv2.HorizontalPodAutoscaler) (int32, error) { + return h.Status.CurrentReplicas, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(currentReplicas)` or read it +opportunistically with `WithOptionalData(currentReplicas)`. See [Declared Data](../component.md#declared-data). + ## Operational Status The default handler inspects `Status.Conditions`: diff --git a/plugin/skills/using-primitives/references/primitives/job.md b/plugin/skills/using-primitives/references/primitives/job.md index 2e40c4d1..04aec209 100644 --- a/plugin/skills/using-primitives/references/primitives/job.md +++ b/plugin/skills/using-primitives/references/primitives/job.md @@ -177,6 +177,28 @@ function accepting `*job.Mutator` and call it directly. See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`job.ExtractInto` declares that this Job produces the value of a data cell, such as how many pods have run to +completion. The function receives a value copy of the reconciled Job after each sync cycle: + +```go +succeeded := concepts.NewData[int32]("migration-succeeded-pods") + +builder := job.NewBuilder(base) +job.ExtractInto(builder, succeeded, func(j batchv1.Job) (int32, error) { + return j.Status.Succeeded, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(succeeded)` or read it +opportunistically with `WithOptionalData(succeeded)`. See [Declared Data](../component.md#declared-data). + +Guard on the Job's `Completable` status when a later resource must wait for the Job to finish. Data cells carry values +between resources; they are not a substitute for the completion condition. + ## Suspension Jobs use the `Completable` lifecycle rather than `Alive`. The suspension behavior differs from Workload primitives: diff --git a/plugin/skills/using-primitives/references/primitives/pod.md b/plugin/skills/using-primitives/references/primitives/pod.md index 63c855e7..2d19f91d 100644 --- a/plugin/skills/using-primitives/references/primitives/pod.md +++ b/plugin/skills/using-primitives/references/primitives/pod.md @@ -161,6 +161,25 @@ m.EditObjectMetadata(func(e *editors.ObjectMetaEditor) error { | `EnsureContainerArg(arg)` | `EditContainers(AllContainers(), ...)` → `EnsureArg(arg)` | | `RemoveContainerArg(arg)` | `EditContainers(AllContainers(), ...)` → `RemoveArg(arg)` | +## Data Extraction + +`pod.ExtractInto` declares that this Pod produces the value of a data cell, such as the address assigned to it by the +cluster. The function receives a value copy of the reconciled Pod after each sync cycle: + +```go +agentIP := concepts.NewData[string]("agent-pod-ip") + +builder := pod.NewBuilder(base) +pod.ExtractInto(builder, agentIP, func(p corev1.Pod) (string, error) { + return p.Status.PodIP, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(agentIP)` or read it +opportunistically with `WithOptionalData(agentIP)`. See [Declared Data](../component.md#declared-data). + ## Suspension Pods cannot be paused. The default behavior deletes the pod when the component is suspended. diff --git a/plugin/skills/using-primitives/references/primitives/pv.md b/plugin/skills/using-primitives/references/primitives/pv.md index cbc054ce..0579d6b1 100644 --- a/plugin/skills/using-primitives/references/primitives/pv.md +++ b/plugin/skills/using-primitives/references/primitives/pv.md @@ -150,6 +150,28 @@ The `Mutator` exposes convenience wrappers for the most common PV spec operation Use these for simple, single-operation mutations. Use `EditPVSpec` when you need multiple operations or raw access in a single edit block. +## Data Extraction + +`pv.ExtractInto` declares that this PersistentVolume produces the value of a data cell, such as the claim the volume was +bound to. The function receives a value copy of the reconciled PersistentVolume after each sync cycle: + +```go +boundClaim := concepts.NewData[string]("data-volume-claim") + +builder := pv.NewBuilder(base) +pv.ExtractInto(builder, boundClaim, func(v corev1.PersistentVolume) (string, error) { + if v.Spec.ClaimRef == nil { + return "", nil + } + return v.Spec.ClaimRef.Name, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(boundClaim)` or read it +opportunistically with `WithOptionalData(boundClaim)`. See [Declared Data](../component.md#declared-data). + ## Operational Status The PV primitive implements `concepts.Operational`. The default handler maps PV phase to operational status: diff --git a/plugin/skills/using-primitives/references/primitives/replicaset.md b/plugin/skills/using-primitives/references/primitives/replicaset.md index 2d7cb9b2..e16f55bc 100644 --- a/plugin/skills/using-primitives/references/primitives/replicaset.md +++ b/plugin/skills/using-primitives/references/primitives/replicaset.md @@ -169,6 +169,25 @@ that accepts `*replicaset.Mutator` and call it directly from a `replicaset.Mutat See [workload-kind-agnostic mutations](../primitives.md#workload-kind-agnostic-mutations) for the cross-kind pattern. +## Data Extraction + +`replicaset.ExtractInto` declares that this ReplicaSet produces the value of a data cell, such as the number of pods +reporting ready. The function receives a value copy of the reconciled ReplicaSet after each sync cycle: + +```go +readyReplicas := concepts.NewData[int32]("worker-ready-replicas") + +builder := replicaset.NewBuilder(base) +replicaset.ExtractInto(builder, readyReplicas, func(rs appsv1.ReplicaSet) (int32, error) { + return rs.Status.ReadyReplicas, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(readyReplicas)` or read it +opportunistically with `WithOptionalData(readyReplicas)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the ReplicaSet is scaled to zero replicas. The resource is not deleted. diff --git a/plugin/skills/using-primitives/references/primitives/secret.md b/plugin/skills/using-primitives/references/primitives/secret.md index 7c7aee84..50c51df5 100644 --- a/plugin/skills/using-primitives/references/primitives/secret.md +++ b/plugin/skills/using-primitives/references/primitives/secret.md @@ -245,6 +245,28 @@ func ChecksumAnnotationMutation(version, secretHash string) deployment.Mutation When the Secret mutations change (version upgrade, feature toggle), `DesiredHash` returns a different value on the same reconcile cycle, the pod template annotation changes, and Kubernetes triggers a rolling restart. +## Data Extraction + +`secret.ExtractInto` declares that this Secret produces the value of a data cell, which is how a generated credential +reaches the resources that need it. The function receives a value copy of the reconciled Secret after each sync cycle: + +```go +password := concepts.NewData[string]("app-password") + +builder := secret.NewBuilder(base) +secret.ExtractInto(builder, password, func(s corev1.Secret) (string, error) { + return string(s.Data["password"]), nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(password)` or read it +opportunistically with `WithOptionalData(password)`. See [Declared Data](../component.md#declared-data). + +A cell extracted from a Secret holds the decoded plaintext for the rest of the reconcile. Use it to build the object you +are applying, and keep it out of conditions, events, and log lines. + ## Full Example ```go diff --git a/plugin/skills/using-primitives/references/primitives/statefulset.md b/plugin/skills/using-primitives/references/primitives/statefulset.md index bfa3ae0b..5c3e50c7 100644 --- a/plugin/skills/using-primitives/references/primitives/statefulset.md +++ b/plugin/skills/using-primitives/references/primitives/statefulset.md @@ -225,6 +225,26 @@ m.EnsureVolumeClaimTemplate(corev1.PersistentVolumeClaim{ such updates. The mutator silently skips these operations on existing StatefulSets (identified by a non-empty `ResourceVersion`). Plan your storage layout before the first creation. +## Data Extraction + +`statefulset.ExtractInto` declares that this StatefulSet produces the value of a data cell, such as the controller +revision the pods are currently running. The function receives a value copy of the reconciled StatefulSet after each +sync cycle: + +```go +currentRevision := concepts.NewData[string]("db-current-revision") + +builder := statefulset.NewBuilder(base) +statefulset.ExtractInto(builder, currentRevision, func(sts appsv1.StatefulSet) (string, error) { + return sts.Status.CurrentRevision, nil +}) + +resource, err := builder.Build() +``` + +Resources registered later in the same component block on the cell with `WithDataGuard(currentRevision)` or read it +opportunistically with `WithOptionalData(currentRevision)`. See [Declared Data](../component.md#declared-data). + ## Suspension When the component is suspended, the StatefulSet is scaled to zero replicas. The resource is not deleted.