Skip to content

RHINENG-26547: re-apply workspace related code changes#2231

Merged
TenSt merged 2 commits into
RedHatInsights:masterfrom
TenSt:stepan/RHINENG-26547-re-apply-workspace-related-code-changes
Jun 11, 2026
Merged

RHINENG-26547: re-apply workspace related code changes#2231
TenSt merged 2 commits into
RedHatInsights:masterfrom
TenSt:stepan/RHINENG-26547-re-apply-workspace-related-code-changes

Conversation

@TenSt

@TenSt TenSt commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

This PR:

  • creates index for workspace_id (workspace_name is not needed based on usage)
  • updates golang code to use new columns
  • updates tests

Summary by Sourcery

Replace inventory group-based access and filtering with workspace ID/name columns across API, database, and RBAC, and simplify workspace filtering using the new indexed workspace_id field.

Enhancements:

  • Add extraction and propagation of workspace IDs from RBAC and Kessel middlewares into request context instead of JSON-based inventory groups.
  • Refactor database helpers and controllers to filter systems, advisories, packages, templates, and tags by workspace_id/workspace_name and extend exported CSVs with workspace columns.
  • Simplify workspace filtering logic to a workspace_id IN (...) condition and adjust OpenAPI-related and tooling dependencies, as well as search handling, accordingly.

Build:

  • Update Go module dependencies for swagger/openapi and related tooling libraries to newer versions.

Deployment:

  • Introduce DB migration 159 to drop the workspace sync trigger function, create an index on system_inventory.workspace_id, and provide a reversible down migration.

Tests:

  • Update and extend unit tests and SQL test data to cover workspace_id/workspace_name usage, RBAC workspace resolution, and revised filtering and export behavior.

@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Migrate inventory workspace handling from JSON-based workspaces to explicit workspace_id/workspace_name columns across middleware, controllers, DB utilities, listener, evaluator, and tests, and add a B-tree index and migration for system_inventory.workspace_id while adjusting search and RBAC logic to work with workspace IDs.

File-Level Changes

Change Details Files
RBAC middleware now derives and injects workspace IDs (instead of JSON groups) from RBAC and Kessel, including new helper logic and tests for workspace extraction.
  • Introduce RBAC constants for inventory groups/workspaces and a root workspace ID in rbac middleware.
  • Update isAccessGranted to store inventory groups under local keys and to compute/set KeyInventoryWorkspaces via new findInventoryWorkspaces.
  • Implement findInventoryWorkspaces to translate group.id resource definitions into a list of UUID workspace IDs, handling all-access, nil (root) and invalid UUIDs.
  • Add comprehensive tests for findInventoryWorkspaces covering grouped, ungrouped, mixed, all-access, invalid-op, and invalid-UUID cases.
  • Update Kessel middleware to pass through raw workspace IDs to Gin context (KeyInventoryWorkspaces) instead of building JSON inventoryGroups, and adjust its tests accordingly.
manager/middlewares/rbac.go
manager/middlewares/rbac_test.go
manager/middlewares/kessel.go
manager/middlewares/kessel_test.go
base/utils/gin.go
platform/rbac.go
Database utilities and filters now operate on workspace_id instead of JSON workspaces, and controller queries accept workspace ID slices from context rather than inventory group maps.
  • Change database.Systems, SystemAdvisories, SystemPackages, and SystemAdvisoriesByInventoryID to accept []string workspaceIDs and propagate this through all callers.
  • Rewrite ApplyInventoryWorkspaceFilter to filter on si.workspace_id IN (?), logging when the slice is empty, instead of JSONB workspaces containment and special ungrouped handling.
  • Update all controller helpers (systems, advisory systems, packages, system packages, template systems, tags, templates, system detail, system advisories, package versions, templates update/delete/subscribed, exports, etc.) to read KeyInventoryWorkspaces ([]string) and pass workspace IDs through to database helpers and advisory queries.
  • Adjust advisory query builders (buildAdvisoryAccountDataQuery, buildQueryAdvisoriesTagged) and cache decision helpers (shouldUseCache, advisory cache condition) to work with workspace IDs; add TODO notes where the presence of root workspace makes some conditions always true.
  • Update buildInventoryQuery to resolve group_name filters via si.workspace_name IN (?) instead of JSON workspace searches.
  • Adapt all affected tests to the new function signatures and workspace semantics (including workspace-aware template, advisory, package, systems, and export tests).
