Add user column to build

We want to track who uploaded what builds.
A `nologin` user is created in the migration that owns all preexisting builds.
This commit is contained in:
Robur 2021-06-09 11:54:24 +00:00
parent 02bfbc956f
commit 31a501e0c4
10 changed files with 182 additions and 31 deletions

View file

@ -145,6 +145,16 @@ let r20210608 =
Cmdliner.Term.(const do_database_action $ const M20210608.rollback $ setup_log $ datadir), Cmdliner.Term.(const do_database_action $ const M20210608.rollback $ setup_log $ datadir),
Cmdliner.Term.info ~doc "rollback-2021-06-08" Cmdliner.Term.info ~doc "rollback-2021-06-08"
let m20210609 =
let doc = "add user column to build" in
Cmdliner.Term.(const do_database_action $ const M20210609.migrate $ setup_log $ datadir),
Cmdliner.Term.info ~doc "migrate-2021-06-09"
let r20210609 =
let doc = "remove user column to build" in
Cmdliner.Term.(const do_database_action $ const M20210609.rollback $ setup_log $ datadir),
Cmdliner.Term.info ~doc "rollback-2021-06-09"
let help_cmd = let help_cmd =
let topic = let topic =
let doc = "Migration to get help on" in let doc = "Migration to get help on" in
@ -172,5 +182,6 @@ let () =
m20210531; r20210531; m20210531; r20210531;
m20210602; r20210602; m20210602; r20210602;
m20210608; r20210608; m20210608; r20210608;
m20210609; r20210609;
] ]
|> Cmdliner.Term.exit |> Cmdliner.Term.exit

View file

@ -1,5 +1,9 @@
let old_version = 4L and new_version = 5L let old_version = 4L and new_version = 5L
let idx_build_job_start =
Caqti_request.exec Caqti_type.unit
"CREATE INDEX idx_build_job_start ON build(job, start_d DESC, start_ps DESC)"
let new_build = let new_build =
Caqti_request.exec Caqti_request.exec
Caqti_type.unit Caqti_type.unit
@ -121,6 +125,7 @@ let migrate _ (module Db : Caqti_blocking.CONNECTION) =
builds >>= fun () -> builds >>= fun () ->
Db.exec drop_build () >>= fun () -> Db.exec drop_build () >>= fun () ->
Db.exec rename_build () >>= fun () -> Db.exec rename_build () >>= fun () ->
Db.exec idx_build_job_start () >>= fun () ->
Db.exec (Grej.set_version new_version) () Db.exec (Grej.set_version new_version) ()
@ -140,4 +145,5 @@ let rollback _ (module Db : Caqti_blocking.CONNECTION) =
builds >>= fun () -> builds >>= fun () ->
Db.exec drop_build () >>= fun () -> Db.exec drop_build () >>= fun () ->
Db.exec rename_build () >>= fun () -> Db.exec rename_build () >>= fun () ->
Db.exec idx_build_job_start () >>= fun () ->
Db.exec (Grej.set_version old_version) () Db.exec (Grej.set_version old_version) ()

119
bin/migrations/m20210609.ml Normal file
View file

