Skip to content

HYPERFLEET-1153 - feat: schema validation middleware driven by entity registry#265

Open
kuudori wants to merge 3 commits into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1153-feat-schema-validation-registry
Open

HYPERFLEET-1153 - feat: schema validation middleware driven by entity registry#265
kuudori wants to merge 3 commits into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1153-feat-schema-validation-registry

Conversation

@kuudori

@kuudori kuudori commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add ValidateSpecSchemas(schemaExists func(string) bool) to pkg/registry — panics at startup if any registered entity's SpecSchemaName doesn't resolve in the OpenAPI spec
  • Wire registry.Validate() at startup in registerAPIMiddleware (never called before)
  • Call ValidateSpecSchemas from NewSchemaValidator after loading the OpenAPI doc
  • Change buildSchemasMap warn+skip to return error for missing schemas (defense-in-depth)
  • Update existing tests to expect panic instead of error for missing schemas
  • Add unit tests for ValidateSpecSchemas (panic/pass/skip)

Hardcoded Cluster/NodePool entries intentionally preserved until HYPERFLEET-1159 migrates them to the registry.

Ticket

HYPERFLEET-1153

AC Coverage

AC Description How
1 POST/PATCH channels/versions validate spec Wiring exists via WithSpecSchema() + fatal check ensures schemas must resolve
2 New entity auto-enables validation Pre-existing via WithSpecSchema() (existing test proves it)
3 Unknown entity paths pass through Pre-existing shouldValidateRequest behavior, unchanged
4 Panic if SpecSchemaName doesn't resolve ValidateSpecSchemas in registry, called from NewSchemaValidator
5 Cluster/nodepool unchanged Hardcoded entries preserved
6 Unit tests for path extraction Existing + new TestValidateSpecSchemas_* tests

Gate Results

  • make verify-all: 1273 tests pass, all static checks clean

Unresolved

E2E

Gated in CI, not run locally.

Base

base: 4fe21ec (main at time of branch)

@openshift-ci

openshift-ci Bot commented Jun 30, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Jun 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign sherine-k for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 2c1ea258-0a54-478c-837d-bd8878c01525

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf43be and 6ee6ca1.

📒 Files selected for processing (5)
  • pkg/registry/descriptor.go
  • pkg/registry/registry.go
  • pkg/registry/registry_test.go
  • pkg/validators/schema_validator.go
  • pkg/validators/schema_validator_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/registry/registry.go
  • pkg/registry/registry_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added startup validation for registered spec schemas referenced during API schema/validator initialization.
    • Introduced a “required spec schema” option for registered entities, enforcing presence in the loaded OpenAPI document.
  • Bug Fixes

    • Missing required entity spec schemas now fail fast at startup (panic), while non-required missing schemas are skipped without breaking validation.
  • Tests

    • Expanded unit tests to cover required vs non-required behavior, empty and mixed registries, and updated schema-validator expectations.

Walkthrough

EntityDescriptor now includes RequireSpecSchema. ValidateSpecSchemas checks registered descriptors against OpenAPI component schemas and panics for required missing schemas. registerAPIMiddleware calls registry.Validate() during startup. NewSchemaValidator now validates registered schemas before building the schema map; missing required schemas panic, while other missing schemas are skipped or returned as errors. Tests were updated for the new panic and skip behavior.

Sequence Diagram(s)

sequenceDiagram
  participant registerAPIMiddleware
  participant registry
  participant NewSchemaValidator
  participant doc.Components.Schemas

  registerAPIMiddleware->>registry: Validate()
  registerAPIMiddleware->>NewSchemaValidator: construct
  NewSchemaValidator->>registry: ValidateSpecSchemas(doc.Components.Schemas lookup)
  registry->>doc.Components.Schemas: resolve SpecSchemaName
  doc.Components.Schemas-->>registry: present or missing
  registry-->>NewSchemaValidator: return or panic
  NewSchemaValidator->>doc.Components.Schemas: buildSchemasMap
  doc.Components.Schemas-->>NewSchemaValidator: present or missing
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: registry-driven schema validation middleware.
Description check ✅ Passed The description is directly aligned with the changeset and covers the new validation flow and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sec-02: Secrets In Log Output ✅ Passed No PR-added log statements expose token/password/credential/secret fields or strings; CWE-532 not triggered.
No Hardcoded Secrets ✅ Passed No hardcoded credentials, private keys, cred-URLs, or long base64 literals in touched files; changes are schema-validation code/tests only. CWE-798/CWE-321 not present.
No Weak Cryptography ✅ Passed No banned crypto, ECB, custom cryptography, or secret/HMAC comparisons found in touched code; only registry/schema validation. CWE-327/328/208 not implicated.
No Injection Vectors ✅ Passed No CWE-89/78/79/502 vector added: touched non-test code has no SQL concatenation, exec.Command*, template.HTML, or yaml.Unmarshal; fmt.Sprintf is only for errors/panics.
No Privileged Containers ✅ Passed No Kubernetes/OpenShift manifests, Helm templates, or Dockerfiles are in the PR diff; no privileged settings were introduced (CWE-250 not observed).
No Pii Or Sensitive Data In Logs ✅ Passed No CWE-532/CWE-200 issue: new logs emit only schema_path, schema_name/kind/plural; request logger masks headers and no raw bodies/secrets are added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands.

