Commit b969134c authored by Sarah Yasonik's avatar Sarah Yasonik Committed by Peter Leitzen

Add table for project alert http integrations

Adds a new table and model to represent expected
url/token combos for alerting. This table will
house integrations which have generic alert
payload formatting and custom mappings, once custom
mappings are available.
parent 0bcf479a
# frozen_string_literal: true
module AlertManagement
class HttpIntegration < ApplicationRecord
belongs_to :project, inverse_of: :alert_management_http_integrations
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm'
validates :project, presence: true
validates :active, inclusion: { in: [true, false] }
validates :token, presence: true
validates :name, presence: true, length: { maximum: 255 }
validates :endpoint_identifier, presence: true, length: { maximum: 255 }
validates :endpoint_identifier, uniqueness: { scope: [:project_id, :active] }, if: :active?
before_validation :prevent_token_assignment
before_validation :ensure_token
private
def prevent_token_assignment
if token.present? && token_changed?
self.token = nil
self.encrypted_token = encrypted_token_was
self.encrypted_token_iv = encrypted_token_iv_was
end
end
def ensure_token
self.token = generate_token if token.blank?
end
def generate_token
SecureRandom.hex
end
end
end
......@@ -270,6 +270,7 @@ class Project < ApplicationRecord
has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project
has_many :alert_management_http_integrations, class_name: 'AlertManagement::HttpIntegration', inverse_of: :project
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
......
---
title: Add table for alert http integrations for project
merge_request: 43634
author:
type: added
# frozen_string_literal: true
class CreateAlertManagementHttpIntegrations < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
UNIQUE_INDEX = 'index_http_integrations_on_active_and_project_and_endpoint'
disable_ddl_transaction!
def up
create_table :alert_management_http_integrations, if_not_exists: true do |t|
t.timestamps_with_timezone
t.bigint :project_id, index: true, null: false
t.boolean :active, null: false, default: false
t.text :encrypted_token, null: false
t.text :encrypted_token_iv, null: false
t.text :endpoint_identifier, null: false
t.text :name, null: false
end
add_text_limit :alert_management_http_integrations, :encrypted_token, 255
add_text_limit :alert_management_http_integrations, :encrypted_token_iv, 255
add_text_limit :alert_management_http_integrations, :endpoint_identifier, 255
add_text_limit :alert_management_http_integrations, :name, 255
add_index :alert_management_http_integrations,
[:active, :project_id, :endpoint_identifier],
unique: true,
name: UNIQUE_INDEX,
where: 'active'
end
def down
drop_table :alert_management_http_integrations
end
end
# frozen_string_literal: true
class AddHttpIntegrationsProjectForeignKey < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_foreign_key :alert_management_http_integrations, :projects, column: :project_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
end
end
def down
with_lock_retries do
remove_foreign_key :alert_management_http_integrations, column: :project_id
end
end
end
fc9719e0822d17eacb375b4adb2eac35afba04cafc2bd429c82c502d2fe5f12e
\ No newline at end of file
788fd828a7aa8fb8741f13596f54fc4d9f4f5caeaf34d08aed47bbefe363ae75
\ No newline at end of file
......@@ -8830,6 +8830,31 @@ CREATE SEQUENCE alert_management_alerts_id_seq
ALTER SEQUENCE alert_management_alerts_id_seq OWNED BY alert_management_alerts.id;
CREATE TABLE alert_management_http_integrations (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
project_id bigint NOT NULL,
active boolean DEFAULT false NOT NULL,
encrypted_token text NOT NULL,
encrypted_token_iv text NOT NULL,
endpoint_identifier text NOT NULL,
name text NOT NULL,
CONSTRAINT check_286943b636 CHECK ((char_length(encrypted_token_iv) <= 255)),
CONSTRAINT check_392143ccf4 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_e270820180 CHECK ((char_length(endpoint_identifier) <= 255)),
CONSTRAINT check_f68577c4af CHECK ((char_length(encrypted_token) <= 255))
);
CREATE SEQUENCE alert_management_http_integrations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE alert_management_http_integrations_id_seq OWNED BY alert_management_http_integrations.id;
CREATE TABLE alerts_service_data (
id bigint NOT NULL,
service_id integer NOT NULL,
......@@ -17129,6 +17154,8 @@ ALTER TABLE ONLY alert_management_alert_user_mentions ALTER COLUMN id SET DEFAUL
ALTER TABLE ONLY alert_management_alerts ALTER COLUMN id SET DEFAULT nextval('alert_management_alerts_id_seq'::regclass);
ALTER TABLE ONLY alert_management_http_integrations ALTER COLUMN id SET DEFAULT nextval('alert_management_http_integrations_id_seq'::regclass);
ALTER TABLE ONLY alerts_service_data ALTER COLUMN id SET DEFAULT nextval('alerts_service_data_id_seq'::regclass);
ALTER TABLE ONLY allowed_email_domains ALTER COLUMN id SET DEFAULT nextval('allowed_email_domains_id_seq'::regclass);
......@@ -18048,6 +18075,9 @@ ALTER TABLE ONLY alert_management_alert_user_mentions
ALTER TABLE ONLY alert_management_alerts
ADD CONSTRAINT alert_management_alerts_pkey PRIMARY KEY (id);
ALTER TABLE ONLY alert_management_http_integrations
ADD CONSTRAINT alert_management_http_integrations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY alerts_service_data
ADD CONSTRAINT alerts_service_data_pkey PRIMARY KEY (id);
......@@ -19480,6 +19510,8 @@ CREATE UNIQUE INDEX index_alert_management_alerts_on_project_id_and_iid ON alert
CREATE INDEX index_alert_management_alerts_on_prometheus_alert_id ON alert_management_alerts USING btree (prometheus_alert_id) WHERE (prometheus_alert_id IS NOT NULL);
CREATE INDEX index_alert_management_http_integrations_on_project_id ON alert_management_http_integrations USING btree (project_id);
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id ON alert_management_alert_user_mentions USING btree (alert_management_alert_id) WHERE (note_id IS NULL);
CREATE UNIQUE INDEX index_alert_user_mentions_on_alert_id_and_note_id ON alert_management_alert_user_mentions USING btree (alert_management_alert_id, note_id);
......@@ -20330,6 +20362,8 @@ CREATE UNIQUE INDEX index_group_wiki_repositories_on_disk_path ON group_wiki_rep
CREATE INDEX index_group_wiki_repositories_on_shard_id ON group_wiki_repositories USING btree (shard_id);
CREATE UNIQUE INDEX index_http_integrations_on_active_and_project_and_endpoint ON alert_management_http_integrations USING btree (active, project_id, endpoint_identifier) WHERE active;
CREATE INDEX index_identities_on_saml_provider_id ON identities USING btree (saml_provider_id) WHERE (saml_provider_id IS NOT NULL);
CREATE INDEX index_identities_on_user_id ON identities USING btree (user_id);
......@@ -23521,6 +23555,9 @@ ALTER TABLE ONLY elasticsearch_indexed_namespaces
ALTER TABLE ONLY vulnerability_occurrence_identifiers
ADD CONSTRAINT fk_rails_be2e49e1d0 FOREIGN KEY (identifier_id) REFERENCES vulnerability_identifiers(id) ON DELETE CASCADE;
ALTER TABLE ONLY alert_management_http_integrations
ADD CONSTRAINT fk_rails_bec49f52cc FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_occurrences
ADD CONSTRAINT fk_rails_bf5b788ca7 FOREIGN KEY (scanner_id) REFERENCES vulnerability_scanners(id) ON DELETE CASCADE;
......
# frozen_string_literal: true
FactoryBot.define do
factory :alert_management_http_integration, class: 'AlertManagement::HttpIntegration' do
project
active { true }
name { 'DataDog' }
endpoint_identifier { SecureRandom.hex(4) }
trait :inactive do
active { false }
end
end
end
......@@ -539,6 +539,7 @@ project:
- product_analytics_events
- pipeline_artifacts
- terraform_states
- alert_management_http_integrations
award_emoji:
- awardable
- user
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AlertManagement::HttpIntegration do
let_it_be(:project) { create(:project) }
subject(:integration) { build(:alert_management_http_integration) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_presence_of(:endpoint_identifier) }
it { is_expected.to validate_length_of(:endpoint_identifier).is_at_most(255) }
context 'when active' do
# Using `create` instead of `build` the integration so `token` is set.
# Uniqueness spec saves integration with `validate: false` otherwise.
subject { create(:alert_management_http_integration) }
it { is_expected.to validate_uniqueness_of(:endpoint_identifier).scoped_to(:project_id, :active) }
end
context 'when inactive' do
subject { create(:alert_management_http_integration, :inactive) }
it { is_expected.not_to validate_uniqueness_of(:endpoint_identifier).scoped_to(:project_id, :active) }
end
end
describe '#token' do
subject { integration.token }
shared_context 'assign token' do |token|
let!(:previous_token) { integration.token }
before do
integration.token = token
integration.valid?
end
end
shared_examples 'valid token' do
it { is_expected.to match(/\A\h{32}\z/) }
end
context 'when unsaved' do
context 'when unassigned' do
before do
integration.valid?
end
it_behaves_like 'valid token'
end
context 'when assigned' do
include_context 'assign token', 'random_token'
it_behaves_like 'valid token'
it { is_expected.not_to eq('random_token') }
end
end
context 'when persisted' do
before do
integration.save!
integration.reload
end
it_behaves_like 'valid token'
context 'when resetting' do
include_context 'assign token', ''
it_behaves_like 'valid token'
it { is_expected.not_to eq(previous_token) }
end
context 'when reassigning' do
include_context 'assign token', 'random_token'
it_behaves_like 'valid token'
it { is_expected.to eq(previous_token) }
end
end
end
end
......@@ -117,6 +117,7 @@ RSpec.describe Project do
it { is_expected.to have_many(:prometheus_alert_events) }
it { is_expected.to have_many(:self_managed_prometheus_alert_events) }
it { is_expected.to have_many(:alert_management_alerts) }
it { is_expected.to have_many(:alert_management_http_integrations) }
it { is_expected.to have_many(:jira_imports) }
it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
it { is_expected.to have_many(:repository_storage_moves) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment