Skip to content

Commit 66a3930

Browse files
committed
move config and associated functions from context toplevel to state
The `Context` toplevel should only store relatively static info that applies globally, like the secrets. When we accommodate multiple repos, a particular repo's config should be moved out of the global toplevel context to the runtime `State`, where it can coexist with other dynamic, repo-specific info like pipeline status.
1 parent 63e9eab commit 66a3930

File tree

5 files changed

+39
-36
lines changed

5 files changed

+39
-36
lines changed

lib/action.ml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
8787
if List.is_empty matched_channel_names then default else matched_channel_names
8888

8989
let partition_status (ctx : Context.t) (n : status_notification) =
90-
let cfg = Context.get_config_exn ctx in
90+
let cfg = State.find_repo_config_exn ctx.state in
9191
let pipeline = n.context in
9292
let current_status = n.state in
9393
let rules = cfg.status_rules.rules in
9494
let action_on_match (branches : branch list) =
9595
let default = Option.to_list cfg.prefix_rules.default_channel in
96-
let () = Context.refresh_pipeline_status ~pipeline ~branches ~status:current_status ctx in
96+
let () = State.set_repo_pipeline_status ~pipeline ~branches ~status:current_status ctx.state in
9797
match List.is_empty branches with
9898
| true -> Lwt.return []
9999
| false ->
@@ -111,7 +111,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
111111
| Ok commit -> Lwt.return @@ partition_commit cfg commit.files
112112
)
113113
in
114-
if Context.is_pipeline_allowed ctx ~pipeline then begin
114+
if State.is_pipeline_allowed ctx.state ~pipeline then begin
115115
match Rule.Status.match_rules ~rules n with
116116
| Some Ignore | None -> Lwt.return []
117117
| Some Allow -> action_on_match n.branches
@@ -130,7 +130,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
130130
else Lwt.return []
131131

132132
let partition_commit_comment (ctx : Context.t) n =
133-
let cfg = Context.get_config_exn ctx in
133+
let cfg = State.find_repo_config_exn ctx.state in
134134
match n.comment.commit_id with
135135
| None -> action_error "unable to find commit id for this commit comment event"
136136
| Some sha ->
@@ -149,7 +149,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
149149
)
150150

151151
let generate_notifications (ctx : Context.t) req =
152-
let cfg = Context.get_config_exn ctx in
152+
let cfg = State.find_repo_config_exn ctx.state in
153153
match req with
154154
| Github.Push n ->
155155
partition_push cfg n |> List.map ~f:(fun (channel, n) -> generate_push_notification n channel) |> Lwt.return
@@ -186,12 +186,12 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
186186
let repo = Github.repo_of_notification notification in
187187
match%lwt Github_api.get_config ~ctx ~repo with
188188
| Ok config ->
189-
ctx.config <- Some config;
189+
ctx.state.config <- Some config;
190190
Context.print_config ctx;
191191
Lwt.return @@ Ok ()
192192
| Error e -> action_error e
193193
in
194-
match ctx.config with
194+
match ctx.state.config with
195195
| None -> fetch_config ()
196196
| Some _ ->
197197
match notification with

lib/context.ml

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,20 @@ type t = {
1111
secrets_filepath : string;
1212
state_filepath : string option;
1313
mutable secrets : Config_t.secrets option;
14-
mutable config : Config_t.config option;
1514
state : State_t.state;
1615
}
1716

18-
let default : t =
17+
let default () : t =
1918
{
2019
config_filename = "monorobot.json";
2120
secrets_filepath = "secrets.json";
2221
state_filepath = None;
2322
secrets = None;
24-
config = None;
25-
state = State.empty;
23+
state = State.empty ();
2624
}
2725

2826
let make ?config_filename ?secrets_filepath ?state_filepath () =
27+
let default = default () in
2928
let config_filename = Option.value config_filename ~default:default.config_filename in
3029
let secrets_filepath = Option.value secrets_filepath ~default:default.secrets_filepath in
3130
{ default with config_filename; secrets_filepath; state_filepath }
@@ -35,31 +34,12 @@ let get_secrets_exn ctx =
3534
| None -> context_error "secrets is uninitialized"
3635
| Some secrets -> secrets
3736

38-
let get_config_exn ctx =
39-
match ctx.config with
40-
| None -> context_error "config is uninitialized"
41-
| Some config -> config
42-
4337
let hook_of_channel ctx channel_name =
4438
let secrets = get_secrets_exn ctx in
4539
match List.find secrets.slack_hooks ~f:(fun webhook -> String.equal webhook.channel channel_name) with
4640
| Some hook -> Some hook.url
4741
| None -> None
4842

