Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2ee4d01
RHINENG-25147: refactor workspaces in DB
Dugowitch Apr 8, 2026
7632358
RHINENG-25147: update create_schema after the refactor
Dugowitch Apr 8, 2026
efc195a
RHINENG-25147: update test_data after migration
Dugowitch Apr 9, 2026
ac28fbb
RHINENG-25147: add SystemWorkspaces
Dugowitch Apr 21, 2026
10b75d8
RHINENG-25147: update columns in export tests
Dugowitch Apr 21, 2026
5a06fea
RHINENG-25147: set invenotry workspaces in context
Dugowitch Apr 21, 2026
e41cfdf
RHINENG-25147: replace groups with workspaces: PostSystemsAdvisories
Dugowitch Apr 22, 2026
e9a3c76
RHINENG-25147: replace groups with workspaces: database.Systems()
Dugowitch Apr 22, 2026
ed5f74d
RHINENG-25146: replace groups with workspaces: PackagesExportHandler,…
Dugowitch Apr 23, 2026
4f4771a
RHINENG-25147: replace groups with workspaces: SystemDetailHandler, S…
Dugowitch Apr 23, 2026
ea0178f
RHINENG-25147: replace groups with workspaces: SystemsListHandler, Sy…
Dugowitch Apr 23, 2026
00cca6b
RHINENG-25147: replace groups with workspaces: SystemTagListHandler
Dugowitch Apr 23, 2026
538139f
RHINENG-25147: replace groups with workspaces: TemplateSystemsListHan…
Dugowitch Apr 23, 2026
a8f97e8
RHINENG-25147: replace groups with workspaces: TemplatesListHandler
Dugowitch Apr 23, 2026
ece5729
RHINENG-25147: replace groups with workspaces: TemplateSystemsUpdateH…
Dugowitch Apr 23, 2026
62323b0
RHINENG-25147: replace groups with workspaces: AdvisoriesListHandler,…
Dugowitch Apr 23, 2026
3e69fed
RHINENG-25147: replace groups with workspaces: AdvisorySystemsListHan…
Dugowitch Apr 23, 2026
27c3b03
RHINENG-25147: replace groups with workspaces: PostAdvisoriesSystems
Dugowitch Apr 23, 2026
5d32fcd
RHINENG-25147: replace groups with workspaces: SystemAdvisoriesHandle…
Dugowitch Apr 23, 2026
2017a32
RHINENG-25147: remove old database.SystemAdvisories()
Dugowitch Apr 23, 2026
3b148f6
RHINENG-25147: replace groups with workspaces: PackageSystemsListHand…
Dugowitch Apr 23, 2026
89acf35
RHINENG-25147: replace groups with workspaces: PackageVersionsListHan…
Dugowitch Apr 23, 2026
d980a15
RHINENG-25147: replace groups with workspaces: SystemPackagesHandler,…
Dugowitch Apr 23, 2026
ac6ea05
RHINENG-25147: remove old database.SystemPackages()
Dugowitch Apr 23, 2026
e6c1a77
RHINENG-25147: remove old database.Systems()
Dugowitch Apr 23, 2026
9efc7ca
RHINENG-25147: remove old database.ApplyInventoryWorkspaceFilter()
Dugowitch Apr 23, 2026
97577f9
RHINENG-25147: refactor filtering by group_name
Dugowitch Apr 23, 2026
3b8ad95
RHINENG-25147: refactor workspaces in system_inventory model
Dugowitch Apr 23, 2026
517a066
RHINENG-25147: drop workspaces column
Dugowitch Apr 28, 2026
0154432
RHINENG-25147: remove SystemGroups
Dugowitch Apr 28, 2026
86d7306
RHINENG-25147: update columns in export tests
Dugowitch Apr 28, 2026
9e80e2d
RHINENG-25147: remove inventory groups from context
Dugowitch Apr 28, 2026
f987ecd
RHINENG-25147: remove workspaces from model
Dugowitch Apr 28, 2026
d19ec59
RHINENG-25147: update openapi
Dugowitch Apr 21, 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
34 changes: 12 additions & 22 deletions base/database/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ func (j joinsT) apply(tx *gorm.DB) *gorm.DB {
return tx
}

