@@ -13,6 +13,7 @@ let action_error msg = raise (Action_error msg)
13
13
let log = Log. from " action"
14
14
15
15
module Github_api = Api_remote. Github
16
+ module Slack_api = Api_remote. Slack
16
17
17
18
module Action = struct
18
19
let partition_push cfg n =
@@ -90,56 +91,60 @@ module Action = struct
90
91
if List. is_empty matched_channel_names then default else matched_channel_names
91
92
92
93
let partition_status (ctx : Context.t ) (n : status_notification ) =
93
- let cfg = ctx.cfg in
94
- let get_commit_info () =
95
- let default () = Lwt. return @@ Option. to_list cfg.prefix_rules.default_channel in
96
- match cfg.main_branch_name with
97
- | None -> default ()
98
- | Some main_branch_name ->
99
- (* non-main branch build notifications go to default channel to reduce spam in topic channels *)
100
- match List. exists n.branches ~f: (fun { name } -> String. equal name main_branch_name) with
101
- | false -> default ()
102
- | true ->
103
- ( match % lwt Github. generate_query_commit cfg ~url: n.commit.url ~sha: n.commit.sha with
104
- | None -> default ()
105
- | Some commit ->
106
- (*
107
- match
108
- List.exists n.branches ~f:(fun { name } -> Github.is_main_merge_message ~msg:commit.commit.message ~branch:name cfg)
109
- with
110
- | true ->
111
- log#info "main branch merge, ignoring status event %s: %s" n.context (first_line commit.commit.message);
112
- Lwt.return []
113
- | false ->
114
- *)
115
- Lwt. return (partition_commit cfg commit.files)
116
- )
117
- in
118
- let res =
119
- match
120
- List. exists cfg.status_rules.status ~f: (fun x ->
121
- match x with
122
- | State s -> Poly. equal s n.state
123
- | HideConsecutiveSuccess -> Poly. equal Success n.state
124
- | _ -> false )
125
- with
126
- | false -> Lwt. return []
127
- | true ->
128
- match List. exists ~f: id [ Rule.Status. hide_cancelled n cfg; Rule.Status. hide_success n ctx ] with
129
- | true -> Lwt. return []
130
- | false ->
131
- match cfg.status_rules.title with
132
- | None -> get_commit_info ()
133
- | Some status_filter ->
134
- match List. exists status_filter ~f: (String. equal n.context) with
135
- | false -> Lwt. return []
136
- | true -> get_commit_info ()
137
- in
138
- Context. update_state ctx (Github. Status n);
139
- res
94
+ match ctx.config with
95
+ | None -> action_error " missing configuration file"
96
+ | Some cfg ->
97
+ let pipeline = n.context in
98
+ let current_status = n.state in
99
+ let updated_at = n.updated_at in
100
+ let get_commit_info () =
101
+ let default = Option. to_list cfg.prefix_rules.default_channel in
102
+ let () =
103
+ Context. refresh_pipeline_status ~pipeline ~branches: n.branches ~status: current_status ~updated_at ctx
104
+ in
105
+ match List. is_empty n.branches with
106
+ | true -> Lwt. return []
107
+ | false ->
108
+ match cfg.main_branch_name with
109
+ | None -> Lwt. return default
110
+ | Some main_branch_name ->
111
+ (* non-main branch build notifications go to default channel to reduce spam in topic channels *)
112
+ match List. exists n.branches ~f: (fun { name } -> String. equal name main_branch_name) with
113
+ | false -> Lwt. return default
114
+ | true ->
115
+ let sha = n.commit.sha in
116
+ let repo = n.repository in
117
+ ( match % lwt Github_api. get_api_commit ~ctx ~repo ~sha with
118
+ | Error e -> action_error e
119
+ | Ok commit -> Lwt. return @@ partition_commit cfg commit.files
120
+ )
121
+ in
122
+ let res =
123
+ match
124
+ List. exists cfg.status_rules.status ~f: (fun x ->
125
+ match x with
126
+ | State s -> Poly. equal s n.state
127
+ | HideConsecutiveSuccess -> Poly. equal Success n.state
128
+ | _ -> false )
129
+ with
130
+ | false -> Lwt. return []
131
+ | true ->
132
+ match List. exists ~f: id [ Rule.Status. hide_cancelled n cfg; Rule.Status. hide_success n ctx ] with
133
+ | true -> Lwt. return []
134
+ | false ->
135
+ match cfg.status_rules.title with
136
+ | None -> get_commit_info ()
137
+ | Some status_filter ->
138
+ match List. exists status_filter ~f: (String. equal n.context) with
139
+ | false -> Lwt. return []
140
+ | true -> get_commit_info ()
141
+ in
142
+ res
140
143
141
144
let partition_commit_comment (ctx : Context.t ) n =
142
- let cfg = ctx.cfg in
145
+ match ctx.config with
146
+ | None -> action_error " missing configuration file"
147
+ | Some cfg ->
143
148
match n.comment.commit_id with
144
149
| None -> action_error " unable to find commit id for this commit comment event"
145
150
| Some sha ->
@@ -158,7 +163,9 @@ module Action = struct
158
163
)
159
164
160
165
let generate_notifications (ctx : Context.t ) req =
161
- let cfg = ctx.cfg in
166
+ match ctx.config with
167
+ | None -> action_error " missing configuration file"
168
+ | Some cfg ->
162
169
match req with
163
170
| Github. Push n ->
164
171
partition_push cfg n |> List. map ~f: (fun (webhook , n ) -> webhook, generate_push_notification n) |> Lwt. return
@@ -187,37 +194,64 @@ module Action = struct
187
194
Lwt. return notifs
188
195
| _ -> Lwt. return []
189
196
190
- let send_notifications (cfg : Config.t ) notifications =
191
- Lwt_list. iter_s
192
- (fun (chan , msg ) ->
193
- let url = Config.Chan_map. find chan cfg.chans in
194
- let data = Slack_j. string_of_webhook_notification msg in
195
- log#info " sending to %s : %s" chan data;
196
- Slack. send_notification url data)
197
- notifications
197
+ let send_notifications (ctx : Context.t ) notifications =
198
+ let notify (chan , msg ) =
199
+ match Context. hook_of_channel ctx chan with
200
+ | None ->
201
+ log#error " webhook not defined for Slack channel '%s'" chan;
202
+ Lwt. return_unit
203
+ | Some url ->
204
+ ( match % lwt Slack_api. send_notification ~chan ~msg ~url with
205
+ | Ok () -> Lwt. return_unit
206
+ | Error e -> action_error e
207
+ )
208
+ in
209
+ Lwt_list. iter_s notify notifications
198
210
199
- let update_config (ctx : Context.t ) = function
200
- | Github. Push n ->
201
- let is_config_file f = String. equal f ctx.data.cfg_filename in
202
- let commit_contains_config_file (c : Github_t.commit ) = List. exists ~f: is_config_file (c.added @ c.modified) in
203
- if List. exists ~f: commit_contains_config_file n.commits then Context. refresh_config ctx else Lwt. return_unit
204
- | _ -> Lwt. return_unit
205
-
206
- let process_github_notification (ctx_thunk : Context.context_thunk ) headers body =
207
- match Github. parse_exn ~secret: ctx_thunk.secrets.gh_webhook_secret headers body with
208
- | exception exn -> Exn_lwt. fail ~exn " unable to parse payload"
209
- | payload ->
210
- try
211
- let % lwt ctx = Context. resolve_ctx_in_thunk ctx_thunk payload in
212
- let % lwt () = update_config ctx payload in
213
- let cfg = ctx.cfg in
214
- let % lwt notifications = generate_notifications ctx payload in
215
- send_notifications cfg notifications
211
+ (* * `refresh_config_of_context ctx n` updates the current context if the configuration
212
+ hasn't been loaded yet, or if the incoming request `n` is a push
213
+ notification containing commits that touched the config file. *)
214
+ let refresh_config_of_context (ctx : Context.t ) notification =
215
+ let fetch_config () =
216
+ let repo = Github. repo_of_notification notification in
217
+ match % lwt Github_api. get_config ~ctx ~repo with
218
+ | Ok config ->
219
+ (* can remove this wrapper once status_rules doesn't depend on Config.t *)
220
+ ctx.config < - Some (Config. make config);
221
+ Lwt. return @@ Ok ()
222
+ | Error e -> action_error e
223
+ in
224
+ match ctx.config with
225
+ | None -> fetch_config ()
226
+ | Some _ ->
227
+ match notification with
228
+ | Github. Push commit_pushed_notification ->
229
+ let commits = commit_pushed_notification.commits in
230
+ let modified_files = List. concat_map commits ~f: Github. modified_files_of_commit in
231
+ let config_was_modified = List. exists modified_files ~f: (String. equal ctx.config_filename) in
232
+ if config_was_modified then fetch_config () else Lwt. return @@ Ok ()
233
+ | _ -> Lwt. return @@ Ok ()
234
+
235
+ let process_github_notification (ctx : Context.t ) headers body =
236
+ try % lwt
237
+ match Github. parse_exn ~secret: ctx.gh_hook_token headers body with
238
+ | exception exn -> Exn_lwt. fail ~exn " failed to parse payload"
239
+ | payload ->
240
+ ( match % lwt refresh_config_of_context ctx payload with
241
+ | Error e -> action_error e
242
+ | Ok () ->
243
+ let % lwt notifications = generate_notifications ctx payload in
244
+ let % lwt () = send_notifications ctx notifications in
245
+ ( match ctx.state_filepath with
246
+ | None -> Lwt. return_unit
247
+ | Some path -> Lwt. return @@ State. save path ctx.state
248
+ )
249
+ )
216
250
with
217
- | Context. Context_error s ->
218
- log#error " error creating context from payload: %s " s ;
251
+ | Yojson. Json_error msg ->
252
+ log#error " failed to parse file as valid JSON (%s) " msg ;
219
253
Lwt. return_unit
220
- | Github. Remote_config_error s ->
221
- log#error " error retrieving config from payload: %s" s ;
254
+ | Action_error msg ->
255
+ log#error " %s" msg ;
222
256
Lwt. return_unit
223
257
end
0 commit comments