@kuudori kuudori marked this pull request as ready for review June 30, 2026 19:18
@openshift-ci openshift-ci Bot requested review from mliptak0 and sherine-k June 30, 2026 19:18
@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

Risk Score: 4 — risk/high

Signal Detail Points
PR size 343 lines (>200) +1
Sensitive paths cmd/ +2
Test coverage Missing tests for: cmd/hyperfleet-api/server +1

Computed by hyperfleet-risk-scorer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/validators/schema_validator.go`:
- Around line 57-67: The hardcoded `ClusterSpec` and `NodePoolSpec` branches in
`buildSchemasMap` still return errors instead of matching the new panic contract
used by the registry-backed `WithSpecSchema` path. Update those
`buildSchemasMap` cases to fail fast with the same `panic(fmt.Sprintf(...))`
style as the existing `schemaRef == nil` check, or refactor them to go through
the registry lookup so `NewSchemaValidator` behaves consistently for all missing
spec schemas.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: e9c58bd5-e006-4839-b308-cc219877bfdd

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe21ec and 26c2bf1.

📒 Files selected for processing (5)
  • cmd/hyperfleet-api/server/routes.go
  • pkg/registry/registry.go
  • pkg/registry/registry_test.go
  • pkg/validators/schema_validator.go
  • pkg/validators/schema_validator_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)

Comment thread pkg/validators/schema_validator.go
@kuudori kuudori force-pushed the HYPERFLEET-1153-feat-schema-validation-registry branch from 26c2bf1 to 8bf43be Compare June 30, 2026 19:43
Comment thread pkg/validators/schema_validator.go Outdated
} {
schemaRef := doc.Components.Schemas[hc.schema]
if schemaRef == nil {
panic(fmt.Sprintf(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to return an error here 🤔

Comment thread pkg/validators/schema_validator.go Outdated
Comment on lines +38 to +42
registry.ValidateSpecSchemas(func(name string) bool {
return doc.Components.Schemas[name] != nil
})

schemas := buildSchemasMap(doc)

@pnguyen44 pnguyen44 Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChannelSpec, VersionSpec, and WifConfigSpec don't exist in the upstream hyperfleet-api-spec yet, but the Channel, Version, and WifConfig plugins register those as their SpecSchemaName. The switch from warn+skip to panic means make run with the default openapi.yaml will crash at startup.

A few options:

  • Remove SpecSchemaName from those plugin registrations until the schemas are added to hyperfleet-api-spec
  • Keep warn+skip for now and switch to panic once all schemas exist upstream
  • Add the schemas to hyperfleet-api-spec first (separate PR)

Comment thread pkg/validators/schema_validator_test.go Outdated
Comment on lines +195 to +222
@@ -256,60 +217,9 @@ components:
err := os.WriteFile(schemaPath, []byte(schemaWithoutNodePool), 0600)
Expect(err).To(BeNil())

_, err = NewSchemaValidator(schemaPath)
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("NodePoolSpec schema not found"))
}

func TestValidate_SkipsWhenOptionalEntitySchemaNotLoaded(t *testing.T) {
RegisterTestingT(t)

var logBuf bytes.Buffer
logger.ReconfigureGlobalLogger(&logger.LogConfig{
Level: slog.LevelWarn,
Format: logger.FormatText,
Output: &logBuf,
Component: "validators-test",
})

registerRequiredSpecValidationEntities()
registry.Register(registry.EntityDescriptor{
Kind: "WifConfig",
Plural: "wifconfigs",
SpecSchemaName: "WifConfigSpec",
})

schemaWithoutWifConfig := `
openapi: 3.0.0
info:
title: Cluster NodePool Only
version: 1.0.0
paths: {}
components:
schemas:
ClusterSpec:
type: object
properties:
region:
type: string
NodePoolSpec:
type: object
properties:
replicas:
type: integer
`

tmpDir := t.TempDir()
schemaPath := filepath.Join(tmpDir, "cluster-nodepool-only.yaml")
err := os.WriteFile(schemaPath, []byte(schemaWithoutWifConfig), 0600)
Expect(err).To(BeNil())

validator, err := NewSchemaValidator(schemaPath)
Expect(err).To(BeNil())

// Invalid spec would fail validation if WifConfigSpec were loaded.
err = validator.Validate("wifconfigs", map[string]interface{}{})
Expect(err).To(BeNil())
Expect(func() {
_, _ = NewSchemaValidator(schemaPath)
}).To(PanicWith(ContainSubstring("NodePoolSpec")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor naming nit: this test now asserts a panic, but the name still ends with _Fails. The sibling test was renamed to _Panics for the same behavior change. Worth renaming this one too for consistency.

@kuudori kuudori force-pushed the HYPERFLEET-1153-feat-schema-validation-registry branch from 8bf43be to 6ee6ca1 Compare June 30, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants