Skip to content

HYPERFLEET-1471 - feat: Server-set tenancy on the resource write path - #339

Open
mliptak0 wants to merge 1 commit into
openshift-hyperfleet:mainfrom
mliptak0:HYPERFLEET-1471
Open

HYPERFLEET-1471 - feat: Server-set tenancy on the resource write path#339
mliptak0 wants to merge 1 commit into
openshift-hyperfleet:mainfrom
mliptak0:HYPERFLEET-1471

Conversation

@mliptak0

@mliptak0 mliptak0 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

HYPERFLEET-1471

  • Stamp the caller's tenancy map on resource creation from server-derived context (pkg/tenant), never from the request body
  • Keep tenancy immutable on PATCH (structurally — ResourcePatch has no tenancy field; enforced by schema validation and covered by regression tests)
  • Surface tenancy in API responses via a new presenter helper
  • Bump hyperfleet-api-spec to v1.0.27 to generate the tenancy response field

Test Plan

  • Unit tests added/updated
  • make test-all passes
  • make lint passes
  • Helm chart changes validated with make test-helm (if applicable)
  • Deployed to a development cluster and verified (if Helm/config changes)
  • E2E tests passed (if cross-component or major changes)

Task link: https://redhat.atlassian.net/browse/HYPERFLEET-1471

@openshift-ci
openshift-ci Bot requested review from Ruclo and tirthct August 14, 2026 11:50
@openshift-ci

openshift-ci Bot commented Aug 14, 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 Aug 14, 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: Pro Plus

Run ID: c9fa4182-fe16-4dc2-8dc9-db057127e151

📥 Commits

Reviewing files that changed from the base of the PR and between 8be7c0e and bc976f7.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum, !**/go.sum
📒 Files selected for processing (8)
  • go.mod
  • pkg/api/presenters/resource.go
  • pkg/api/presenters/resource_test.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • pkg/tenant/context.go
  • pkg/tenant/context_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)

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Resources now automatically capture tenant context when created.
    • Tenant information is displayed when available and represented as an empty object when none is present.
    • Tenant assignments remain unchanged when resources are patched.
  • Bug Fixes

    • Tenant data supplied directly in create or update payloads is ignored.
    • Invalid or unsupported tenant fields are rejected with a validation error.

Walkthrough

Resources now derive tenancy from tenant context during creation. System identities and absent tenant context produce empty tenancy JSON. Patch operations preserve existing tenancy. Resource presenters convert stored tenancy JSON into API tenancy maps. Client-supplied tenancy is ignored during conversion and rejected in PATCH payloads. The API specification dependency was upgraded from v1.0.26 to v1.0.27.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to bc976

This change is merge-ready after normal checks and review; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant RequestContext
  participant ResourceService
  participant ResourceStore
  participant ResourcePresenter
  RequestContext->>ResourceService: provide resolved tenant
  ResourceService->>ResourceStore: persist resource with tenancy JSON
  ResourceStore-->>ResourceService: return persisted resource
  ResourceService->>ResourcePresenter: present persisted resource
  ResourcePresenter-->>RequestContext: return tenancy map
Loading

Suggested reviewers: kuudori, tirthct

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
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 The PR adds no production log statements and no added lines contain token, password, credential, or secret fields/interpolations; the existing logger call is unchanged and non-sensitive.
No Hardcoded Secrets ✅ Passed PR diff adds no API keys, tokens, passwords, private keys, embedded credentials, or secret-named literal assignments; go.sum additions are Go module checksums, not secrets.
No Weak Cryptography ✅ Passed The PR adds only JSON/context tenancy logic and an API-spec bump; the diff contains no crypto/md5, DES, RC4, SHA-1, ECB, custom crypto, or secret comparisons.
No Injection Vectors ✅ Passed The PR adds JSON serialization and a GORM model assignment; it introduces no SQL formatting, command execution, template.HTML, or yaml.Unmarshal injection sink.
No Privileged Containers ✅ Passed PR changes only Go files and dependency metadata. No prohibited settings were added; existing USER root is documented for make installation and unchanged, with non-root execution afterward. No CWE/...
No Pii Or Sensitive Data In Logs ✅ Passed The PR adds no logging statements. Added-line and structural scans found no slog, logr, zap, log, or fmt.Print calls; existing logger calls are unchanged.
Title check ✅ Passed The title clearly identifies server-set tenancy as the main change in the resource write path.
Description check ✅ Passed The description directly explains tenancy stamping, PATCH immutability, response presentation, dependency updates, and test coverage.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