@ -0,0 +1,119 @@
let new_version = 7L and old_version = 6L
let idx_build_job_start =
Caqti_request.exec Caqti_type.unit
"CREATE INDEX idx_build_job_start ON build(job, start_d DESC, start_ps DESC)"
let nologin_user =
Caqti_request.exec
Caqti_type.unit
"INSERT INTO user (username, password_hash, password_salt, scrypt_n, scrypt_r, scrypt_p, restricted) \
VALUES ('nologin', '', '', 16384, 8, 1, true)"
let remove_nologin_user =
Caqti_request.exec
Caqti_type.unit
"DELETE FROM user WHERE username = 'nologin'"
let new_build =
Caqti_request.exec
Caqti_type.unit
{| CREATE TABLE new_build (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
uuid VARCHAR(36) NOT NULL UNIQUE,
start_d INTEGER NOT NULL,
start_ps INTEGER NOT NULL,
finish_d INTEGER NOT NULL,
finish_ps INTEGER NOT NULL,
result_kind TINYINT NOT NULL,
result_code INTEGER,
result_msg TEXT,
console BLOB NOT NULL,
script TEXT NOT NULL,
main_binary INTEGER,
user INTEGER NOT NULL,
job INTEGER NOT NULL,
FOREIGN KEY(main_binary) REFERENCES build_artifact(id),
FOREIGN KEY(job) REFERENCES job(id),
FOREIGN KEY(user) REFERENCES user(id)
)
|}
let old_build =
Caqti_request.exec
Caqti_type.unit
{| CREATE TABLE new_build (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
uuid VARCHAR(36) NOT NULL UNIQUE,
start_d INTEGER NOT NULL,
start_ps INTEGER NOT NULL,
finish_d INTEGER NOT NULL,
finish_ps INTEGER NOT NULL,
result_kind TINYINT NOT NULL,
result_code INTEGER,
result_msg TEXT,
console BLOB NOT NULL,
script TEXT NOT NULL,
main_binary INTEGER,
job INTEGER NOT NULL,
FOREIGN KEY(main_binary) REFERENCES build_artifact(id),
FOREIGN KEY(job) REFERENCES job(id)
)
|}
let insert_from_old_build =
Caqti_request.exec ~oneshot:true
Builder_db.Rep.id
{| INSERT INTO new_build (id, uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, console,
script, main_binary, job, user)
SELECT id, uuid, start_d, start_ps, finish_d, finish_ps, result_kind,
result_code, result_msg, console, script, main_binary, job, ?
FROM build |}
let insert_from_new_build =
Caqti_request.exec ~oneshot:true
Caqti_type.unit
{| INSERT INTO new_build (id, uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, console,
script, main_binary, job)
SELECT id, uuid, start_d, start_ps, finish_d, finish_ps, result_kind,
result_code, result_msg, console, script, main_binary, job
FROM build |}
let drop_build =
Caqti_request.exec ~oneshot:true
Caqti_type.unit
"DROP TABLE build"
let rename_build =
Caqti_request.exec ~oneshot:true
Caqti_type.unit
"ALTER TABLE new_build RENAME TO build"
open Rresult.R.Infix
let migrate _datadir (module Db : Caqti_blocking.CONNECTION) =
Grej.check_version ~user_version:old_version (module Db) >>= fun () ->
Db.exec nologin_user () >>= fun () ->
Db.find Builder_db.last_insert_rowid () >>= fun user_id ->
Db.exec new_build () >>= fun () ->
Db.exec insert_from_old_build user_id >>= fun () ->
Db.exec drop_build () >>= fun () ->
Db.exec rename_build () >>= fun () ->
Db.exec idx_build_job_start () >>= fun () ->
Db.exec (Grej.set_version new_version) ()
let rollback _datadir (module Db : Caqti_blocking.CONNECTION) =
Grej.check_version ~user_version:new_version (module Db) >>= fun () ->
Db.exec old_build () >>= fun () ->
Db.exec insert_from_new_build () >>= fun () ->
Db.exec drop_build () >>= fun () ->
Db.exec rename_build () >>= fun () ->
Db.exec idx_build_job_start () >>= fun () ->
Db.exec remove_nologin_user () >>= fun () ->
Db.exec (Grej.set_version old_version) ()

View file