base/database/utils.go
base/database/utils_test.go
manager/controllers/utils.go
manager/controllers/utils_test.go
manager/controllers/systems.go
manager/controllers/system_detail.go
manager/controllers/systemtags.go
manager/controllers/advisories.go
manager/controllers/advisories_export.go
manager/controllers/advisory_systems.go
manager/controllers/advisory_systems_export.go
manager/controllers/system_advisories.go
manager/controllers/system_advisories_export.go
manager/controllers/packages.go
manager/controllers/packages_export.go
manager/controllers/package_versions.go
manager/controllers/package_systems.go
manager/controllers/package_systems_export.go
manager/controllers/template_systems.go
manager/controllers/template_systems_export.go
manager/controllers/template_systems_update.go
manager/controllers/template_systems_delete.go
manager/controllers/template_subscribed_systems_update.go
manager/controllers/systems_advisories_view.go
manager/controllers/systems_advisories_view_test.go
manager/controllers/systems_export.go
manager/controllers/systems_export_test.go
manager/controllers/advisory_systems_export_test.go
manager/controllers/package_systems_export_test.go
manager/controllers/template_systems_export_test.go
Listener upload and data models now persist workspace ID/name columns instead of JSON workspaces, with tests verifying correct mapping from incoming host groups.
  • Extend models.SystemInventory with WorkspaceID and WorkspaceName fields while keeping legacy Workspaces field present but unused by new code.
  • Change listener.updateSystemPlatform to compute a single workspace from host.Groups[0], validating UUID, warning on multiple workspaces, and setting WorkspaceID/WorkspaceName instead of Workspaces.
  • Update upload processing and repo update functions to use explicit start times for timing metrics (minor behavior change only).
  • Adjust listener tests to construct SystemInventory with workspace columns, assert that stored rows correctly mirror host workspace ID/name, and remove dependence on JSON Workspaces.
  • Update auxiliary tests (e.g., test helpers, culling, turnpike admin init-delete) to use new workspace helpers rather than JSON workspaces.
base/models/models.go
listener/upload.go
listener/upload_test.go
listener/common_test.go
tasks/system_culling/system_culling_test.go
turnpike/controllers/admin_test.go
base/database/testing.go
Schema and seed data are updated to treat workspaces as normalized workspace_id/workspace_name columns, with a new migration adding an index on workspace_id and dropping the legacy sync trigger.
  • Bump schema_migrations version from 158 to 159 in create_schema.sql.
  • Remove creation and trigger wiring of sync_system_inventory_workspace from the base schema definition and its JSONB workspaces-based behavior.
  • Add migration 159: .up drops the sync trigger and function and creates system_inventory_workspace_id_index on workspace_id; .down recreates the function/trigger and drops the index.
  • Modify dev/test_data.sql to populate system_inventory.workspace_id and workspace_name columns consistently across all rows, including a special root-ws workspace ID used as root, while removing JSON workspaces payloads and related cleanup statements.
  • Update tests relying on counts and grouping (e.g., database/utils_test.go) to align with the new workspace distribution and root workspace behavior.
database_admin/schema/create_schema.sql
database_admin/migrations/159_create_workspace_columns_indexes.up.sql
database_admin/migrations/159_create_workspace_columns_indexes.down.sql
dev/test_data.sql
base/database/utils_test.go
Systems/advisories/package/template APIs and exports now expose workspace info and updated search behavior to clients.
  • Introduce SystemWorkspace struct used in multiple response types to expose workspace_id and workspace_name alongside groups, and update CSV headers and expected lines in related tests (systems, advisory systems, package systems, template systems).
  • Adjust SystemGroups query so groups are synthesized from workspace_id/workspace_name via jsonb_build_array, preserving existing API contract while using new columns.
  • Update systems/advisories view handlers to treat system IDs as strings rather than UUID types and adjust SQL joins to cast parameters appropriately (IN (?::uuid)).
  • Refine ApplySearch to perform one concatenated, case-insensitive LIKE over multiple columns using LOWER(CONCAT(...)) instead of multiple ORed ILIKE conditions, and remove now-invalid tests for the old behavior.
  • Modify test fixtures and expectations across controllers to reflect deterministic workspace IDs/names in exports and JSON payloads.
