-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM only when user breaks a step #160
Changes from 7 commits
e009cb4
dc96df5
7ddbc93
e8373eb
771ac0e
e297f1d
0fd1067
4db4833
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct | |
n.commits | ||
|> List.filter (fun c -> | ||
let skip = Github.is_merge_commit_to_ignore ~cfg ~branch c in | ||
if skip then log#info "main branch merge, ignoring %s: %s" c.id (first_line c.message); | ||
if skip then log#info "main branch merge, ignoring %s: %s" c.id (Util.first_line c.message); | ||
not skip) | ||
|> List.concat_map (fun commit -> | ||
let rules = List.filter (filter_by_branch ~distinct:commit.distinct) rules in | ||
|
@@ -128,71 +128,87 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct | |
in | ||
if matched_channel_names = [] then default else matched_channel_names | ||
|
||
let buildkite_is_failed_re = Re2.create_exn {|^Build #\d+ failed|} | ||
|
||
let partition_status (ctx : Context.t) (n : status_notification) = | ||
let repo = n.repository in | ||
let cfg = Context.find_repo_config_exn ctx repo.url in | ||
let pipeline = n.context in | ||
let current_status = n.state in | ||
let rules = cfg.status_rules.rules in | ||
let repo_state = State.find_or_add_repo ctx.state n.repository.url in | ||
|
||
let action_on_match (branches : branch list) ~notify_channels ~notify_dm = | ||
let%lwt direct_message = | ||
if notify_dm then begin | ||
match%lwt Slack_api.lookup_user ~ctx ~cfg ~email:n.commit.commit.author.email () with | ||
| Ok res -> | ||
State.set_repo_pipeline_commit ctx.state repo.url ~pipeline ~commit:n.sha; | ||
(* To send a DM, channel parameter is set to the user id of the recipient *) | ||
Lwt.return [ res.user.id ] | ||
| Error e -> | ||
log#warn "couldn't match commit email %s to slack profile: %s" n.commit.commit.author.email e; | ||
Lwt.return [] | ||
end | ||
else Lwt.return [] | ||
in | ||
let%lwt chans = | ||
let default = Stdlib.Option.to_list cfg.prefix_rules.default_channel in | ||
match notify_channels with | ||
| false -> Lwt.return [] | ||
| true -> | ||
match branches with | ||
| [] -> Lwt.return [] | ||
| _ -> | ||
match cfg.main_branch_name with | ||
| None -> Lwt.return default | ||
| Some main_branch_name -> | ||
(* non-main branch build notifications go to default channel to reduce spam in topic channels *) | ||
match List.exists (fun ({ name } : branch) -> String.equal name main_branch_name) branches with | ||
| false -> Lwt.return default | ||
| true -> | ||
let sha = n.commit.sha in | ||
(match%lwt Github_api.get_api_commit ~ctx ~repo ~sha with | ||
| Error e -> action_error e | ||
| Ok commit -> Lwt.return @@ partition_commit cfg commit.files) | ||
in | ||
Lwt.return (direct_message @ chans) | ||
let broken_steps = Util.Build.new_failed_steps n repo_state pipeline in | ||
match n.state = Failure && broken_steps = [] with | ||
| true -> Lwt.return [] | ||
| false -> | ||
let%lwt direct_message = | ||
if notify_dm then begin | ||
match%lwt Slack_api.lookup_user ~ctx ~cfg ~email:n.commit.commit.author.email () with | ||
| Ok res -> | ||
State.set_repo_pipeline_commit ctx.state repo.url ~pipeline ~commit:n.sha; | ||
(* To send a DM, channel parameter is set to the user id of the recipient *) | ||
Lwt.return [ res.user.id ] | ||
| Error e -> | ||
log#warn "couldn't match commit email %s to slack profile: %s" n.commit.commit.author.email e; | ||
Lwt.return [] | ||
end | ||
else Lwt.return [] | ||
in | ||
let%lwt chans = | ||
let default = Stdlib.Option.to_list cfg.prefix_rules.default_channel in | ||
match notify_channels with | ||
| false -> Lwt.return [] | ||
| true -> | ||
match branches with | ||
| [] -> Lwt.return [] | ||
| _ -> | ||
match cfg.main_branch_name with | ||
| None -> Lwt.return default | ||
| Some main_branch_name -> | ||
match List.exists (fun ({ name } : branch) -> String.equal name main_branch_name) branches with | ||
| false -> | ||
(* non-main branch build notifications go to default channel to reduce spam in topic channels *) | ||
Lwt.return default | ||
| true -> | ||
let sha = n.commit.sha in | ||
(match%lwt Github_api.get_api_commit ~ctx ~repo ~sha with | ||
| Error e -> action_error e | ||
| Ok commit -> Lwt.return @@ partition_commit cfg commit.files) | ||
in | ||
Lwt.return (direct_message @ chans) | ||
in | ||
|
||
let%lwt recipients = | ||
if Context.is_pipeline_allowed ctx repo.url ~pipeline then begin | ||
let repo_state = State.find_or_add_repo ctx.state repo.url in | ||
match Rule.Status.match_rules ~rules n with | ||
| Some (Ignore, _, _) | None -> Lwt.return [] | ||
| Some (Allow, notify_channels, notify_dm) -> action_on_match n.branches ~notify_channels ~notify_dm | ||
| Some (Allow_once, notify_channels, notify_dm) -> | ||
let branches = | ||
match StringMap.find_opt pipeline repo_state.pipeline_statuses with | ||
| Some branch_statuses -> | ||
let has_same_status_as_prev (branch : branch) = | ||
match StringMap.find_opt branch.name branch_statuses with | ||
| Some { status; _ } when status = current_status -> true | ||
| _ -> false | ||
in | ||
let branches = List.filter (Fun.negate has_same_status_as_prev) n.branches in | ||
branches | ||
| None -> n.branches | ||
in | ||
let notify_dm = | ||
notify_dm && not (State.mem_repo_pipeline_commits ctx.state repo.url ~pipeline ~commit:n.sha) | ||
let full_build_failed = | ||
n.state = Failure && Re2.matches buildkite_is_failed_re (Option.default "" n.description) | ||
in | ||
action_on_match branches ~notify_channels ~notify_dm | ||
(* for failing states, if we only allow one notification, it must be the final one, not an intermediate one *) | ||
(match n.state <> Failure || full_build_failed with | ||
| false -> Lwt.return [] | ||
| true -> | ||
Comment on lines
+185
to
+191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above |
||
let branches = | ||
match StringMap.find_opt pipeline repo_state.pipeline_statuses with | ||
| Some branch_statuses -> | ||
let has_same_status_as_prev (branch : branch) = | ||
match StringMap.find_opt branch.name branch_statuses with | ||
| Some { status; _ } when status = current_status -> true | ||
| _ -> false | ||
in | ||
let branches = List.filter (Fun.negate has_same_status_as_prev) n.branches in | ||
branches | ||
| None -> n.branches | ||
in | ||
let notify_dm = | ||
notify_dm && not (State.mem_repo_pipeline_commits ctx.state repo.url ~pipeline ~commit:n.sha) | ||
in | ||
action_on_match branches ~notify_channels ~notify_dm) | ||
end | ||
else Lwt.return [] | ||
in | ||
|
@@ -265,7 +281,7 @@ module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct | |
Lwt.return notifs | ||
| Status n -> | ||
let%lwt channels = partition_status ctx n in | ||
let notifs = List.map (generate_status_notification cfg n) channels in | ||
let notifs = List.map (generate_status_notification ctx cfg n) channels in | ||
Lwt.return notifs | ||
|
||
let send_notifications (ctx : Context.t) notifications = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,32 +40,3 @@ module Re2 = struct | |
let wrap s = create_exn s | ||
let unwrap = Re2.to_string | ||
end | ||
|
||
open Devkit | ||
|
||
let fmt_error ?exn fmt = | ||
Printf.ksprintf | ||
(fun s -> | ||
match exn with | ||
| Some exn -> Error (s ^ " : exn " ^ Exn.str exn) | ||
| None -> Error s) | ||
fmt | ||
|
||
let first_line s = | ||
match String.split_on_char '\n' s with | ||
| x :: _ -> x | ||
| [] -> s | ||
|
||
let decode_string_pad s = Stre.rstrip ~chars:"= \n\r\t" s |> Base64.decode_exn ~pad:false | ||
|
||
let http_request ?headers ?body meth path = | ||
let setup h = | ||
Curl.set_followlocation h true; | ||
Curl.set_maxredirs h 1 | ||
in | ||
match%lwt Web.http_request_lwt ~setup ~ua:"monorobot" ~verbose:true ?headers ?body meth path with | ||
| `Ok s -> Lwt.return @@ Ok s | ||
| `Error e -> Lwt.return @@ Error e | ||
|
||
let sign_string_sha256 ~key ~basestring = | ||
Cstruct.of_string basestring |> Nocrypto.Hash.SHA256.hmac ~key:(Cstruct.of_string key) |> Hex.of_cstruct |> Hex.show | ||
Comment on lines
-43
to
-71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved these more general/utility functions to the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
open Devkit | ||
|
||
module StringMap = Common.StringMap | ||
|
||
let fmt_error ?exn fmt = | ||
Printf.ksprintf | ||
(fun s -> | ||
match exn with | ||
| Some exn -> Error (s ^ " : exn " ^ Exn.str exn) | ||
| None -> Error s) | ||
fmt | ||
|
||
let first_line s = | ||
match String.split_on_char '\n' s with | ||
| x :: _ -> x | ||
| [] -> s | ||
|
||
let decode_string_pad s = Stre.rstrip ~chars:"= \n\r\t" s |> Base64.decode_exn ~pad:false | ||
|
||
let http_request ?headers ?body meth path = | ||
let setup h = | ||
Curl.set_followlocation h true; | ||
Curl.set_maxredirs h 1 | ||
in | ||
match%lwt Web.http_request_lwt ~setup ~ua:"monorobot" ~verbose:true ?headers ?body meth path with | ||
| `Ok s -> Lwt.return @@ Ok s | ||
| `Error e -> Lwt.return @@ Error e | ||
|
||
let sign_string_sha256 ~key ~basestring = | ||
Cstruct.of_string basestring |> Nocrypto.Hash.SHA256.hmac ~key:(Cstruct.of_string key) |> Hex.of_cstruct |> Hex.show | ||
|
||
module Build = struct | ||
let new_failed_steps (n : Github_t.status_notification) (repo_state : State_t.repo_state) pipeline = | ||
let to_failed_steps branch step statuses acc = | ||
(* check if step of an allowed pipeline *) | ||
match step with | ||
| step when step = pipeline -> acc | ||
| step when not @@ Devkit.Stre.starts_with step pipeline -> acc | ||
| _ -> | ||
(* check if this step failed *) | ||
match StringMap.find_opt branch statuses with | ||
| Some (s : State_t.build_status) when s.status = Failure -> | ||
(match s.current_failed_commit, s.original_failed_commit with | ||
| Some _, _ -> | ||
(* if we have a value for current_failed_commit, this step was already failed and notified *) | ||
acc | ||
| None, Some { build_link = Some build_link; sha; _ } when sha = n.commit.sha -> | ||
(* we need to check the value of the commit sha to avoid false positives *) | ||
(step, build_link) :: acc | ||
| _ -> acc) | ||
| _ -> acc | ||
in | ||
match n.state = Failure, n.branches with | ||
| false, _ | true, [] -> [] | ||
| true, [ branch ] -> StringMap.fold (to_failed_steps branch.name) repo_state.pipeline_statuses [] | ||
| true, _ -> [] | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the only change, which changed the indentation of the
action_on_match
fn, hence the big diff, but the function itself wasn't changed