Compare commits

...

2 Commits

Author SHA1 Message Date
Gibheer 8131e0a7e7 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.
2021-05-09 20:32:34 +02:00
Gibheer b3b14b9894 fix wrong error message
The old message was copied over from the layer3domains.
2021-05-09 20:32:05 +02:00
3 changed files with 79 additions and 5 deletions

View File

@ -45,8 +45,8 @@ func PoolCreate(c *Context, req Request, res *Response) error {
_, err = c.tx.Exec(`insert into pools(name, created_by, modified_by, attributes, layer3domain_id)
values ($1, $2, $2, $3, $4)`, name, c.username, attrs, l3id)
if err != nil {
res.AddMessage(LevelError, "could not create new layer3domain")
return fmt.Errorf("could not create new layer3domain '%s': %#v", name, err)
res.AddMessage(LevelError, "could not create new pool")
return fmt.Errorf("could not create new pool '%s': %#v", name, err)
}
res.AddMessage(LevelInfo, "created pool '%s'", name)
return nil

View File

@ -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();

View File

@ -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 {