❤️ Share

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

@hyperfleet-ci-bot

Copy link
Copy Markdown

Risk Score: 1 — risk/low

Signal Detail Points
PR size 264 lines (>200) +1
Sensitive paths none +0
Test coverage Tests cover changed packages +0

Computed by hyperfleet-risk-scorer

Comment on lines +180 to +183
func presentTenancy(t datatypes.JSON) *map[string]string {
if len(t) == 0 {
return nil
}

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.

presentTenancy returns nil for zero-length JSON but {} for stored "{}". Worth normalizing so empty tenancy always serializes the same way.

Comment on lines +267 to +289
func TestPresentResource_WithTenancy(t *testing.T) {
RegisterTestingT(t)

now := time.Now()
resource := &api.Resource{
Meta: api.Meta{ID: "id", CreatedTime: now, UpdatedTime: now},
Kind: "Channel",
Name: "test",
Spec: datatypes.JSON(`{}`),
Tenancy: datatypes.JSON(`{"org":"acme"}`),
CreatedBy: "user@test.com",
UpdatedBy: "user@test.com",
}

resp := PresentResource(resource)
Expect(resp.Tenancy).ToNot(BeNil())
Expect(*resp.Tenancy).To(HaveKeyWithValue("org", "acme"))
}

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

now := time.Now()

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.

Consider consolidating these into a table-driven test. The cases share the same setup and assertions, and a single table will scale better as we add more tenancy presentation scenarios.

Comment on lines +414 to +450
func TestResourceService_Create_StampsTenancyFromContext(t *testing.T) {
RegisterTestingT(t)
setupTestDescriptors()

mockDao := newMockResourceDao()
svc, _, _ := newTestResourceService(mockDao)

ctx := tenant.WithTenant(context.Background(), &tenant.ResolvedTenant{
Dimensions: map[string]string{"org": "acme"},
})
resource := testResource("Channel", "ch-1", "stable")

result, svcErr := svc.Create(ctx, "Channel", resource, nil)
Expect(svcErr).To(BeNil())
Expect(string(result.Tenancy)).To(MatchJSON(`{"org":"acme"}`))
}

func TestResourceService_Create_SystemIdentityGetsEmptyTenancy(t *testing.T) {
RegisterTestingT(t)
setupTestDescriptors()

mockDao := newMockResourceDao()
svc, _, _ := newTestResourceService(mockDao)

ctx := tenant.WithTenant(context.Background(), &tenant.ResolvedTenant{
System: true,
Dimensions: map[string]string{"org": "acme"},
})
resource := testResource("Channel", "ch-1", "stable")

result, svcErr := svc.Create(ctx, "Channel", resource, nil)
Expect(svcErr).To(BeNil())
Expect(string(result.Tenancy)).To(MatchJSON(`{}`))
}

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

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.

Consider consolidating the three Create tenancy tests into one table-driven test. They share the same setup and only differ by context input and expected Tenancy JSON, similar to TestTenancyJSON in pkg/tenant/context_test.go.

Expect(result.UpdatedBy).To(Equal("user@test.com"))
}

func TestResourceService_Create_StampsTenancyFromContext(t *testing.T) {

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.

Consider a test that pre-sets resource.Tenancy before calling Create and asserts it is overwritten by TenancyJSON(ctx). Create already does this unconditionally, but it is not covered today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants