Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
339e519
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
429fe8f
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
d584656
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
6f443d4
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
8257e71
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
5401138
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
1f87706
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 27, 2026
a822121
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 28, 2026
5e5b32a
HYPERFLEET-538 - feat: CEL-based condition mapping engine
ldornele Jul 28, 2026
3d1ab44
HYPERFLEET-538 - refactor: address code review feedback
ldornele Jul 30, 2026
0578f42
HYPERFLEET-538 - fix: enforce CEL error rollback per design doc
ldornele Jul 30, 2026
4bfb65e
HYPERFLEET-538 - fix: add missing db.MarkForRollback on CEL error
ldornele Jul 30, 2026
8360794
HYPERFLEET-538 - fix: move Gomega assertion out of goroutine
ldornele Jul 30, 2026
1defeaf
HYPERFLEET-538 - fix: propagate field validation error instead of sil…
ldornele Jul 30, 2026
83d78b5
HYPERFLEET-538 - fix: address code review feedback
ldornele Jul 30, 2026
f7ea5d8
HYPERFLEET-538 - perf: avoid map allocation when hasUnknown=true
ldornele Jul 30, 2026
edb36d4
HYPERFLEET-538 - test: add coverage for reason/message expression errors
ldornele Jul 30, 2026
e57c438
HYPERFLEET-538 - feat: remove redundant tags, fix review comments
tirthct Aug 6, 2026
2e2dde0
HYPERFLEET-538 - feat: remaining small fix
tirthct Aug 6, 2026
65392fc
HYPERFLEET-538 - feat: fix coderabbit suggestions
tirthct Aug 6, 2026
80f4d84
HYPERFLEET-538 - feat: fix coderabbit and Dmitrii's suggestions
tirthct Aug 7, 2026
5e6cbb0
HYPERFLEET-538 - feat: fix linter
tirthct Aug 7, 2026
6049b2c
HYPERFLEET-538 - feat: skip message and fix coderabbit comment
tirthct Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/hyperfleet-api/container/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import (

func (c *Container) ResourceService() services.ResourceService {
if c.resourceService == nil {
c.resourceService = services.NewResourceService(
svc, err := services.NewResourceService(
c.ResourceDao(),
c.ResourceLabelDao(),
c.AdapterStatusDao(),
c.ResourceConditionDao(),
c.GenericService(),
)
if err != nil {
panic("failed to create resource service: " + err.Error())
}
c.resourceService = svc
Comment thread
tirthct marked this conversation as resolved.
}
return c.resourceService
}
Expand Down
42 changes: 41 additions & 1 deletion configs/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ health:
# Entity Registration
# Generic resource types registered at startup. Each entry auto-generates
# REST endpoints, spec validation, and delete policies.
# See docs/config.md for condition mapping documentation.
entities:
- kind: Cluster
plural: clusters
Expand All @@ -115,6 +116,26 @@ entities:
name_max_len: 53
require_spec_schema: true

# CEL-based condition mapping rules
# conditions:
# Example: Expose Landing Zone namespace readiness
# - type: LandingZoneReady
# when:
# expression: 'statuses.exists(s, s.adapter == "landing-zone-adapter" && s.conditions.exists(c, c.type == "NamespaceReady"))'
Comment thread
tirthct marked this conversation as resolved.
# output:
# status:
# expression: |
# statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
# .conditions.filter(c, c.type == "NamespaceReady")[0].status
# reason:
# expression: |
# statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
# .conditions.filter(c, c.type == "NamespaceReady")[0].reason
# message:
# expression: |
# "Landing zone: " + statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
# .conditions.filter(c, c.type == "NamespaceReady")[0].message
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- kind: NodePool
plural: nodepools
parent_kind: Cluster
Expand All @@ -126,6 +147,26 @@ entities:
name_max_len: 15
require_spec_schema: true

# CEL-based condition mapping rules
# conditions:
# Example: Expose Validation quota check status
# - type: QuotaValid
# when:
# expression: 'statuses.exists(s, s.adapter == "validation-adapter" && s.conditions.exists(c, c.type == "QuotaSufficient"))'
# output:
# status:
# expression: |
# statuses.filter(s, s.adapter == "validation-adapter")[0]
# .conditions.filter(c, c.type == "QuotaSufficient")[0].status
# reason:
# expression: |
# statuses.filter(s, s.adapter == "validation-adapter")[0]
# .conditions.filter(c, c.type == "QuotaSufficient")[0].reason
# message:
# expression: |
# statuses.filter(s, s.adapter == "validation-adapter")[0]
# .conditions.filter(c, c.type == "QuotaSufficient")[0].message

- kind: Channel
plural: channels
spec_schema_name: ChannelSpec
Expand All @@ -142,7 +183,6 @@ entities:
plural: wifconfigs
spec_schema_name: WifConfigSpec


# ----------------------------------------------------------------------------
# Configuration Priority (highest to lowest):
# 1. Command-line flags (e.g., --server-host=0.0.0.0 --server-port=8000)
Expand Down
73 changes: 73 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,79 @@ health:

</details>

<details>
<summary><b>Condition Mapping (CEL)</b> (click to expand)</summary>

Each entity can define CEL-based condition mapping rules that expose
provider-specific adapter conditions in the public `status.conditions` array.

**Lifecycle:**

- Rules are compiled at startup (fail-fast). Invalid CEL expressions prevent API startup.
- Evaluation happens during status aggregation. Adapter entries with any `Unknown` condition are excluded entirely.

**Reserved condition types** (cannot be overridden by mapping):

- `Reconciled`
- `LastKnownReconciled`
- Per-adapter synthesized types (auto-generated from `required_adapters`).
Example: `validation` adapter produces `ValidationSuccessful` condition type.

**CEL Context Variables:**

| Variable | Type | Description |
|----------|------|-------------|
| `statuses` | `list(dyn)` | Array of adapter statuses. Each entry: `adapter` (string), `observed_generation` (number), `conditions` (array), `data` (map) |
| `resource` | `dyn` | Full cluster/nodepool object as map (sensitive fields masked) |

**Custom CEL Functions:**

| Function | Description |
|----------|-------------|
| `toJson(value)` | Marshal any value to a JSON string |
| `dig(target, "dot.path")` | Safe nested navigation returning `null` on missing keys |

**Security:** Adapter data fields matching sensitive patterns (`password`, `secret`, `token`,
`auth`, `private`, `connection`, `cert`, `credential`, etc.) are automatically masked with
`***REDACTED***` before CEL evaluation. This prevents credential leakage in public
condition messages/reasons. See `pkg/util/mask_sensitive.go` for the full pattern list.

**Field Length Constraints:**

| Field | Limit | Behavior |
|-------|-------|----------|
| `type` | 128 bytes | Validation error (prevents startup) |
| `reason` | 256 bytes | Condition skipped if exceeded |
| `message` | 2048 bytes | Truncated if exceeded |

**Example:**

```yaml
entities:
- kind: Cluster
conditions:
- type: LandingZoneReady
when:
expression: |
statuses.exists(s, s.adapter == "landing-zone-adapter"
&& s.conditions.exists(c, c.type == "NamespaceReady"))
output:
status:
expression: |
statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
.conditions.filter(c, c.type == "NamespaceReady")[0].status
reason:
expression: |
statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
.conditions.filter(c, c.type == "NamespaceReady")[0].reason
message:
expression: |
"Landing zone: " + statuses.filter(s, s.adapter == "landing-zone-adapter")[0]
.conditions.filter(c, c.type == "NamespaceReady")[0].message
```

</details>

---

## Complete Reference
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/go-gormigrate/gormigrate/v2 v2.1.6
github.com/go-playground/validator/v10 v10.30.3
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/cel-go v0.29.0
github.com/google/uuid v1.6.0
github.com/jinzhu/inflection v1.0.0
github.com/lib/pq v1.12.3
Expand Down Expand Up @@ -42,6 +43,8 @@ require (
)

require (
cel.dev/expr v0.25.2 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand All @@ -51,6 +54,7 @@ require (
go.opentelemetry.io/contrib/propagators/jaeger v1.45.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.45.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/time v0.15.0 // indirect
)

Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
Expand All @@ -15,6 +17,8 @@ github.com/MicahParks/keyfunc/v3 v3.8.1/go.mod h1:LcorJ0sz2tZGvgZqIfaeyLkJmM+kxI
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -100,6 +104,8 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.29.0 h1:fEG+Ja3YRwNOqnQxTyJwoByAUAvTuxUGiro/jhrm4F4=
github.com/google/cel-go v0.29.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
Expand Down Expand Up @@ -291,6 +297,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
Expand Down
Loading