manager/controllers/common_attributes.go
manager/controllers/systems.go
manager/controllers/systems_export.go
manager/controllers/systems_export_test.go
manager/controllers/advisory_systems.go
manager/controllers/advisory_systems_export.go
manager/controllers/package_systems.go
manager/controllers/package_systems_export.go
manager/controllers/template_systems.go
manager/controllers/template_systems_export.go
manager/controllers/system_advisories.go
manager/controllers/system_advisories_view.go
manager/controllers/system_advisories_view_test.go
manager/controllers/utils.go
manager/controllers/utils_test.go
Evaluator’s advisory update events now use the stored workspace_id field directly for routing, with tests updated accordingly.
  • Change createAdvisoryUpdateEvent to read system.Inventory.WorkspaceID instead of parsing JSON Workspaces, only logging a warning when WorkspaceID is nil.
  • Update TestCreateAdvisoryUpdateEvent to build a SystemInventory with WorkspaceID/WorkspaceName and assert the event’s workspace ID matches.
  • Remove unused JSON workspace parsing logic from the evaluator.
evaluator/advisory_update.go
evaluator/advisory_update_test.go
Testing and shared infrastructure now assume workspace IDs in Gin context during tests and adjust helpers accordingly.
  • Add WorkspacesTestCtx in core/gintesting.go that injects a default list of workspace IDs into Gin test contexts; wire this into InitRouterWithParams.
  • Adjust tests across controllers to rely on KeyInventoryWorkspaces being present with default values, instead of KeyInventoryGroups.
  • Update database testing helpers to provide workspace ID/name pointers for constructing SystemInventory test rows.
