Skip to content

fix(kubeblocks): lookup-safe optional fields, dead-code toggles, output schema fixes#368

Merged
unni-facets merged 5 commits into
mainfrom
fix/mongo-kubeblocks-restore-null-crash
Jul 6, 2026
Merged

fix(kubeblocks): lookup-safe optional fields, dead-code toggles, output schema fixes#368
unni-facets merged 5 commits into
mainfrom
fix/mongo-kubeblocks-restore-null-crash

Conversation

@RoguedBear

@RoguedBear RoguedBear commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Four commits, one per module, all in the KubeBlocks family (mongo/postgres/mysql datastore modules + the shared kubeblocks-operator):

  • mongo/kubeblocks: restore optional-object null-crash (RULE-015) + RULE-006/012/017/025 output-wiring and schema fixes + missing storage_class schema field. Version 1.11.2.
  • postgres/kubeblocks: same restore null-crash, plus a real bugcreate_read_service was hardcoded true, so standalone (non-HA) deploys handed out a reader endpoint pointing at a Service with zero backing pods. Fixed to follow local.ha_enabled. Same RULE-006/012/017/025 fixes as mongo.
  • mysql/kubeblocks: same restore null-crash and same create_read_service bug as postgres, plus the same RULE-006/017/025 fixes (mysql's output types were already correct, so no RULE-012 fix needed here).
  • kubeblocks-operator: resources optional-object null-crash (RULE-015, reproduced locally via terraform apply before fixing), plus a real bugspec.database_addons was supposed to toggle which addon Helm charts install but was never actually wired up; all five installed unconditionally regardless of the setting, and an unrelated redis/kafka chart failure could block the whole operator apply. Fixed by filtering enabled_addons against the spec (defaulting to true, so no behavior change for existing deployments that don't set it). Also swapped a stray bitnamilegacy/kubectl image for the canonical registry.k8s.io/kubectl tag.

Test plan

  • raptor create iac-module --dry-run passes clean on all four modules
  • terraform fmt / terraform validate clean on all four (only pre-existing, unrelated deprecation warnings)
  • Traced the create_read_service fix through both standalone and replication branches of the output ternary on postgres and mysql
  • Reproduced the kubeblocks-operator resources null-crash locally via terraform apply before fixing, confirmed the fix resolves it

RoguedBear and others added 4 commits July 2, 2026 14:10
…ixes (RULE-006/012/015/017/025)

restore is optional(object({...})) with no default, so an unset restore
block resolves to null (not absent). lookup(var.instance.spec, "restore",
{}) only substitutes its default for genuinely absent keys, so it returned
the literal null, and the following lookup(local.restore_config, "enabled",
false) crashed with "argument must not be null" on every deploy that
doesn't configure a restore (the common case). Replaced with
try(var.instance.spec.restore.enabled/.backup_name, default) and gave the
restore object real defaults in variables.tf. Same RULE-015 class as the
karpenter/kubernetes_cluster fixes on this branch.

Also, while auditing this module against rules.md:
- RULE-006: kubeblocks_operator input was missing the interfaces wrapper
  (source emits @facets/kubeblocks-operator under `default`, needs both
  attributes and interfaces). kubernetes_cluster input was wrapped in an
  extra attributes{} layer when the source emits it flat.
- RULE-025: node_pool.interfaces was typed `any`; replaced with an explicit
  optional(object({}), {}) matching what karpenter actually emits.
- RULE-012: outputs/mongo_k8s/outputs.yaml declared external_endpoints as
  type: object, but the module emits jsonencode(...) (a string, as the
  README already documents). Fixed the schema to type: string.
- RULE-012: outputs/mongo/outputs.yaml's interfaces schema only declared
  the three fixed keys (cluster/reader/writer), but output_interfaces
  dynamically merges in a key per configured external_access entry. Added
  additionalProperties to the schema rather than restrict the feature.
- storage_class was read in main.tf and documented in the README, but was
  never exposed in facets.yaml's storage schema, so it could not actually
  be set via Facets. Added it as an optional string (default '').
- Removed a redundant depends_on on the credentials data source; the
  try(...) reference to the same data source already creates it implicitly.

Version bumped 1.1 -> 1.2 for the input/output schema corrections.
Validated via `raptor create iac-module --dry-run`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… restore, schema fixes (RULE-006/012/015/017/025)

create_read_service was hardcoded true, so the reader-fallback ternary in
outputs.tf was dead code: in standalone mode (spec.mode != replication),
the read Service selects kubeblocks.io/role: secondary pods, which don't
exist outside HA. Every standalone deploy handed out a reader endpoint
that resolved to nothing while the writer endpoint worked fine - a silent
failure, no plan/apply error. Fixed to create_read_service = local.ha_enabled,
so standalone correctly falls back to the writer/primary host, matching
mongo's existing (correct) behavior. Traced the fix through both branches
of the ternary post-change to confirm.

restore is optional(object({...})) with no default, so an unset restore
block resolves to null (not absent) and lookup(local.restore_config,
"enabled", false) crashed with "argument must not be null" on every
deploy that doesn't configure a restore. Replaced with try() directly on
var.instance.spec.restore.*, matching the mongo/RULE-015 fix.

Also, matching the audit done on the mongo sibling module:
- RULE-006: kubeblocks_operator input was missing the interfaces wrapper;
  kubernetes_cluster input was wrapped in an extra attributes{} layer when
  the source emits it flat. Verified kubernetes_cluster is unreferenced
  elsewhere in this module before flattening.
- RULE-025: node_pool.interfaces was typed `any`; replaced with an explicit
  optional(object({}), {}).
- RULE-017: removed a redundant depends_on already implied by a try()
  reference to the same data source.
- storage_class was read in main.tf but never exposed in facets.yaml;
  added it as an optional string (default ''), and guarded the PVC
  template so an empty value doesn't emit storageClassName: "" (which
  disables dynamic provisioning in Kubernetes, unlike omitting the field).
- RULE-012: port was emitted as a number but the @facets/postgres output
  schema types it string; wrapped in tostring(). Added the missing
  `database` property to the interfaces schema (the module already emits
  it, the schema just never declared it).

Validated via `raptor create iac-module --dry-run`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…store, schema fixes (RULE-006/015/017/025)

Same bugs as the postgres sibling module in this branch:

create_read_service was hardcoded true, so in standalone mode (non-HA)
the reader output pointed at a Kubernetes Service selecting
kubeblocks.io/role: secondary pods, which don't exist outside replication
mode. Fixed to create_read_service = local.ha_enabled, matching mongo's
already-correct fallback to the writer/primary host.

restore is optional(object({...})) with no default, so an unset restore
block resolved to null and crashed lookup(local.restore_config, "enabled",
false) with "argument must not be null" on every deploy that doesn't
configure a restore. Replaced with try() directly on
var.instance.spec.restore.*.

Also matching the mongo/postgres audit:
- RULE-006: kubeblocks_operator input missing the interfaces wrapper;
  kubernetes_cluster wrapped in an extra attributes{} layer. Verified
  kubernetes_cluster is unreferenced elsewhere in this module.
- RULE-025: node_pool.interfaces typed `any` -> explicit
  optional(object({}), {}).
- RULE-017: removed a redundant depends_on already implied by a try()
  reference to the same data source.
- storage_class was read in main.tf but never exposed in facets.yaml;
  added as an optional string (default ''), guarded so an empty value
  doesn't emit storageClassName: "" (disables dynamic provisioning).

mysql's port/database output types already matched the @facets/mysql
schema, so unlike postgres no RULE-012 output-type fix was needed here.

Validated via `raptor create iac-module --dry-run`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rces, schema fixes (RULE-006/015/025)

resources is optional(object({...})) with no default, so an unset
resources block resolved to null and crashed the nested
lookup(lookup(var.instance.spec, "resources", {}), "cpu_limit", ...)
chain with "argument must not be null" on every deploy that doesn't
override CPU/memory. Gave the object real defaults (matching the literals
already hardcoded as lookup() fallbacks in main.tf) instead of patching
the call site. Same RULE-015 class as mongo/postgres/mysql's restore fix
in this branch. Reproduced the crash locally via `terraform apply` before
fixing, and confirmed the fix resolves it even when resources is sent as
an explicit null.

Real bug: spec.database_addons declared five boolean toggles
(postgresql/mysql/mongodb/redis/kafka) meant to control which KubeBlocks
addon Helm charts install, but main.tf's enabled_addons was never
filtered by them - the comment claimed "Filter only enabled addons" but
installed all five unconditionally. Setting redis=false did nothing, and
because the addon releases are wired atomic+wait+prevent_destroy, an
unrelated redis/kafka chart failure could block the entire operator
apply. Fixed by filtering enabled_addons against
var.instance.spec.database_addons (defaulting each to true, so existing
deployments that don't set this field see no behavior change). The addon
releases were already a single for_each block, so a filtered-out addon is
now genuinely absent from the plan, not just skipped-but-still-installed.
Also added database_addons to facets.yaml, which never exposed it.

Also matching the audit done on the datastore siblings:
- RULE-006: kubernetes_cluster input was wrapped in attributes{}+
  interfaces{} when the source emits @facets/kubernetes-details flat
  under `attributes`. Verified unreferenced elsewhere before flattening.
- RULE-025: node_pool.interfaces typed `any` -> explicit
  optional(object({}), {}).
- Swapped the bitnamilegacy/kubectl:1.33.4 image for the equivalent
  registry.k8s.io/kubectl:v1.33.4 (verified the tag exists) - bitnami's
  legacy shelf isn't where this should be pulled from.

Not changed: prevent_destroy = true on both helm releases contradicts
the module's own teardown logic (wait_for_cleanup sleep, keepAddons=false,
cleanup_on_fail=true all exist specifically to let destroy work cleanly,
but prevent_destroy blocks terraform destroy from running at all). Left
as-is pending a decision on whether that protection is intentional.

Version kept at 1.0: the output type (@facets/kubeblocks-operator) is
unchanged and the new database_addons field is additive, so nothing
consuming this module's outputs breaks.

Validated via `raptor create iac-module --dry-run`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@RoguedBear RoguedBear changed the title fix(datastore/mongo-kubeblocks): lookup-safe restore, output schema fixes fix(kubeblocks): lookup-safe optional fields, dead-code toggles, output schema fixes Jul 2, 2026
@RoguedBear
RoguedBear requested a review from unni-facets July 2, 2026 08:53
@unni-facets
unni-facets merged commit ff993b6 into main Jul 6, 2026
@unni-facets
unni-facets deleted the fix/mongo-kubeblocks-restore-null-crash branch July 6, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants