Skip to content

Commit

Permalink
feat: add on insert trigger on config changes
Browse files Browse the repository at this point in the history
  • Loading branch information
adityathebe committed Sep 6, 2024
1 parent 1013795 commit c36ba45
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 7 deletions.
65 changes: 65 additions & 0 deletions tests/config_changes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package tests
import (
"time"

"github.com/flanksource/duty/db"
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/query"
"github.com/flanksource/duty/tests/fixtures/dummy"
"github.com/flanksource/duty/types"
"github.com/google/uuid"
ginkgo "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -366,3 +368,66 @@ var _ = ginkgo.Describe("Config changes recursive", ginkgo.Ordered, func() {
})
})
})

var _ = ginkgo.Describe("handle external id conflict on config change inserts", ginkgo.Ordered, func() {
// An arbitrarily chosen time of the event we will be inserting multiple times
var referenceTime = time.Date(2020, 01, 15, 12, 00, 00, 00, time.UTC)

dummyChanges := []models.ConfigChange{
{ConfigID: dummy.EKSCluster.ID.String(), ExternalChangeID: lo.ToPtr("conflict_test_1"), Count: 1, CreatedAt: lo.ToPtr(referenceTime.Add(-time.Minute * 5)), Details: []byte(`{"replicas": "1"}`)},
{ConfigID: dummy.EKSCluster.ID.String(), ExternalChangeID: lo.ToPtr("conflict_test_2"), Count: 1, CreatedAt: lo.ToPtr(referenceTime.Add(-time.Minute * 4)), Details: []byte(`{"replicas": "2"}`)},
}

ginkgo.BeforeAll(func() {
err := DefaultContext.DB().Create(dummyChanges).Error
Expect(err).To(BeNil())
})

ginkgo.AfterAll(func() {
err := DefaultContext.DB().Delete(dummyChanges).Error
Expect(err).To(BeNil())
})

ginkgo.It("should increase count when the details is changed", func() {
c := models.ConfigChange{ConfigID: dummy.EKSCluster.ID.String(), ExternalChangeID: lo.ToPtr("conflict_test_1"), Count: 1, CreatedAt: lo.ToPtr(referenceTime), Details: []byte(`{"replicas": "3"}`)}
err := DefaultContext.DB().Create(&c).Error
Expect(err).To(BeNil())

{
var inserted models.ConfigChange
err := DefaultContext.DB().Where("external_change_id = ? AND config_id = ?", c.ExternalChangeID, c.ConfigID).First(&inserted).Error
Expect(db.ErrorDetails(err)).NotTo(HaveOccurred())
Expect(inserted.Count).To(Equal(2))
}
})

ginkgo.It("should NOT increase count", func() {
// insert the same change with the same details and external change id
// and ensure the count doesn't change.
for i := 0; i < 10; i++ {
c := models.ConfigChange{ConfigID: dummy.EKSCluster.ID.String(), ExternalChangeID: lo.ToPtr("conflict_test_1"), CreatedAt: lo.ToPtr(referenceTime), Count: 1, Details: []byte(`{"replicas": "3"}`)}
err := DefaultContext.DB().Create(&c).Error
Expect(err).To(BeNil())

{
var inserted models.ConfigChange
err := DefaultContext.DB().Where("external_change_id = ? AND config_id = ?", c.ExternalChangeID, c.ConfigID).First(&inserted).Error
Expect(db.ErrorDetails(err)).NotTo(HaveOccurred())
Expect(inserted.Count).To(Equal(2))
}
}
})

ginkgo.It("should increase count when the details is the same but the created_at is changed", func() {
c := models.ConfigChange{ConfigID: dummy.EKSCluster.ID.String(), ExternalChangeID: lo.ToPtr("conflict_test_1"), Count: 1, CreatedAt: lo.ToPtr(referenceTime.Add(time.Minute)), Details: []byte(`{"replicas": "3"}`)}
err := DefaultContext.DB().Create(&c).Error
Expect(err).To(BeNil())

{
var inserted models.ConfigChange
err := DefaultContext.DB().Where("external_change_id = ? AND config_id = ?", c.ExternalChangeID, c.ConfigID).First(&inserted).Error
Expect(db.ErrorDetails(err)).NotTo(HaveOccurred())
Expect(inserted.Count).To(Equal(3))
}
})
})
68 changes: 61 additions & 7 deletions views/030_config_changes.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE
OR REPLACE FUNCTION config_changes_update_trigger() RETURNS TRIGGER AS $$
CREATE OR REPLACE FUNCTION config_changes_update_trigger()
RETURNS TRIGGER AS $$
DECLARE
count_increment INT;
BEGIN
Expand Down Expand Up @@ -61,8 +61,62 @@ EXCEPTION
END;
$$ LANGUAGE plpgsql;

CREATE
OR REPLACE TRIGGER config_changes_update_trigger BEFORE
UPDATE
ON config_changes FOR EACH ROW
WHEN (pg_trigger_depth() = 0) EXECUTE FUNCTION config_changes_update_trigger();
CREATE OR REPLACE TRIGGER config_changes_update_trigger
BEFORE UPDATE
ON config_changes FOR EACH ROW
WHEN (pg_trigger_depth() = 0) EXECUTE FUNCTION config_changes_update_trigger();

---
CREATE OR REPLACE FUNCTION config_changes_insert_trigger()
RETURNS TRIGGER AS $$
DECLARE
existing_details JSONB;
existing_created_at TIMESTAMP WITH TIME ZONE;
BEGIN
-- run the original insert manually.
INSERT INTO config_changes SELECT NEW.*;

-- Prevent the original insert by returning NULL
RETURN NULL;
EXCEPTION
WHEN unique_violation THEN
IF sqlerrm LIKE '%config_changes_config_id_external_change_id_key%' THEN
SELECT details, created_at FROM config_changes
WHERE external_change_id = NEW.external_change_id
INTO existing_details, existing_created_at;

INSERT INTO event_queue(name, properties) VALUES('test', jsonb_build_object('id', NEW.id, 'oldDetails', OLD.details, 'newDetails', NEW.details));
UPDATE config_changes
SET
change_type = NEW.change_type,
count = CASE
WHEN (NEW.details IS DISTINCT FROM existing_details OR NEW.created_at IS DISTINCT FROM existing_created_at) THEN config_changes.count + 1
ELSE count
END,
created_at = NEW.created_at,
created_by = NEW.created_by,
details = NEW.details,
diff = NEW.diff,
external_created_by = NEW.external_created_by,
first_observed = LEAST(first_observed, created_at),
patches = NEW.patches,
severity = NEW.severity,
source = NEW.source,
summary = NEW.summary
WHERE
external_change_id = NEW.external_change_id;

RETURN NULL;
ELSE
RAISE;
END IF;
WHEN OTHERS THEN
RAISE;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER config_changes_insert_trigger
BEFORE INSERT
ON config_changes FOR EACH ROW
WHEN (pg_trigger_depth() = 0) EXECUTE FUNCTION config_changes_insert_trigger();
---

0 comments on commit c36ba45

Please sign in to comment.