@ -4,7 +4,7 @@ open Rep
let application_id = 1234839235l let application_id = 1234839235l
(* Please update this when making changes! *) (* Please update this when making changes! *)
let current_version = 6L let current_version = 7L
type id = Rep.id type id = Rep.id
@ -212,12 +212,13 @@ module Build = struct
console : (int * string) list; console : (int * string) list;
script : string; script : string;
main_binary : id option; main_binary : id option;
user_id : id;
job_id : id; job_id : id;
} }
let t = let t =
let rep = let rep =
Caqti_type.(tup2 Caqti_type.(tup3
(tup4 (tup4
uuid uuid
(tup2 (tup2
@ -229,13 +230,14 @@ module Build = struct
(tup2 (tup2
string string
(option Rep.id))) (option Rep.id)))
id
id) id)
in in
let encode { uuid; start; finish; result; console; script; main_binary; job_id } = let encode { uuid; start; finish; result; console; script; main_binary; user_id; job_id } =
Ok ((uuid, (start, finish), (result, console), (script, main_binary)), job_id) Ok ((uuid, (start, finish), (result, console), (script, main_binary)), user_id, job_id)
in in
let decode ((uuid, (start, finish), (result, console), (script, main_binary)), job_id) = let decode ((uuid, (start, finish), (result, console), (script, main_binary)), user_id, job_id) =
Ok { uuid; start; finish; result; console; script; main_binary; job_id } Ok { uuid; start; finish; result; console; script; main_binary; user_id; job_id }
in in
Caqti_type.custom ~encode ~decode rep Caqti_type.custom ~encode ~decode rep
@ -246,12 +248,13 @@ module Build = struct
finish : Ptime.t; finish : Ptime.t;
result : Builder.execution_result; result : Builder.execution_result;
main_binary : id option; main_binary : id option;
user_id : id;
job_id : id; job_id : id;
} }
let t = let t =
let rep = let rep =
Caqti_type.(tup2 Caqti_type.(tup3
(tup4 (tup4
uuid uuid
(tup2 (tup2
@ -259,13 +262,14 @@ module Build = struct
Rep.ptime) Rep.ptime)
execution_result execution_result
(option Rep.id)) (option Rep.id))
id
id) id)
in in
let encode { uuid; start; finish; result; main_binary; job_id } = let encode { uuid; start; finish; result; main_binary; user_id; job_id } =
Ok ((uuid, (start, finish), result, main_binary), job_id) Ok ((uuid, (start, finish), result, main_binary), user_id, job_id)
in in
let decode ((uuid, (start, finish), result, main_binary), job_id) = let decode ((uuid, (start, finish), result, main_binary), user_id, job_id) =
Ok { uuid; start; finish; result; main_binary; job_id } Ok { uuid; start; finish; result; main_binary; user_id; job_id }
in in
Caqti_type.custom ~encode ~decode rep Caqti_type.custom ~encode ~decode rep
end end
@ -286,9 +290,11 @@ module Build = struct
console BLOB NOT NULL, console BLOB NOT NULL,
script TEXT NOT NULL, script TEXT NOT NULL,
main_binary INTEGER, main_binary INTEGER,
user INTEGER NOT NULL,
job INTEGER NOT NULL, job INTEGER NOT NULL,
FOREIGN KEY(main_binary) REFERENCES build_artifact(id), FOREIGN KEY(main_binary) REFERENCES build_artifact(id),
FOREIGN KEY(user) REFERENCES user(id),
FOREIGN KEY(job) REFERENCES job(id) FOREIGN KEY(job) REFERENCES job(id)
) )
|} |}
@ -304,7 +310,7 @@ module Build = struct
t t
{| SELECT uuid, start_d, start_ps, finish_d, finish_ps, {| SELECT uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, result_kind, result_code, result_msg,
console, script, main_binary, job console, script, main_binary, user, job
FROM build FROM build
WHERE id = ? WHERE id = ?
|} |}
@ -315,7 +321,7 @@ module Build = struct
(Caqti_type.tup2 id t) (Caqti_type.tup2 id t)
{| SELECT id, uuid, start_d, start_ps, finish_d, finish_ps, {| SELECT id, uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, result_kind, result_code, result_msg,
console, script, main_binary, job console, script, main_binary, user, job
FROM build FROM build
WHERE uuid = ? WHERE uuid = ?
|} |}
@ -326,7 +332,7 @@ module Build = struct
(Caqti_type.tup2 id t) (Caqti_type.tup2 id t)
{| SELECT id, uuid, start_d, start_ps, finish_d, finish_ps, {| SELECT id, uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, console, result_kind, result_code, result_msg, console,
script, main_binary, job script, main_binary, user, job
FROM build FROM build
WHERE job = ? WHERE job = ?
ORDER BY start_d DESC, start_ps DESC ORDER BY start_d DESC, start_ps DESC
@ -340,7 +346,7 @@ module Build = struct
{| SELECT build.id, build.uuid, {| SELECT build.id, build.uuid,
build.start_d, build.start_ps, build.finish_d, build.finish_ps, build.start_d, build.start_ps, build.finish_d, build.finish_ps,
build.result_kind, build.result_code, build.result_msg, build.result_kind, build.result_code, build.result_msg,
build.main_binary, build.job, build.main_binary, build.user, build.job,
build_artifact.filepath, build_artifact.localpath, build_artifact.sha256, build_artifact.size build_artifact.filepath, build_artifact.localpath, build_artifact.sha256, build_artifact.size
FROM build, job FROM build, job
LEFT JOIN build_artifact ON LEFT JOIN build_artifact ON
@ -359,7 +365,7 @@ module Build = struct
{| SELECT b.id, {| SELECT b.id,
b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps,
b.result_kind, b.result_code, b.result_msg, b.result_kind, b.result_code, b.result_msg,
b.main_binary, b.job, b.main_binary, b.user, b.job,
a.filepath, a.localpath, a.sha256, a.size a.filepath, a.localpath, a.sha256, a.size
FROM build b FROM build b
LEFT JOIN build_artifact a ON LEFT JOIN build_artifact a ON
@ -398,7 +404,7 @@ module Build = struct
{| SELECT b.id, {| SELECT b.id,
b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps,
b.result_kind, b.result_code, b.result_msg, b.result_kind, b.result_code, b.result_msg,
b.main_binary, b.job b.main_binary, b.user, b.job
FROM build b, build b0 FROM build b, build b0
WHERE b0.id = ? AND b0.job = b.job AND WHERE b0.id = ? AND b0.job = b.job AND
b.result_kind = 0 AND b.result_code = 0 AND b.result_kind = 0 AND b.result_code = 0 AND
@ -412,9 +418,9 @@ module Build = struct
t t
{| INSERT INTO build {| INSERT INTO build
(uuid, start_d, start_ps, finish_d, finish_ps, (uuid, start_d, start_ps, finish_d, finish_ps,
result_kind, result_code, result_msg, console, script, main_binary, job) result_kind, result_code, result_msg, console, script, main_binary, user, job)
VALUES VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|} |}
let get_by_hash = let get_by_hash =
@ -426,7 +432,7 @@ module Build = struct
{| SELECT job.name, {| SELECT job.name,
b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps, b.uuid, b.start_d, b.start_ps, b.finish_d, b.finish_ps,
b.result_kind, b.result_code, b.result_msg, b.result_kind, b.result_code, b.result_msg,
b.console, b.script, b.main_binary, b.job b.console, b.script, b.main_binary, b.user, b.job
FROM build_artifact a FROM build_artifact a
INNER JOIN build b ON b.id = a.build INNER JOIN build b ON b.id = a.build
INNER JOIN job ON job.id = b.job INNER JOIN job ON job.id = b.job

View file

@ -113,6 +113,7 @@ sig
console : (int * string) list; console : (int * string) list;
script : string; script : string;
main_binary : id option; main_binary : id option;
user_id : id;
job_id : id; job_id : id;
} }
module Meta : module Meta :
@ -123,6 +124,7 @@ sig
finish : Ptime.t; finish : Ptime.t;
result : Builder.execution_result; result : Builder.execution_result;
main_binary : id option; main_binary : id option;
user_id : id;
job_id : id; job_id : id;
} }
end end

View file

@ -196,7 +196,9 @@ let add_routes datadir =
|> Lwt_result.ok |> Lwt_result.ok
| false -> | false ->
let datadir = Dream.global datadir_global req in let datadir = Dream.global datadir_global req in
Dream.sql req (Model.add_build datadir exec) (Lwt.return (Dream.local Authorization.user_info_local req |>
Option.to_result ~none:(`Msg "no authenticated user")) >>= fun (user_id, _) ->
Dream.sql req (Model.add_build datadir user_id exec))
|> if_error "Internal server error" |> if_error "Internal server error"
~log:(fun e -> Log.warn (fun m -> m "Error saving build %a: %a" pp_exec exec pp_error e)) ~log:(fun e -> Log.warn (fun m -> m "Error saving build %a: %a" pp_exec exec pp_error e))
>>= fun () -> Dream.respond "" |> Lwt_result.ok >>= fun () -> Dream.respond "" |> Lwt_result.ok

View file

@ -191,6 +191,7 @@ let commit_files datadir staging_dir job_name uuid =
let add_build let add_build
datadir datadir
user_id
((job, uuid, console, start, finish, result, _) as exec) ((job, uuid, console, start, finish, result, _) as exec)
(module Db : CONN) = (module Db : CONN) =
let open Builder_db in let open Builder_db in
@ -214,7 +215,7 @@ let add_build
Lwt.return (Option.to_result ~none:(`Msg "No such job id") job_id) >>= fun job_id -> Lwt.return (Option.to_result ~none:(`Msg "No such job id") job_id) >>= fun job_id ->
Db.exec Build.add { Build.uuid; start; finish; result; Db.exec Build.add { Build.uuid; start; finish; result;
console; script = job.Builder.script; console; script = job.Builder.script;
main_binary = None; job_id } >>= fun () -> main_binary = None; user_id; job_id } >>= fun () ->
Db.find last_insert_rowid () >>= fun id -> Db.find last_insert_rowid () >>= fun id ->
List.fold_left List.fold_left
(fun r file -> (fun r file ->

View file

@ -61,6 +61,7 @@ val authorized : Builder_db.id -> string -> Caqti_lwt.connection -> (unit, [> Ca
val add_build : val add_build :
Fpath.t -> Fpath.t ->
Builder_db.id ->
(Builder.job * Uuidm.t * (int * string) list * Ptime.t * Ptime.t * (Builder.job * Uuidm.t * (int * string) list * Ptime.t * Ptime.t *
Builder.execution_result * (Fpath.t * string) list) -> Builder.execution_result * (Fpath.t * string) list) ->
Caqti_lwt.connection -> Caqti_lwt.connection ->

View file

@ -168,7 +168,7 @@ let job name builds =
let job_build let job_build
name name
{ Builder_db.Build.uuid; start; finish; result; console; script; main_binary = _; job_id = _ } { Builder_db.Build.uuid; start; finish; result; console; script; _ }
artifacts artifacts
latest_uuid latest_uuid
previous_build previous_build

View file

@ -75,12 +75,13 @@ let restricted = false
let auth = Builder_web_auth.hash ~scrypt_params ~username ~password ~restricted () let auth = Builder_web_auth.hash ~scrypt_params ~username ~password ~restricted ()
let add_test_user (module Db : CONN) = let add_test_user (module Db : CONN) =
Db.exec Builder_db.User.add auth Db.exec Builder_db.User.add auth >>= fun () ->
Db.find Builder_db.last_insert_rowid ()
let with_user_db f () = let with_user_db f () =
or_fail or_fail
(setup_db () >>= fun conn -> (setup_db () >>= fun conn ->
add_test_user conn >>= fun () -> add_test_user conn >>= fun _id ->
f conn) f conn)
let test_user_get_all (module Db : CONN) = let test_user_get_all (module Db : CONN) =
@ -150,17 +151,17 @@ let main_binary =
let size = String.length data in let size = String.length data in
{ Builder_db.Rep.filepath; localpath; sha256; size } { Builder_db.Rep.filepath; localpath; sha256; size }
let fail_if_none = let fail_if_none a =
Option.to_result ~none:(`Msg "Failed to retrieve job id") Option.to_result ~none:(`Msg "Failed to retrieve") a
let add_test_build (module Db : CONN) = let add_test_build user_id (module Db : CONN) =
let r = let r =
let open Builder_db in let open Builder_db in
Db.start () >>= fun () -> Db.start () >>= fun () ->
Db.exec Job.try_add job_name >>= fun () -> Db.exec Job.try_add job_name >>= fun () ->
Db.find_opt Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> Db.find_opt Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id ->
Db.exec Build.add { Build.uuid; start; finish; result; console; script; Db.exec Build.add { Build.uuid; start; finish; result; console; script;
main_binary = None; main_binary = None; user_id;
job_id } >>= fun () -> job_id } >>= fun () ->
Db.find last_insert_rowid () >>= fun id -> Db.find last_insert_rowid () >>= fun id ->
Db.exec Build_artifact.add (main_binary, id) >>= fun () -> Db.exec Build_artifact.add (main_binary, id) >>= fun () ->
@ -175,7 +176,8 @@ let add_test_build (module Db : CONN) =
let with_build_db f () = let with_build_db f () =
or_fail or_fail
(setup_db () >>= fun conn -> (setup_db () >>= fun conn ->
add_test_build conn >>= fun () -> add_test_user conn >>= fun user_id ->
add_test_build user_id conn >>= fun () ->
f conn) f conn)
let test_job_get_all (module Db : CONN) = let test_job_get_all (module Db : CONN) =
@ -224,10 +226,11 @@ let finish' = Option.get (Ptime.of_float_s 3601.)
let add_second_build (module Db : CONN) = let add_second_build (module Db : CONN) =
let uuid = uuid' and start = start' and finish = finish' in let uuid = uuid' and start = start' and finish = finish' in
let open Builder_db in let open Builder_db in
Db.find_opt User.get_user username >>= fail_if_none >>= fun (user_id, _) ->
Db.start () >>= fun () -> Db.start () >>= fun () ->
Db.find_opt Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id -> Db.find_opt Job.get_id_by_name job_name >>= fail_if_none >>= fun job_id ->
Db.exec Build.add { Build.uuid; start; finish; result; console; script; Db.exec Build.add { Build.uuid; start; finish; result; console; script;
main_binary = None; job_id; main_binary = None; user_id; job_id;
} >>= fun () -> } >>= fun () ->
Db.find last_insert_rowid () >>= fun id -> Db.find last_insert_rowid () >>= fun id ->
Db.exec Build_artifact.add (main_binary, id) >>= fun () -> Db.exec Build_artifact.add (main_binary, id) >>= fun () ->