From 55f84a97a931018711a594a1b10a6bbcd4394526 Mon Sep 17 00:00:00 2001 From: Don Browne Date: Mon, 10 Jun 2024 11:34:39 +0100 Subject: [PATCH] Define evaluation history schemas (#3565) Fixes #3555 Implements the new database schema for storing evaluation history. Compared with the existing tables for profile/rule status, this will track every single evaluation, and not just the most recent state. This will be used to build an API for querying the history of evaluations for a given rule/entity pair. Queries and logic to fill the tables will be implemented in the next PR. --- .../000064_evaluation_history.down.sql | 24 +++++++ .../000064_evaluation_history.up.sql | 67 +++++++++++++++++++ internal/db/models.go | 43 ++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 database/migrations/000064_evaluation_history.down.sql create mode 100644 database/migrations/000064_evaluation_history.up.sql diff --git a/database/migrations/000064_evaluation_history.down.sql b/database/migrations/000064_evaluation_history.down.sql new file mode 100644 index 0000000000..06415f9113 --- /dev/null +++ b/database/migrations/000064_evaluation_history.down.sql @@ -0,0 +1,24 @@ +-- Copyright 2024 Stacklok, Inc +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +BEGIN; + +DROP TABLE IF EXISTS rule_entity_evaluations; +DROP TABLE IF EXISTS evaluation_history; +DROP TABLE IF EXISTS evaluation_instance; +DROP TABLE IF EXISTS latest_evaluation_state; +DROP TABLE IF EXISTS remediation_events; +DROP TABLE IF EXISTS alert_events; + +COMMIT; diff --git a/database/migrations/000064_evaluation_history.up.sql b/database/migrations/000064_evaluation_history.up.sql new file mode 100644 index 0000000000..296802cc37 --- /dev/null +++ b/database/migrations/000064_evaluation_history.up.sql @@ -0,0 +1,67 @@ +-- Copyright 2024 Stacklok, Inc +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +BEGIN; + +CREATE TABLE rule_entity_evaluations( + id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + rule_id UUID NOT NULL REFERENCES rule_instances(id) ON DELETE CASCADE, + -- Copying the same pattern of linking to entity types as used in rule_evaluations + repository_id UUID REFERENCES repositories(id), + pull_request_id UUID REFERENCES pull_requests(id), + artifact_id UUID REFERENCES artifacts(id), + UNIQUE(rule_id, repository_id), + UNIQUE(rule_id, pull_request_id), + UNIQUE(rule_id, artifact_id), + -- exactly one entity ID column must be set + CONSTRAINT one_entity_id CHECK (num_nonnulls(repository_id, artifact_id, pull_request_id) = 1) +); + +CREATE TABLE evaluation_history( + id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + rule_entity_id UUID NOT NULL REFERENCES rule_entity_evaluations(id) ON DELETE CASCADE, + status eval_status_types NOT NULL, + details TEXT NOT NULL +); + +CREATE TABLE evaluation_instance( + evaluation_id UUID NOT NULL REFERENCES evaluation_history(id) ON DELETE CASCADE, + evaluation_time TIMESTAMP NOT NULL DEFAULT NOW(), + PRIMARY KEY (evaluation_id, evaluation_time) +); + +CREATE TABLE latest_evaluation_state( + rule_entity_id UUID NOT NULL PRIMARY KEY REFERENCES rule_entity_evaluations(id) ON DELETE CASCADE, + evaluation_history_id UUID NOT NULL REFERENCES evaluation_history(id) +); + +CREATE TABLE remediation_events( + id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + evaluation_id UUID NOT NULL REFERENCES evaluation_history(id), + status remediation_status_types NOT NULL, + details TEXT NOT NULL, + metadata JSONB NOT NULL DEFAULT '{}'::JSONB, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +CREATE TABLE alert_events( + id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + evaluation_id UUID NOT NULL REFERENCES evaluation_history(id), + status alert_status_types NOT NULL, + details TEXT NOT NULL, + metadata JSONB NOT NULL DEFAULT '{}'::JSONB, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +COMMIT; diff --git a/internal/db/models.go b/internal/db/models.go index 823a4dd162..49e5423626 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -418,6 +418,15 @@ func (ns NullSeverity) Value() (driver.Value, error) { return string(ns.Severity), nil } +type AlertEvent struct { + ID uuid.UUID `json:"id"` + EvaluationID uuid.UUID `json:"evaluation_id"` + Status AlertStatusTypes `json:"status"` + Details string `json:"details"` + Metadata json.RawMessage `json:"metadata"` + CreatedAt time.Time `json:"created_at"` +} + type Artifact struct { ID uuid.UUID `json:"id"` RepositoryID uuid.NullUUID `json:"repository_id"` @@ -472,6 +481,18 @@ type EntityProfileRule struct { CreatedAt time.Time `json:"created_at"` } +type EvaluationHistory struct { + ID uuid.UUID `json:"id"` + RuleEntityID uuid.UUID `json:"rule_entity_id"` + Status EvalStatusTypes `json:"status"` + Details string `json:"details"` +} + +type EvaluationInstance struct { + EvaluationID uuid.UUID `json:"evaluation_id"` + EvaluationTime time.Time `json:"evaluation_time"` +} + type Feature struct { Name string `json:"name"` Settings json.RawMessage `json:"settings"` @@ -489,6 +510,11 @@ type FlushCache struct { ProjectID uuid.NullUUID `json:"project_id"` } +type LatestEvaluationState struct { + RuleEntityID uuid.UUID `json:"rule_entity_id"` + EvaluationHistoryID uuid.UUID `json:"evaluation_history_id"` +} + type MigrationProfileBackfillLog struct { ProfileID uuid.UUID `json:"profile_id"` } @@ -581,6 +607,15 @@ type PullRequest struct { UpdatedAt time.Time `json:"updated_at"` } +type RemediationEvent struct { + ID uuid.UUID `json:"id"` + EvaluationID uuid.UUID `json:"evaluation_id"` + Status RemediationStatusTypes `json:"status"` + Details string `json:"details"` + Metadata json.RawMessage `json:"metadata"` + CreatedAt time.Time `json:"created_at"` +} + type Repository struct { ID uuid.UUID `json:"id"` Provider string `json:"provider"` @@ -627,6 +662,14 @@ type RuleDetailsRemediate struct { Metadata json.RawMessage `json:"metadata"` } +type RuleEntityEvaluation struct { + ID uuid.UUID `json:"id"` + RuleID uuid.UUID `json:"rule_id"` + RepositoryID uuid.NullUUID `json:"repository_id"` + PullRequestID uuid.NullUUID `json:"pull_request_id"` + ArtifactID uuid.NullUUID `json:"artifact_id"` +} + type RuleEvaluation struct { ID uuid.UUID `json:"id"` Entity Entities `json:"entity"`