feat(provider): add the cozystack_kubernetes_nodes resource - #24
Aleksei Sviridkin (lexfrei) merged 7 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
NOT LGTM
The resource itself is well-designed and its core claim holds up: there is no provider-side default that would pin a pool to the Talos release of its creation day, the presence-preserving round-trip is sound, and the new KubernetesNodes tests all pass. The change is not approved only because of one concrete, author-actionable blocker plus two minor items below. Fix the sign-off and this is essentially an approve.
Blocking
[MAJOR]DCO check is red: a commit is not signed off. Commit13100ebd8ead("feat(provider): model the KubernetesNodes worker pool spec") lacks aSigned-off-bytrailer; the other three commits have it. The PR cannot merge while DCO fails. Pleasegit rebase --signoff feat/cozystack-1.6(or amend that one commit) and force-push.
CI analysis (the red test check is inherited, not introduced here)
The test job fails only on *ExpandKeysMatchConfigSpec coverage guards for kinds this PR does not modify: tenant.gateway, cozystack_kubernetes oidc/talos/nodeHealthCheck/maxUnhealthy/nodeStartupTimeout, and nats/qdrant tls. This PR adds the cozystack_kubernetes_nodes resource and touches none of those models. The same tenant/kubernetes failures appear on sibling branch #22, which also does not touch them, confirming they live in the stacked base feat/cozystack-1.6 and are resolved by other PRs in the stack (#22 for tls, #25 for the kubernetes/tenant surface). The resource's own guards (TestKubernetesNodesExpandNestedKeysMatchTheirSpecs, naming/validation, three-state) all pass. So CI-red is not a reason for the NOT LGTM; the DCO sign-off is.
What was verified
- No creation-day Talos pin.
expandemitstalos/images/kubelet(at minimum{}) and omitsroles/gpuswhen null; unset fields stay server-managed rather than being frozen to a default, so a pool is not silently pinned.keepConfigured*maps server-echoed empties back to null, so an unset block does not produce perpetual drift. - Whole-spec replacement on update is real.
client.Updatedoescurrent.Object["spec"] = app.Spec(application.go), so any field present on the object but absent from config is dropped on the next apply. This is the correct, visible behaviour (capture-on-import surfaces the removal as a plan diff rather than losing it silently), but see the docs note below. versionenum and naming validation match the nodes chartvalues.yaml;go vetand the new tests pass.
Minor findings
[MINOR]kubernetes_nodes_schema.go:132: the poolversionenum isOneOf("v1.35".."v1.31")and omitsv1.30, which the parentcozystack_kubernetesversion(kubernetes_schema.go:69) still accepts. Both the docs and the shipped example wireversion = cozystack_kubernetes.cluster.version, so an operator running a parent cluster onv1.30who copies the example gets a plan-time validation error on the pool. The enum matches the nodes chart, so this is a cross-resource inconsistency rather than a nodes-side bug, but worth reconciling or documenting.[MINOR]Import docs (docs/resources/kubernetes_nodes.md, Import section): becauseexpandreplaces the whole spec, any override captured on import (for example an air-gappedtalos.installer_repository) that is not copied into the.tfconfig is dropped on the first apply. The capture-on-import design is the right choice (it makes the loss a visible plan diff instead of silent), but the docs should tell operators to reconciletalos/images/kubelet/roles/gpusinto config before applying.
Cozystack 1.6 splits worker node pools out of the Kubernetes CR into a standalone KubernetesNodes kind, served by the aggregated API under the plural kubernetesnodeses. Declaring the descriptor lets the provider address the kind through the same dynamic-client CRUD every other app kind uses. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
A pool carries the sizing fields a Kubernetes nodeGroup already carried plus kubelet reservations, a Talos image block, and image overrides. The aggregated API resolves the chart's schema defaults into the spec on every read, so a block the pool never configured comes back populated. Taking those values into state would write them back as explicit spec keys on the next update, freezing the pool on the Talos release and images that were current the day it was created — while the parent cluster, which does not model them at all, keeps following the chart. The talos, kubelet, and images blocks are therefore optional and not computed, and a field the configuration left unset stays out of state. The values that are semantically stable keep an explicit default, matching how the Kubernetes kind models the same fields. The read after an import is the exception and records everything the server reports. It is the only chance to capture what a pool already has, and hiding an existing override there would let the next update, which replaces the spec whole, delete it with nothing in the plan. The chart derives the pool name by stripping the parent cluster from the release name and fails the render when that prefix is missing, which otherwise lands as a HelmRelease that can never install. Name and cluster both force replacement, so a mistyped rename plans as destroy plus create and a check that waited for Create would fire with the pool already torn down; the generic resource therefore grows an optional hook that lets a kind reject a configuration while the plan is built. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
KubernetesNodes was the one upstream app kind the provider did not serve. Registering the resource and its data source alongside the Kubernetes kind makes worker pools manageable on their own, which is the point of the split. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
The example pairs a cluster with a standalone GPU pool so the naming rule is visible in the code rather than only in prose: the pool object is <cluster>-<pool>, and the pool part cannot reuse a group the parent cluster still manages. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
An update replaces the whole spec, so an override captured on import and left out of configuration is removed by the first apply. The plan shows the removal, but only the configuration can prevent it. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
0564564 to
64339f1
Compare
|
Fixed: rebased with sign-off, every commit now carries a Signed-off-by matching the author. Also took the import note: the resource description now tells importers to copy captured overrides into configuration before the first apply (docs regenerated). On the v1.30 mismatch: #25 drops v1.30 from the parent validator, so the two resources agree once the stack lands. |
…zystack-1.6-kubernetesnodes
…ass changes The aggregated apiserver accepts a storage class change without migrating any volume, so an in-place update records a class the disks do not live on. Matches the behaviour every other kind adopted in this release. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
d000174
into
feat/cozystack-1.6
* feat(client): add the KubernetesNodes resource descriptor Cozystack 1.6 splits worker node pools out of the Kubernetes CR into a standalone KubernetesNodes kind, served by the aggregated API under the plural kubernetesnodeses. Declaring the descriptor lets the provider address the kind through the same dynamic-client CRUD every other app kind uses. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * feat(provider): model the KubernetesNodes worker pool spec A pool carries the sizing fields a Kubernetes nodeGroup already carried plus kubelet reservations, a Talos image block, and image overrides. The aggregated API resolves the chart's schema defaults into the spec on every read, so a block the pool never configured comes back populated. Taking those values into state would write them back as explicit spec keys on the next update, freezing the pool on the Talos release and images that were current the day it was created — while the parent cluster, which does not model them at all, keeps following the chart. The talos, kubelet, and images blocks are therefore optional and not computed, and a field the configuration left unset stays out of state. The values that are semantically stable keep an explicit default, matching how the Kubernetes kind models the same fields. The read after an import is the exception and records everything the server reports. It is the only chance to capture what a pool already has, and hiding an existing override there would let the next update, which replaces the spec whole, delete it with nothing in the plan. The chart derives the pool name by stripping the parent cluster from the release name and fails the render when that prefix is missing, which otherwise lands as a HelmRelease that can never install. Name and cluster both force replacement, so a mistyped rename plans as destroy plus create and a check that waited for Create would fire with the pool already torn down; the generic resource therefore grows an optional hook that lets a kind reject a configuration while the plan is built. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * feat(provider): register cozystack_kubernetes_nodes KubernetesNodes was the one upstream app kind the provider did not serve. Registering the resource and its data source alongside the Kubernetes kind makes worker pools manageable on their own, which is the point of the split. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * docs(kubernetes-nodes): add pool examples and generated pages The example pairs a cluster with a standalone GPU pool so the naming rule is visible in the code rather than only in prose: the pool object is <cluster>-<pool>, and the pool part cannot reuse a group the parent cluster still manages. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * docs(kubernetes-nodes): tell importers to reconcile captured overrides An update replaces the whole spec, so an override captured on import and left out of configuration is removed by the first apply. The plan shows the removal, but only the configuration can prevent it. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * feat(kubernetes-nodes): replace the pool when a configured storage class changes The aggregated apiserver accepts a storage class change without migrating any volume, so an in-place update records a class the disks do not live on. Matches the behaviour every other kind adopted in this release. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> --------- Signed-off-by: Aleksei Sviridkin <f@lex.la>
Pull Request
Summary
Cozystack 1.6 introduced the
KubernetesNodeskind, which splits worker node pools out of theKubernetesCR so pools can be managed independently of the parent cluster. This adds thecozystack_kubernetes_nodesresource and matching data source, the only upstream app kind the provider did not register.Defaults follow two rules. Stable semantic defaults (storage class, replica bounds, instance type, health checks, version) are materialised, mirroring the
kuberneteskind. Drift-prone fields (talos,images,kubelet,roles,gpus) stay presence-preserving: the aggregated apiserver resolves their defaults on every read, and a Computed attribute would write the server-resolved Talos values back as explicit spec keys, pinning the pool to the Talos release current on creation day. Import is the deliberate exception so existing overrides are captured instead of silently deleted on the next update.The CR must be named
<cluster>-<pool>; a name colliding with a parent-managed node group fails on ownership conflict. Documented on the resource page.Changes
KubernetesNodesclient resource descriptor and register resource plus data sourceTesting
make test): 16 new KubernetesNodes tests pass; the branch inherits 7 spec-coverage failures from the integration base (v1.6.1 pin bump), fixed by the sibling PRs. Zero new failures, verified against the merge base.make lint)make docsproduces no diff)make testacc), if applicable: needs a live parent cluster, planned as a follow-up before releaseDocumentation
examples/make docs)Checklist
type(scope): description)Additional Notes
Targets the
feat/cozystack-1.6integration branch, notmaster.