Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions base/database/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ const (
TestWorkspace1ID = "aaaaaaaa-0000-0000-0000-000000000001"
)

func TestWorkspace1IDPtr() *uuid.UUID {
id := uuid.MustParse(TestWorkspace1ID)
return &id
func TestWorkspace1UUID() uuid.UUID {
return uuid.MustParse(TestWorkspace1ID)
}

func TestWorkspace1NamePtr() *string {
Expand Down
8 changes: 4 additions & 4 deletions base/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,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
WorkspaceID *uuid.UUID `gorm:"column:workspace_id"`
WorkspaceName *string `gorm:"column:workspace_name"`
Tags []byte `gorm:"column:tags"`
Created time.Time // set by trigger system_platform_insert_trigger
WorkspaceID uuid.UUID `gorm:"column:workspace_id"`
WorkspaceName *string `gorm:"column:workspace_name"`
StaleTimestamp *time.Time
StaleWarningTimestamp *time.Time
CulledTimestamp *time.Time
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
ALTER TABLE system_inventory ALTER COLUMN workspace_id DROP NOT NULL;

CREATE OR REPLACE FUNCTION refresh_account_advisory_caches_multi(advisory_ids_in INTEGER[] DEFAULT NULL,
rh_account_id_in INTEGER DEFAULT NULL)
RETURNS VOID AS
$refresh_account_advisory$
BEGIN
PERFORM aa.rh_account_id, aa.workspace_id, aa.advisory_id
FROM account_advisory aa
WHERE (aa.advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (aa.rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL)
FOR UPDATE OF aa;

WITH current_counts AS (
SELECT sa.advisory_id, sa.rh_account_id, si.workspace_id,
count(sa.*) FILTER (WHERE sa.status_id = 0) AS systems_installable,
count(sa.*) AS systems_applicable
FROM system_advisories sa
JOIN system_inventory si
ON sa.rh_account_id = si.rh_account_id AND sa.system_id = si.id
JOIN system_patch sp
ON si.id = sp.system_id AND sp.rh_account_id = si.rh_account_id
WHERE sp.last_evaluation IS NOT NULL
AND si.stale = FALSE
AND si.workspace_id IS NOT NULL
AND (sa.advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (si.rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL)
GROUP BY sa.advisory_id, sa.rh_account_id, si.workspace_id
),
upserted AS (
INSERT INTO account_advisory (advisory_id, rh_account_id, workspace_id, systems_installable, systems_applicable)
SELECT advisory_id, rh_account_id, workspace_id, systems_installable, systems_applicable
FROM current_counts
ON CONFLICT (rh_account_id, workspace_id, advisory_id) DO UPDATE SET
systems_installable = EXCLUDED.systems_installable,
systems_applicable = EXCLUDED.systems_applicable
)
DELETE FROM account_advisory
WHERE (advisory_id, rh_account_id, workspace_id) NOT IN (SELECT advisory_id, rh_account_id, workspace_id FROM current_counts)
AND (advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL);
END;
$refresh_account_advisory$ LANGUAGE plpgsql;
42 changes: 42 additions & 0 deletions database_admin/migrations/167_add_workspace_id_not_null.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
ALTER TABLE system_inventory ALTER COLUMN workspace_id SET NOT NULL;
Comment thread
Dugowitch marked this conversation as resolved.

CREATE OR REPLACE FUNCTION refresh_account_advisory_caches_multi(advisory_ids_in INTEGER[] DEFAULT NULL,
rh_account_id_in INTEGER DEFAULT NULL)
RETURNS VOID AS
$refresh_account_advisory$
BEGIN
PERFORM aa.rh_account_id, aa.workspace_id, aa.advisory_id
FROM account_advisory aa
WHERE (aa.advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (aa.rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL)
FOR UPDATE OF aa;

WITH current_counts AS (
SELECT sa.advisory_id, sa.rh_account_id, si.workspace_id,
count(sa.*) FILTER (WHERE sa.status_id = 0) AS systems_installable,
count(sa.*) AS systems_applicable
FROM system_advisories sa
JOIN system_inventory si
ON sa.rh_account_id = si.rh_account_id AND sa.system_id = si.id
JOIN system_patch sp
ON si.id = sp.system_id AND sp.rh_account_id = si.rh_account_id
WHERE sp.last_evaluation IS NOT NULL
AND si.stale = FALSE
AND (sa.advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (si.rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL)
GROUP BY sa.advisory_id, sa.rh_account_id, si.workspace_id
),
upserted AS (
INSERT INTO account_advisory (advisory_id, rh_account_id, workspace_id, systems_installable, systems_applicable)
SELECT advisory_id, rh_account_id, workspace_id, systems_installable, systems_applicable
FROM current_counts
ON CONFLICT (rh_account_id, workspace_id, advisory_id) DO UPDATE SET
systems_installable = EXCLUDED.systems_installable,
systems_applicable = EXCLUDED.systems_applicable
)
DELETE FROM account_advisory
WHERE (advisory_id, rh_account_id, workspace_id) NOT IN (SELECT advisory_id, rh_account_id, workspace_id FROM current_counts)
AND (advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL);
END;
$refresh_account_advisory$ LANGUAGE plpgsql;
5 changes: 2 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 (166, false);
VALUES (167, false);

-- ---------------------------------------------------------------------------
-- Functions
Expand Down Expand Up @@ -192,7 +192,6 @@ BEGIN
ON si.id = sp.system_id AND sp.rh_account_id = si.rh_account_id
WHERE sp.last_evaluation IS NOT NULL
AND si.stale = FALSE
AND si.workspace_id IS NOT NULL
AND (sa.advisory_id = ANY (advisory_ids_in) OR advisory_ids_in IS NULL)
AND (si.rh_account_id = rh_account_id_in OR rh_account_id_in IS NULL)
GROUP BY sa.advisory_id, sa.rh_account_id, si.workspace_id
Expand Down Expand Up @@ -654,7 +653,7 @@ 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)),
workspace_id UUID,
workspace_id UUID NOT NULL,
workspace_name TEXT CHECK (NOT empty(workspace_name)),
crowdstrike_workload BOOLEAN NOT NULL DEFAULT false,
ibm_db2_workload BOOLEAN NOT NULL DEFAULT false,
Expand Down
24 changes: 6 additions & 18 deletions evaluator/advisory_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"app/base/utils"
"time"

"github.com/google/uuid"
"github.com/pkg/errors"
)

Expand All @@ -30,22 +29,6 @@ func getChangedAdvisoryIDs(advisoriesByName extendedAdvisoryMap) []int64 {
return ids
}

func createAdvisoryUpdateEvent(system *models.SystemPlatformV2, advisoryIDs []int64) mqueue.AdvisoryUpdateEvent {
var workspaceID uuid.UUID
if system.Inventory.WorkspaceID != nil {
workspaceID = *system.Inventory.WorkspaceID
} else {
utils.LogWarn("inventoryID", system.GetInventoryID(), "no workspace for system")
}

return mqueue.AdvisoryUpdateEvent{
RhAccountID: system.Inventory.RhAccountID,
WorkspaceID: workspaceID,
AdvisoryIDs: advisoryIDs,
ProducedAt: types.Rfc3339Timestamp(time.Now()),
}
}

func publishAdvisoryUpdates(system *models.SystemPlatformV2, advisoriesByName extendedAdvisoryMap) error {
if advisoryUpdatePublisher == nil {
return nil
Expand All @@ -63,7 +46,12 @@ func publishAdvisoryUpdates(system *models.SystemPlatformV2, advisoriesByName ex
return nil
}

event := createAdvisoryUpdateEvent(system, advisoryIDs)
event := mqueue.AdvisoryUpdateEvent{
RhAccountID: system.Inventory.RhAccountID,
WorkspaceID: system.Inventory.WorkspaceID,
AdvisoryIDs: advisoryIDs,
ProducedAt: types.Rfc3339Timestamp(time.Now()),
}
if err := mqueue.SendMessages(base.Context, advisoryUpdatePublisher, &mqueue.AdvisoryUpdateEvents{event}); err != nil {
return errors.Wrap(err, "writing advisory update events")
}
Expand Down
14 changes: 10 additions & 4 deletions evaluator/advisory_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"app/base/database"
"app/base/models"
"app/base/mqueue"
"app/base/types"
"app/base/utils"
"testing"
"time"

"github.com/bytedance/sonic"
"github.com/google/uuid"
Expand Down Expand Up @@ -34,15 +36,19 @@ func TestCreateAdvisoryUpdateEvent(t *testing.T) {
ID: 1,
RhAccountID: rhAccountID,
InventoryID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
WorkspaceID: &wsID,
WorkspaceID: wsID,
WorkspaceName: &wsName,
},
Patch: models.SystemPatch{},
}

changedAdvisoryIDs := []int64{1, 2}

event := createAdvisoryUpdateEvent(system, changedAdvisoryIDs)
event := mqueue.AdvisoryUpdateEvent{
RhAccountID: system.Inventory.RhAccountID,
WorkspaceID: system.Inventory.WorkspaceID,
AdvisoryIDs: changedAdvisoryIDs,
ProducedAt: types.Rfc3339Timestamp(time.Now()),
}
assert.Equal(t, rhAccountID, event.RhAccountID)
assert.Equal(t, wsID, event.WorkspaceID)
assert.ElementsMatch(t, changedAdvisoryIDs, event.AdvisoryIDs)
Expand All @@ -60,7 +66,7 @@ func TestPublishAdvisoryUpdates(t *testing.T) {
ID: 1,
RhAccountID: rhAccountID,
InventoryID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
WorkspaceID: &wsID,
WorkspaceID: wsID,
WorkspaceName: &wsName,
},
Patch: models.SystemPatch{},
Expand Down
5 changes: 2 additions & 3 deletions listener/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,13 @@ func assertSystemInventoryProfileMatchesHost(t *testing.T, inventoryID uuid.UUID
assert.JSONEq(t, string(utils.MarshalNilToJSONB(host.Tags)), string(inv.Tags))

if len(host.Groups) == 0 {
assert.Nil(t, inv.WorkspaceID)
assert.Equal(t, uuid.Nil, inv.WorkspaceID)
assert.Nil(t, inv.WorkspaceName)
} else {
if hostWorkspaceID := host.Groups[0].ID; hostWorkspaceID != "" {
require.NotNil(t, inv.WorkspaceID)
assert.Equal(t, hostWorkspaceID, inv.WorkspaceID.String())
} else {
require.Nil(t, inv.WorkspaceID)
assert.Equal(t, uuid.Nil, inv.WorkspaceID)
}
if hostWorkspaceName := host.Groups[0].Name; hostWorkspaceName != "" {
require.NotNil(t, inv.WorkspaceName)
Expand Down
37 changes: 19 additions & 18 deletions listener/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,25 +364,26 @@ func updateSystemPlatform(tx *gorm.DB, accountID int, host *Host,
isBootc := len(host.SystemProfile.BootcStatus.Booted.Image) > 0

updatesReqJSONString := string(updatesReqJSON)
var workspaceID *uuid.UUID
var workspaceName *string
if l := len(host.Groups); l > 0 {
workspace := host.Groups[0]
uuid, err := uuid.Parse(workspace.ID)
if err != nil {
utils.LogError("workspaceID", workspace.ID, "invalid workspace UUID")
return nil, errors.New("received invalid workspace UUID")
}
workspaceID = &uuid
if workspace.Name != "" {
workspaceName = &workspace.Name
}
if l != 1 {
utils.LogWarn(
"host_id", host.ID, "org_id", host.OrgID, "workspaces", host.Groups,
"received a host with multiple workspaces",
)
}
l := len(host.Groups)
if l == 0 {
utils.LogError("inventoryID", inventoryID, "workspace UUID missing for system")
return nil, errors.New("workspace UUID missing for system")
}
Comment on lines +369 to +372

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Uploads for hosts with no workspace group now return an error instead of storing the inventory. The existing code accepted such hosts and persisted a NULL workspace ID, so any still-valid host event without Groups[0] is rejected and its upload transaction fails.

Triggers: When an inventory upload contains an empty host.Groups list.

Suggested fix: Ensure the event pipeline supplies a valid workspace before this function, or define and use a non-NULL fallback workspace value instead of rejecting the upload.

workspace := host.Groups[0]
workspaceID, err := uuid.Parse(workspace.ID)
if err != nil {
utils.LogError("workspaceID", workspace.ID, "invalid workspace UUID")
return nil, errors.New("received invalid workspace UUID")
}
if workspace.Name != "" {
workspaceName = &workspace.Name
}
if l != 1 {
utils.LogWarn(
"host_id", host.ID, "org_id", host.OrgID, "workspaces", host.Groups,
"received a host with multiple workspaces",
)
}
systemPlatform := &models.SystemPlatformV2{
Inventory: models.SystemInventory{
Expand Down
5 changes: 2 additions & 3 deletions listener/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ func TestStoreOrUpdateSysPlatform(t *testing.T) {
SatelliteManaged: false,
Created: hostEvent.Host.Created,
Tags: utils.MarshalNilToJSONB(hostEvent.Host.Tags),
WorkspaceID: &workspaceID,
WorkspaceID: workspaceID,
WorkspaceName: workspaceName,
OSName: utils.EmptyToNil(&hostEvent.Host.SystemProfile.OperatingSystem.Name),
OSMajor: &hostEvent.Host.SystemProfile.OperatingSystem.Major,
Expand Down Expand Up @@ -535,7 +535,6 @@ func TestStoreOrUpdateSysPlatform(t *testing.T) {
assert.Contains(t, string(inventoryAfterInsert.Tags), `"key": "env"`)
assert.Contains(t, string(inventoryAfterInsert.Tags), `"value": "prod"`)

require.NotNil(t, inventoryAfterInsert.WorkspaceID)
assert.Equal(t, hostEvent.Host.Groups[0].ID, inventoryAfterInsert.WorkspaceID.String())
require.NotNil(t, inventoryAfterInsert.WorkspaceName)
assert.Equal(t, hostEvent.Host.Groups[0].Name, *inventoryAfterInsert.WorkspaceName)
Expand Down Expand Up @@ -578,7 +577,7 @@ func TestStoreOrUpdateSysPlatform(t *testing.T) {
SatelliteManaged: true,
Created: hostEvent.Host.Created,
Tags: utils.MarshalNilToJSONB(hostEvent.Host.Tags),
WorkspaceID: &workspaceID,
WorkspaceID: workspaceID,
WorkspaceName: workspaceName,
OSName: utils.EmptyToNil(&hostEvent.Host.SystemProfile.OperatingSystem.Name),
OSMajor: &hostEvent.Host.SystemProfile.OperatingSystem.Major,
Expand Down
6 changes: 3 additions & 3 deletions manager/controllers/common_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ type SystemTags struct {
}

type SystemGroups struct {
Groups SystemGroupsList `json:"groups" csv:"groups" query:"CASE WHEN si.workspace_id IS NOT NULL THEN jsonb_build_array(jsonb_build_object('id', si.workspace_id, 'name', si.workspace_name)) ELSE '[]'::jsonb END" order_query:"si.workspace_name"`
Groups SystemGroupsList `json:"groups" csv:"groups" query:"jsonb_build_array(jsonb_build_object('id', si.workspace_id, 'name', si.workspace_name))" order_query:"si.workspace_name"`
}

type SystemWorkspace struct {
WorkspaceID *uuid.UUID `json:"workspace_id" csv:"workspace_id" query:"si.workspace_id" gorm:"column:workspace_id"`
WorkspaceName *string `json:"workspace_name" csv:"workspace_name" query:"si.workspace_name" gorm:"column:workspace_name"`
WorkspaceID uuid.UUID `json:"workspace_id" csv:"workspace_id" query:"si.workspace_id" gorm:"column:workspace_id"`
WorkspaceName *string `json:"workspace_name" csv:"workspace_name" query:"si.workspace_name" gorm:"column:workspace_name"`
}

// baseline attributes are obsoleted and we keep them only for backward API compatibility
Expand Down
2 changes: 1 addition & 1 deletion manager/controllers/template_systems_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func testUpdateTemplateBadRequest(t *testing.T, satelliteManaged, bootc bool) {
RhAccountID: templateAccount,
DisplayName: "template_bad_request_test",
Tags: []byte("[]"),
WorkspaceID: database.TestWorkspace1IDPtr(),
WorkspaceID: database.TestWorkspace1UUID(),
WorkspaceName: database.TestWorkspace1NamePtr(),
BuiltPkgcache: true,
SatelliteManaged: satelliteManaged,
Expand Down
2 changes: 1 addition & 1 deletion tasks/system_culling/system_culling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestCullSystems(t *testing.T) {
RhAccountID: 1,
DisplayName: invID,
Tags: []byte("[]"),
WorkspaceID: database.TestWorkspace1IDPtr(),
WorkspaceID: database.TestWorkspace1UUID(),
WorkspaceName: database.TestWorkspace1NamePtr(),
CulledTimestamp: &staleDate,
}
Expand Down
2 changes: 1 addition & 1 deletion turnpike/controllers/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestInitDelete(t *testing.T) {
RhAccountID: 1,
DisplayName: del,
Tags: []byte("[]"),
WorkspaceID: database.TestWorkspace1IDPtr(),
WorkspaceID: database.TestWorkspace1UUID(),
WorkspaceName: database.TestWorkspace1NamePtr(),
}
assert.NoError(t, database.DB.Create(&inv).Error)
Expand Down
Loading