base/core/gintesting.go
manager/controllers/*_test.go
base/database/testing.go
Go module dependencies are bumped for swagger tooling and related libs, and a few additional indirect dependencies are added.
  • Add new indirect dependencies used by swagger and CLI tooling (github.com/cpuguy83/go-md2man/v2, github.com/russross/blackfriday/v2, github.com/shurcooL/sanitized_anchor_name, github.com/urfave/cli/v2, github.com/xrash/smetrics, gopkg.in/yaml.v2, sigs.k8s.io/yaml).
  • Update various github.com/go-openapi/* modules to newer versions in go.mod.
  • Bump golang.org/x/mod, golang.org/x/sync, and golang.org/x/text versions in go.mod and refresh go.sum accordingly.
go.mod
go.sum

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@TenSt TenSt marked this pull request as ready for review June 10, 2026 14:05
@TenSt TenSt requested a review from a team as a code owner June 10, 2026 14:05

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues, and left some high level feedback:

  • ApplyInventoryWorkspaceFilter currently always applies WHERE si.workspace_id IN (?) even when workspaceIDs is nil/empty; since findInventoryWorkspaces returns nil for “all workspaces” this will unintentionally filter out all systems instead of leaving the query unbounded—consider treating nil (or empty) as no workspace restriction and only adding the WHERE clause when there is at least one ID.
  • The RBAC middleware still sets KeyInventoryGroups in the Gin context, but all call sites now read KeyInventoryWorkspaces instead; you can simplify and avoid confusion by removing the KeyInventoryGroups constant and the associated c.Set call if groups are no longer used anywhere.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- ApplyInventoryWorkspaceFilter currently always applies `WHERE si.workspace_id IN (?)` even when `workspaceIDs` is nil/empty; since `findInventoryWorkspaces` returns nil for “all workspaces” this will unintentionally filter out all systems instead of leaving the query unbounded—consider treating `nil` (or empty) as no workspace restriction and only adding the WHERE clause when there is at least one ID.
- The RBAC middleware still sets `KeyInventoryGroups` in the Gin context, but all call sites now read `KeyInventoryWorkspaces` instead; you can simplify and avoid confusion by removing the `KeyInventoryGroups` constant and the associated `c.Set` call if groups are no longer used anywhere.

## Individual Comments

### Comment 1
<location path="base/database/utils.go" line_range="244-248" />
<code_context>
-	db := DB.Where("si.workspaces @> ANY (?::jsonb[])", groups[utils.KeyGrouped])
-	if _, ok := groups[utils.KeyUngrouped]; ok {
-		db = db.Or("si.workspaces = '[]'")
+func ApplyInventoryWorkspaceFilter(tx *gorm.DB, workspaceIDs []string) *gorm.DB {
+	if len(workspaceIDs) == 0 {
+		utils.LogWarn("there should always be some workspaces, at least root workspace")
 	}
-	return tx.Where(db)
+	return tx.Where("si.workspace_id IN (?)", workspaceIDs)
 }

</code_context>
<issue_to_address>
**issue:** Applying `IN (?)` with an empty workspaceID slice can lead to invalid SQL or unintended filtering.

With an empty `workspaceIDs`, this still runs `WHERE si.workspace_id IN (?)` with no values. Depending on the dialect/driver this may produce invalid SQL (`IN ()`), or `IN (NULL)` that filters out all rows, which is both unpredictable and implicit. Since you already log for the empty case, consider returning `tx` unchanged (or an explicit hard-deny) when `len(workspaceIDs) == 0` so the behavior is well-defined and intentional.
</issue_to_address>

### Comment 2
<location path="manager/controllers/systems_advisories_view.go" line_range="159-160" />
<code_context>
 		Distinct(systemsAdvisoriesSelect).
 		Joins("LEFT JOIN system_advisories sa ON am.id = sa.advisory_id AND sa.rh_account_id = ? AND sa.status_id = 0", acc)
 	if len(systems) > 0 {
-		query = query.Joins(fmt.Sprintf("%s AND si.inventory_id in (?)", siJoin), systems)
+		query = query.Joins(fmt.Sprintf("%s AND si.inventory_id in (?::uuid)", siJoin), systems)
 	} else {
 		query = query.Joins(siJoin)
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `IN (?::uuid)` with a slice of `SystemID` strings may generate invalid SQL or cast issues.

Because `systems` is a slice of `SystemID` (a `string` alias), GORM will expand `IN (?)` into multiple placeholders. With `(?::uuid)`, the `::uuid` will only apply to the last placeholder, yielding invalid SQL. Since the Go type is already `string`, the cast is usually unnecessary if the DB column is UUID. Either drop the `::uuid` or switch to a single array parameter with `si.inventory_id = ANY (?::uuid[])` if you truly need an explicit cast.
</issue_to_address>

### Comment 3
<location path="manager/middlewares/kessel_test.go" line_range="69" />
<code_context>
 	utils.CoreCfg.KesselAuthClientSecret = originalKesselAuthClientSecret
 }

-func TestProcessWorkspaces(t *testing.T) {
-	id1 := "aaaaaaaa-0000-0000-0000-000000000001"
-	id2 := "bbbbbbbb-0000-0000-0000-000000000002"
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for the new Kessel workspace handling now that processWorkspaces is removed

With `processWorkspaces` and `TestProcessWorkspaces` removed, `hasPermissionKessel` now builds the workspace slice inline and sets `utils.KeyInventoryWorkspaces`. To keep equivalent coverage for the new behavior, please:

- Add a test where the mocked Kessel client returns multiple workspaces and assert the full slice of workspace IDs on the context.
- Add a test where the mocked Kessel client returns zero workspaces and assert a 401 response and that `KeyInventoryWorkspaces` is not set.

These can live alongside `TestHasPermissionKessel` to fully exercise the new representation.

Suggested implementation:

```golang
	"app/base/utils"
	"net/http"
	"net/http/httptest"
	"testing"

	"google.golang.org/grpc"

```

```golang
	utils.CoreCfg.KesselAuthClientSecret = originalKesselAuthClientSecret
}

func TestHasPermissionKessel_MultipleWorkspacesSetsContext(t *testing.T) {
	// arrange: mocked Kessel client returns multiple workspaces
	workspaces := []string{
		"aaaaaaaa-0000-0000-0000-000000000001",
		"bbbbbbbb-0000-0000-0000-000000000002",
	}

	mockClient := &MockKesselClient{
		WorkspacesToReturn: workspaces,
	}

	// newKesselMiddlewareWithClient should follow the same pattern used by existing tests
	middleware := newKesselMiddlewareWithClient(mockClient)

	handlerCalled := false
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handlerCalled = true

		got := r.Context().Value(utils.KeyInventoryWorkspaces)
		if got == nil {
			t.Fatalf("expected %q to be set on context, got nil", utils.KeyInventoryWorkspaces)
		}

		gotSlice, ok := got.([]string)
		if !ok {
			t.Fatalf("expected context value type []string, got %T", got)
		}

		if len(gotSlice) != len(workspaces) {
			t.Fatalf("expected %d workspaces, got %d", len(workspaces), len(gotSlice))
		}

		for i, id := range workspaces {
			if gotSlice[i] != id {
				t.Errorf("workspace[%d]: expected %q, got %q", i, id, gotSlice[i])
			}
		}
	})

	req := httptest.NewRequest(http.MethodGet, "/some/path", nil)
	rr := httptest.NewRecorder()

	// act
	middleware(handler).ServeHTTP(rr, req)

	// assert
	if !handlerCalled {
		t.Errorf("expected wrapped handler to be called")
	}
	if rr.Code != http.StatusOK {
		t.Errorf("expected status %d, got %d", http.StatusOK, rr.Code)
	}
}

func TestHasPermissionKessel_NoWorkspacesReturnsUnauthorized(t *testing.T) {
	// arrange: mocked Kessel client returns zero workspaces
	mockClient := &MockKesselClient{
		WorkspacesToReturn: []string{},
	}

	middleware := newKesselMiddlewareWithClient(mockClient)

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatalf("handler should not be called when there are no workspaces")
	})

	req := httptest.NewRequest(http.MethodGet, "/some/path", nil)
	rr := httptest.NewRecorder()

	// act
	middleware(handler).ServeHTTP(rr, req)

	// assert
	if rr.Code != http.StatusUnauthorized {
		t.Fatalf("expected status %d when no workspaces, got %d", http.StatusUnauthorized, rr.Code)
	}

	// If the middleware only sets KeyInventoryWorkspaces for authorized requests,
	// then the absence of a downstream handler invocation is enough to guarantee
	// it won't be visible to handlers. If the middleware mutates req.Context()
	// even on unauthorized paths, adjust this assertion accordingly.
	if got := req.Context().Value(utils.KeyInventoryWorkspaces); got != nil {
		t.Fatalf("expected %q not to be set on the original request context, got %#v", utils.KeyInventoryWorkspaces, got)
	}
}

```

1. Replace `MockKesselClient` and its `WorkspacesToReturn` field with whatever mock type and configuration your existing `TestHasPermissionKessel` uses to control the list of workspaces returned by the Kessel client.
2. Implement `newKesselMiddlewareWithClient(mockClient)` (or rename this call) to match the constructor/helper already used in your current Kessel middleware tests so the middleware under test is configured with the mocked client.
3. If `hasPermissionKessel` sets `utils.KeyInventoryWorkspaces` onto the context using a different type than `[]string` (e.g. `[]uuid.UUID` or a custom struct), adjust the type assertion and expected slice accordingly in `TestHasPermissionKessel_MultipleWorkspacesSetsContext`.
4. For the “no workspaces” test, if the middleware mutates the request context even on unauthorized responses, instead of checking `req.Context()`, you should assert the absence of `utils.KeyInventoryWorkspaces` in whatever request object the middleware actually passes to the next handler (e.g. by having the handler record the context if it is ever invoked, or by exposing a helper used in other tests).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread base/database/utils.go
Comment thread manager/controllers/systems_advisories_view.go Outdated
Comment thread manager/middlewares/kessel_test.go
@TenSt TenSt force-pushed the stepan/RHINENG-26547-re-apply-workspace-related-code-changes branch from 5f4a256 to 65478f5 Compare June 10, 2026 14:18
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.32530% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.34%. Comparing base (b6f1ae3) to head (65478f5).

Files with missing lines Patch % Lines
listener/upload.go 66.66% 6 Missing and 2 partials ⚠️
base/database/utils.go 36.36% 7 Missing ⚠️
base/database/testing.go 0.00% 6 Missing ⚠️
manager/controllers/system_detail.go 50.00% 2 Missing ⚠️
manager/controllers/systems_advisories_view.go 71.42% 0 Missing and 2 partials ⚠️
manager/controllers/template_systems.go 71.42% 2 Missing ⚠️
manager/controllers/utils.go 75.00% 1 Missing and 1 partial ⚠️
evaluator/advisory_update.go 66.66% 1 Missing ⚠️
manager/middlewares/rbac.go 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2231      +/-   ##
==========================================
- Coverage   58.56%   58.34%   -0.22%     
==========================================
  Files         140      140              
  Lines        8973     8967       -6     
==========================================
- Hits         5255     5232      -23     
- Misses       3166     3187      +21     
+ Partials      552      548       -4     
Flag Coverage Δ
unittests 58.34% <81.32%> (-0.22%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TenSt

TenSt commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

@TenSt TenSt merged commit 6e84a59 into RedHatInsights:master Jun 11, 2026
8 of 9 checks passed
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.

3 participants