49-
(** `is_pipeline_allowed ctx p` returns `true` if ctx.config.status_rules
50-
doesn't define a whitelist of allowed pipelines, or if the list
51-
contains pipeline `p`; returns `false` otherwise. *)
52-
let is_pipeline_allowed ctx ~pipeline =
53-
match ctx.config with
54-
| None -> false
55-
| Some config ->
56-
match config.status_rules.allowed_pipelines with
57-
| Some allowed_pipelines when not @@ List.exists allowed_pipelines ~f:(String.equal pipeline) -> false
58-
| _ -> true
59-
60-
let refresh_pipeline_status ctx ~pipeline ~(branches : Github_t.branch list) ~status =
61-
if is_pipeline_allowed ctx ~pipeline then State.refresh_pipeline_status ctx.state ~pipeline ~branches ~status else ()
62-
6343
let log = Log.from "context"
6444

6545
let refresh_secrets ctx =
@@ -91,7 +71,7 @@ let refresh_state ctx =
9171
else Ok ctx
9272

9373
let print_config ctx =
94-
let cfg = get_config_exn ctx in
74+
let cfg = State.find_repo_config_exn ctx.state in
9575
let secrets = get_secrets_exn ctx in
9676
log#info "using prefix routing:";
9777
Rule.Prefix.print_prefix_routing cfg.prefix_rules.rules;

lib/state.atd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
type status_state <ocaml from="Github"> = abstract
2+
type config <ocaml from="Config"> = abstract
23
type 'v map_as_object <ocaml from="Common"> = abstract
34

45
(* A map from branch names to build statuses *)
@@ -11,5 +12,6 @@ type pipeline_statuses = branch_statuses map_as_object
1112

1213
(* The serializable runtime state of the bot *)
1314
type state = {
15+
?config <ocaml mutable>: config option;
1416
pipeline_statuses <ocaml mutable>: pipeline_statuses
1517
}

lib/state.ml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,35 @@ open Base
22
open Common
33
open Devkit
44

5-
let empty : State_t.state = { pipeline_statuses = StringMap.empty }
5+
exception State_error of string
66

7-
let refresh_pipeline_status (state : State_t.state) ~pipeline ~(branches : Github_t.branch list) ~status =
8-
let update_pipeline_status branch_statuses =
7+
let state_error fmt = Printf.ksprintf (fun msg -> raise (State_error msg)) fmt
8+
9+
let empty () : State_t.state = { pipeline_statuses = StringMap.empty; config = None }
10+
11+
let find_repo_config_exn (state : State_t.state) =
12+
match state.config with
13+
| None -> state_error "config is uninitialized"
14+
| Some config -> config
15+
16+
let set_repo_pipeline_status (state : State_t.state) ~pipeline ~(branches : Github_t.branch list) ~status =
17+
let set_branch_status branch_statuses =
918
let new_statuses = List.map branches ~f:(fun b -> b.name, status) in
1019
let init = Option.value branch_statuses ~default:(Map.empty (module String)) in
1120
List.fold_left new_statuses ~init ~f:(fun m (key, data) -> Map.set m ~key ~data)
1221
in
13-
state.pipeline_statuses <- Map.update state.pipeline_statuses pipeline ~f:update_pipeline_status
22+
state.pipeline_statuses <- Map.update state.pipeline_statuses pipeline ~f:set_branch_status
23+
24+
(** `is_pipeline_allowed s p` returns `true` if status_rules of state
25+
`s`'s config doesn't define a whitelist of allowed pipelines, or
26+
if the list contains pipeline `p`; returns `false` otherwise. *)
27+
let is_pipeline_allowed (state : State_t.state) ~pipeline =
28+
match state.config with
29+
| None -> false
30+
| Some config ->
31+
match config.status_rules.allowed_pipelines with
32+
| Some allowed_pipelines when not @@ List.exists allowed_pipelines ~f:(String.equal pipeline) -> false
33+
| _ -> true
1434

1535
let log = Log.from "state"
1636

test/test.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let process ~(ctx : Context.t) (kind, path, state_path) =
3030
Lwt.return ctx
3131
| Ok file ->
3232
let state = State_j.state_of_string file in
33+
state.config <- ctx.state.config;
3334
Lwt.return { ctx with state }
3435
in
3536
Stdio.printf "===== file %s =====\n" path;
@@ -52,7 +53,7 @@ let () =
5253
log#error "%s" e;
5354
Lwt.return_unit
5455
| Ok config ->
55-
let ctx = { ctx with config = Some config } in
56+
ctx.state.config <- Some config;
5657
( match Context.refresh_secrets ctx with
5758
| Ok ctx -> Lwt_list.iter_s (process ~ctx) payloads
5859
| Error e ->

0 commit comments

Comments
 (0)