Skip to content

feat(api): enforce strict single-database topology via CEL validation and image settings to children#100

Merged
fernando-villalba merged 3 commits intomainfrom
default-restrictions
Jan 3, 2026
Merged

feat(api): enforce strict single-database topology via CEL validation and image settings to children#100
fernando-villalba merged 3 commits intomainfrom
default-restrictions

Conversation

@fernando-villalba
Copy link
Collaborator

This commit introduces CEL XValidation rules to the MultigresCluster API to align the operator with the current limitations of the Multigres Gateway.

The Problem:
The Multigres Gateway currently hardcodes query routing to the "default" tablegroup and ignores the database name provided in the connection string. Consequently, creating multiple databases or custom-named tablegroups results in resources that are provisioned but effectively unreachable, as the Gateway will never route traffic to them.

The Solution:
We are enforcing a "Single Database" mode at the API level to prevent users from creating invalid or "zombie" configurations.

Changes:

  • Max Items: Restricted spec.databases to a maximum of 1 entry.
  • System Database Enforcement: Added a CEL rule to spec.databases ensuring the single database must be named "postgres" and marked as default: true.
  • TableGroup Naming: Added a CEL rule to TableGroupConfig ensuring that if default: true is set, the resource name must be "default". (This will need to be changed when multigres is updated to support different naming here)

This commit introduces CEL `XValidation` rules to the `MultigresCluster` API to
align the operator with the current limitations of the Multigres Gateway.

**The Problem:**
The Multigres Gateway currently hardcodes query routing to the "default"
tablegroup and ignores the database name provided in the connection string.
Consequently, creating multiple databases or custom-named tablegroups results in
resources that are provisioned but effectively unreachable, as the Gateway will
never route traffic to them.

**The Solution:**
We are enforcing a "Single Database" mode at the API level to prevent users
from creating invalid or "zombie" configurations.

**Changes:**
* **Max Items:** Restricted `spec.databases` to a maximum of 1 entry.
* **System Database Enforcement:** Added a CEL rule to `spec.databases` ensuring
    the single database must be named `"postgres"` and marked as `default: true`.
* **TableGroup Naming:** Added a CEL rule to `TableGroupConfig` ensuring that
    if `default: true` is set, the resource name must be `"default"`. (This will need to be changed when multigres is updated to support different naming here)
@github-actions

This comment has been minimized.

This commit updates the CEL validation rule for `TableGroups` to strictly
require exactly one default TableGroup per database.

**The Fix:**
Previously, the validation only ensured *at most* one default (`<= 1`). This left
a gap where a user could define a list of custom TableGroups but forget to mark
any of them as `default`, leading to a valid CR but a broken runtime state (as
the Gateway would have no target for standard traffic).

**Change:**
* Updated the `TableGroups` CEL rule from `size() <= 1` to `size() == 1`.

This complements the Mutating Webhook (which handles empty lists) by ensuring
that explicit, user-provided configurations are also complete and routable.
@github-actions

This comment has been minimized.

Replaces the standalone `MultiGatewayImage` field in `CellSpec` with a `CellImages` struct to align with the `Shard` resource structure.

Additionally, adds `ImagePullPolicy` and `ImagePullSecrets` to both `CellImages` and `ShardImages`. This allows proper configuration of private registry credentials and pull policies at the Cell and Shard levels.
@fernando-villalba
Copy link
Collaborator Author

Replaces the standalone MultiGatewayImage field in CellSpec with a CellImages struct to align with the Shard resource structure.

Additionally, adds ImagePullPolicy and ImagePullSecrets to both CellImages and ShardImages. This allows proper configuration of private registry credentials and pull policies at the Cell and Shard levels.

@github-actions
Copy link

github-actions bot commented Jan 3, 2026

🔬 Go Test Coverage Report

Summary

Coverage Type Result
Threshold 0%
Previous Test Coverage Unknown%
New Test Coverage 0.0%

Status

✅ PASS

Detail

Show New Coverage
github.com/numtide/multigres-operator/api/v1alpha1/cell_types.go:145:			init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/celltemplate_types.go:62:		init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/coretemplate_types.go:63:		init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/multigrescluster_types.go:359:	init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/shard_types.go:185:			init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/shardtemplate_types.go:63:		init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/tablegroup_types.go:111:		init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/toposerver_types.go:186:		init		0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:14:		DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:23:		DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:33:		DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:41:		DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:56:		DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:66:		DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:76:		DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:86:		DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:97:		DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:107:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:121:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:131:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:139:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:149:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:159:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:178:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:188:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:200:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:210:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:215:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:225:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:233:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:243:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:251:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:265:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:275:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:283:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:298:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:308:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:318:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:328:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:334:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:344:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:352:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:362:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:370:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:384:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:394:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:402:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:417:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:427:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:439:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:449:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:454:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:464:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:476:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:486:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:496:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:506:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:511:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:521:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:536:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:546:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:561:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:571:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:581:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:591:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:602:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:612:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:621:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:631:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:639:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:653:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:663:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:671:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:694:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:704:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:730:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:740:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:763:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:773:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:782:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:792:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:800:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:815:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:825:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:835:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:845:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:858:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:868:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:882:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:892:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:900:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:917:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:927:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:940:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:950:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:965:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:975:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:992:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1002:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1010:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1020:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1028:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1042:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1052:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1060:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1077:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1087:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1117:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1127:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1132:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1142:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1151:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1161:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1169:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1181:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1191:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1205:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1215:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1223:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1237:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1247:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1259:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1269:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1274:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1284:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1293:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1303:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1311:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1325:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1335:	DeepCopyObject	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1343:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1353:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1363:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1375:	DeepCopy	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1385:	DeepCopyInto	0.0%
github.com/numtide/multigres-operator/api/v1alpha1/zz_generated.deepcopy.go:1390:	DeepCopy	0.0%
total:											(statements)	0.0%

@fernando-villalba fernando-villalba changed the title feat(api): enforce strict single-database topology via CEL validation feat(api): enforce strict single-database topology via CEL validation and image settings to children Jan 3, 2026
@fernando-villalba fernando-villalba merged commit 0fec3ca into main Jan 3, 2026
3 checks passed
@fernando-villalba fernando-villalba deleted the default-restrictions branch January 3, 2026 11:53
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.

1 participant