func Systems(tx *gorm.DB, accountID int, groups map[string]string, joins ...join) *gorm.DB {
func Systems(tx *gorm.DB, accountID int, workspaceIDs []string, joins ...join) *gorm.DB {
tx = tx.Table("system_inventory si").
Joins("JOIN system_patch spatch ON si.id = spatch.system_id AND si.rh_account_id = spatch.rh_account_id").
Where("si.rh_account_id = ?", accountID)
tx = (joinsT)(joins).apply(tx)
return ApplyInventoryWorkspaceFilter(tx, groups)
return ApplyInventoryWorkspaceFilter(tx, workspaceIDs)
}

func SystemAdvisories(tx *gorm.DB, accountID int, groups map[string]string, joins ...join) *gorm.DB {
tx = Systems(tx, accountID, groups).
func SystemAdvisories(tx *gorm.DB, accountID int, workspaceIDs []string, joins ...join) *gorm.DB {
tx = Systems(tx, accountID, workspaceIDs).
Joins("JOIN system_advisories sa on sa.system_id = si.id AND sa.rh_account_id = ?", accountID)
return (joinsT)(joins).apply(tx)
}
Expand All @@ -46,8 +46,8 @@ func SystemPackagesShort(tx *gorm.DB, accountID int, joins ...join) *gorm.DB {
return (joinsT)(joins).apply(tx)
}

func SystemPackages(tx *gorm.DB, accountID int, groups map[string]string, joins ...join) *gorm.DB {
tx = Systems(tx, accountID, groups).
func SystemPackages(tx *gorm.DB, accountID int, workspaceIDs []string, joins ...join) *gorm.DB {
tx = Systems(tx, accountID, workspaceIDs).
Joins("JOIN system_package2 spkg on spkg.system_id = si.id AND spkg.rh_account_id = ?", accountID).
Joins("JOIN package p on p.id = spkg.package_id").
Joins("JOIN package_name pn on pn.id = spkg.name_id")
Expand All @@ -65,9 +65,9 @@ func PackageByName(tx *gorm.DB, pkgName string, joins ...join) *gorm.DB {
return (joinsT)(joins).apply(tx)
}

func SystemAdvisoriesByInventoryID(tx *gorm.DB, accountID int, groups map[string]string, inventoryID string,
func SystemAdvisoriesByInventoryID(tx *gorm.DB, accountID int, workspaceIDs []string, inventoryID string,
joins ...join) *gorm.DB {
tx = SystemAdvisories(tx, accountID, groups).Where("si.inventory_id = ?::uuid", inventoryID)
tx = SystemAdvisories(tx, accountID, workspaceIDs).Where("si.inventory_id = ?::uuid", inventoryID)
return (joinsT)(joins).apply(tx)
}

Expand Down Expand Up @@ -240,21 +240,11 @@ func ReadReplicaConfigured() bool {
return len(utils.CoreCfg.DBReadReplicaHost) > 0 && utils.CoreCfg.DBReadReplicaPort != 0
}

func ApplyInventoryWorkspaceFilter(tx *gorm.DB, groups map[string]string) *gorm.DB {
if _, ok := groups[utils.KeyGrouped]; !ok {
if _, ok := groups[utils.KeyUngrouped]; ok {
// show only systems with '[]' group
return tx.Where("si.workspaces = '[]'")
}
// return query without WHERE if there are no groups
return tx
}

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)
}

// LEFT JOIN templates to spatch (system_patch)
Expand Down
31 changes: 12 additions & 19 deletions base/database/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,31 @@ var (
// counts of systems from system_inventory (+ system_patch join in Systems())
nGroup1 int64 = 7
nGroup2 int64 = 2
nUngrouped int64 = 7
nUngrouped int64 = 9
nAll int64 = 18
)

var testCases = []map[int64]map[string]string{
{nGroup1: {utils.KeyGrouped: `{"[{\"id\":\"inventory-group-1\"}]"}`}},
{nGroup2: {utils.KeyGrouped: `{"[{\"id\":\"inventory-group-2\"}]"}`}},
{nGroup1 + nGroup2: {utils.KeyGrouped: `{"[{\"id\":\"inventory-group-1\"}]","[{\"id\":\"inventory-group-2\"}]"}`}},
{nGroup1 + nUngrouped: {
utils.KeyGrouped: `{"[{\"id\":\"inventory-group-1\"}]"}`,
utils.KeyUngrouped: "[]",
}},
{nUngrouped: {
utils.KeyGrouped: `{"[{\"id\":\"non-existing-group\"}]"}`,
utils.KeyUngrouped: "[]",
}},
{0: {utils.KeyGrouped: `{"[{\"id\":\"non-existing-group\"}]"}`}},
{nUngrouped: {utils.KeyUngrouped: "[]"}},
{nAll: {}},
{nAll: nil},
var testCases = []map[int64][]string{
{nGroup1: {"inventory-group-1"}},
{nGroup2: {"inventory-group-2"}},
{nGroup1 + nGroup2: {"inventory-group-1", "inventory-group-2"}},
{nGroup1 + nUngrouped: {"inventory-group-1", "root-workspace"}},
{nUngrouped: {"non-existing-group", "root-workspace"}},
{0: {"non-existing-group"}},
{nUngrouped: {"root-workspace"}},
{nAll: {"inventory-group-1", "inventory-group-2", "root-workspace"}},
}

func TestApplyInventoryWorkspaceFilter(t *testing.T) {
utils.SkipWithoutDB(t)
Configure()

for _, tc := range testCases {
for expectedCount, groups := range tc {
for expectedCount, workspaceIDs := range tc {
var count int64
ApplyInventoryWorkspaceFilter(DB.Table("system_inventory si").
Joins("JOIN system_patch spatch ON si.id = spatch.system_id AND si.rh_account_id = spatch.rh_account_id"),
groups).Count(&count)
workspaceIDs).Count(&count)
assert.Equal(t, expectedCount, count)
}
}
Expand Down
32 changes: 0 additions & 32 deletions base/inventory/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package inventory

import (
"app/base/types"
"database/sql/driver"
"encoding/json"
"errors"

"github.com/google/uuid"
)
Expand Down Expand Up @@ -86,35 +83,6 @@ type Group struct {
Name string `json:"name"`
}

// Groups is a slice of Group that implements driver.Valuer and sql.Scanner
// for storing/loading as JSONB in the database (e.g. system_inventory.workspaces).
type Groups []Group

// Value implements driver.Valuer for GORM: marshal to JSON for DB write.
func (g *Groups) Value() (driver.Value, error) {
if g == nil {
return nil, nil
}
return json.Marshal(g)
}

// Scan implements sql.Scanner for GORM: unmarshal from JSON on DB read.
func (g *Groups) Scan(value interface{}) error {
if value == nil {
*g = nil
return nil
}
b, ok := value.([]byte)
if !ok {
return errors.New("inventory.Groups: type assertion to []byte failed")
}
if len(b) == 0 {
*g = nil
return nil
}
return json.Unmarshal(b, g)
}

type Workloads struct {
Sap SapWorkload `json:"sap,omitempty"`
Ansible AnsibleWorkload `json:"ansible,omitempty"`
Expand Down
8 changes: 4 additions & 4 deletions base/models/models.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package models

import (
"app/base/inventory"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -71,9 +70,10 @@ type SystemInventory struct {
BuiltPkgcache bool `gorm:"column:built_pkgcache"`
Arch *string
Bootc bool
Tags []byte `gorm:"column:tags"`
Created time.Time // set by trigger system_platform_insert_trigger
Workspaces *inventory.Groups `gorm:"column:workspaces"`
Tags []byte `gorm:"column:tags"`
Created time.Time // set by trigger system_platform_insert_trigger
WorkspaceID *string `gorm:"column:workspace_id"`
WorkspaceName *string `gorm:"column:workspace_name"`
StaleTimestamp *time.Time
StaleWarningTimestamp *time.Time
CulledTimestamp *time.Time
Expand Down
14 changes: 6 additions & 8 deletions base/utils/gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import (
)

const (
KeyApiver = "apiver"
KeyAccount = "account"
KeyOrgID = "org_id"
KeyUser = "user"
KeySystem = "system_cn"
KeyInventoryGroups = "inventoryGroups"
KeyGrouped = "grouped"
KeyUngrouped = "ungrouped"
KeyApiver = "apiver"
KeyAccount = "account"
KeyOrgID = "org_id"
KeyUser = "user"
KeySystem = "system_cn"
KeyInventoryWorkspaces = "workspaceIDs"
// ReadHeaderTimeout same as nginx default
ReadHeaderTimeout = 60 * time.Second
)
Expand Down
13 changes: 13 additions & 0 deletions database_admin/migrations/153_simplify_workspaces.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
ALTER TABLE system_inventory
ADD COLUMN workspace_id TEXT CHECK (NOT empty(workspace_id)),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

workspace_id is uuid so we should use UUID type here - 2 reasons (at least):

  • you won't need empty() check,
  • and mostly UUID takes 16 bytes, while as text it takes 36 bytes .

ADD COLUMN workspace_name TEXT CHECK (NOT empty(workspace_name));

UPDATE system_inventory
SET workspace_id = workspaces->0->>'id',
workspace_name = workspaces->0->>'name';

CREATE INDEX IF NOT EXISTS system_inventory_workspace_id_index ON system_inventory (workspace_id);
CREATE INDEX IF NOT EXISTS system_inventory_workspace_name_index ON system_inventory (workspace_name);

ALTER TABLE system_inventory
DROP COLUMN workspaces;
8 changes: 5 additions & 3 deletions database_admin/schema/create_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations


INSERT INTO schema_migrations
VALUES (152, false);
VALUES (153, false);

-- ---------------------------------------------------------------------------
-- Functions
Expand Down Expand Up @@ -592,7 +592,8 @@ CREATE TABLE IF NOT EXISTS system_inventory
ansible_workload_controller_version TEXT CHECK (NOT empty(ansible_workload_controller_version)),
mssql_workload BOOLEAN NOT NULL DEFAULT false,
mssql_workload_version TEXT CHECK (NOT empty(mssql_workload_version)),
workspaces JSONB,
workspace_id TEXT CHECK (NOT empty(workspace_id)),
workspace_name TEXT CHECK (NOT empty(workspace_name)),
PRIMARY KEY (rh_account_id, id),
UNIQUE (rh_account_id, inventory_id)
) PARTITION BY HASH (rh_account_id);
Expand Down Expand Up @@ -624,7 +625,8 @@ SELECT create_table_partition_triggers('system_inventory_on_update',
CREATE INDEX IF NOT EXISTS system_inventory_inventory_id_idx ON system_inventory (inventory_id);
CREATE INDEX IF NOT EXISTS system_inventory_tags_index ON system_inventory USING GIN (tags JSONB_PATH_OPS);
CREATE INDEX IF NOT EXISTS system_inventory_stale_timestamp_index ON system_inventory (stale_timestamp);
CREATE INDEX IF NOT EXISTS system_inventory_workspaces_index ON system_inventory USING GIN (workspaces);
CREATE INDEX IF NOT EXISTS system_inventory_workspace_id_index ON system_inventory (workspace_id);
CREATE INDEX IF NOT EXISTS system_inventory_workspace_name_index ON system_inventory (workspace_name);

CREATE TABLE IF NOT EXISTS deleted_system
(
Expand Down
Loading
Loading