From 8131e0a7e790fef1f5d9ba440501950f80916df8 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Sun, 9 May 2021 20:32:34 +0200 Subject: [PATCH] add history support This commit consists of two things. 1. server.go will now set two variables for the current transaction, the username and request id. These are transaction local and therefore do not leak into the connection. 2. The initial schema received a history table and a trigger. This trigger writes changes into the history table. When inserting records the function will pull the transaction local variables and add them to the record. The trigger is added to all tables, so that a complete changelog is created. These changes serve as the basis for further features. One is the searching for changes on specific resources (think history rr, history zone, ...). The other feature is a way to subscribe to changes in the database based on filters. This will be the way to implement the output feature of dim. --- schema/01_initial.sql | 60 +++++++++++++++++++++++++++++++++++++++++++ server.go | 20 ++++++++++++--- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/schema/01_initial.sql b/schema/01_initial.sql index c7acbfb..94409c0 100644 --- a/schema/01_initial.sql +++ b/schema/01_initial.sql @@ -124,3 +124,63 @@ create table if not exists outputgroup_outputs( output_id integer not null references outputs(id), unique(outputgroups_id, output_id) ); + +-- create history functionality +create table if not exists history( + transaction text not null, + created_at timestamptz not null default now(), + created_by text not null, + type text not null, + action text not null, + old_entity jsonb default '{}', + new_entity jsonb default '{}' +); +comment on table history is 'History is the log of all changes happening in DIM.'; +comment on column history.transaction is 'The transaction ID is the same for all changes originating in the same request.'; +comment on column history.created_by is 'The username is not a foreign key to keep the history around'; +comment on column history.type is 'Type represents the table that originated the change.'; +comment on column history.action is 'The action can be one of insert, update or delete.'; + +create or replace function record() returns trigger as $$ +begin + insert into history(transaction, created_by, type, action, old_entity, new_entity) + values ( + current_setting('dim.transaction'), + current_setting('dim.username'), + TG_TABLE_NAME, TG_OP, + row_to_json(OLD), + row_to_json(NEW) + ); + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end;$$ language plpgsql; + +-- drop existing triggers +drop trigger if exists trg_containers on containers; +drop trigger if exists trg_ips on ips; +drop trigger if exists trg_layer3domains on layer3domains; +drop trigger if exists trg_outputgroup_outputs on outputgroup_outputs; +drop trigger if exists trg_outputgroups on outputgroups; +drop trigger if exists trg_outputs on outputs; +drop trigger if exists trg_pools on pools; +drop trigger if exists trg_records on records; +drop trigger if exists trg_zones on zones; +drop trigger if exists trg_zoneviews on zoneviews; +drop trigger if exists trg_zoneviews_outputgroups on zoneviews_outputgroups; + +-- recreate history triggers +create trigger trg_containers before insert or update or delete on containers for each row execute function record(); +create trigger trg_ips before insert or update or delete on ips for each row execute function record(); +create trigger trg_layer3domains before insert or update or delete on layer3domains for each row execute function record(); +create trigger trg_outputgroup_outputs before insert or update or delete on outputgroup_outputs for each row execute function record(); +create trigger trg_outputgroups before insert or update or delete on outputgroups for each row execute function record(); +create trigger trg_outputs before insert or update or delete on outputs for each row execute function record(); +create trigger trg_pools before insert or update or delete on pools for each row execute function record(); +create trigger trg_records before insert or update or delete on records for each row execute function record(); +create trigger trg_zones before insert or update or delete on zones for each row execute function record(); +create trigger trg_zoneviews before insert or update or delete on zoneviews for each row execute function record(); +create trigger trg_zoneviews_outputgroups before insert or update or delete on zoneviews_outputgroups for each row execute function record(); + diff --git a/server.go b/server.go index 057a71d..551fcdb 100644 --- a/server.go +++ b/server.go @@ -93,9 +93,10 @@ func (s *Server) Handle(w http.ResponseWriter, r *http.Request) { return } c := &Context{ - id: id, - req: r, - w: w, + id: id, + req: r, + w: w, + username: "unknown", } req := Request{} @@ -116,6 +117,19 @@ func (s *Server) Handle(w http.ResponseWriter, r *http.Request) { defer tx.Rollback() // make a rollback, just in case something goes wrong c.tx = tx + // set username for transaction + // TODO check username to be ASCII and nothing else + _, err = c.tx.Exec( + fmt.Sprintf(`set local dim.username to '%s'; set local dim.transaction = '%s'`, c.username, c.id), + ) + if err != nil { + c.Logf(LevelError, "could not set transaction username: %s", err) + w.WriteHeader(http.StatusInternalServerError) + res.AddMessage(LevelFatal, "could not create transaction") + c.render(res) + return + } + dec := json.NewDecoder(r.Body) defer r.Body.Close() if err := dec.Decode(&req); err != nil {