aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancois Kritzinger <francois@codesynthesis.com>2024-12-03 14:51:16 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2024-12-10 16:44:55 +0200
commite28291b10fa2fbe12d33eba5acfc7de62b0f1dcc (patch)
treef6b2de1c2ee00c38822c60cd012202cbb417a245
parentc05051582afa6d778edf544bf8ccd9392ef64bb0 (diff)
Handle completed check_suite webhook events
-rw-r--r--mod/mod-ci-github-gh.cxx16
-rw-r--r--mod/mod-ci-github-gh.hxx7
-rw-r--r--mod/mod-ci-github.cxx105
-rw-r--r--mod/mod-ci-github.hxx8
4 files changed, 130 insertions, 6 deletions
diff --git a/mod/mod-ci-github-gh.cxx b/mod/mod-ci-github-gh.cxx
index f0de991..a25e52c 100644
--- a/mod/mod-ci-github-gh.cxx
+++ b/mod/mod-ci-github-gh.cxx
@@ -123,7 +123,7 @@ namespace brep
{
p.next_expect (event::begin_object);
- bool ni (false), hb (false), hs (false);
+ bool ni (false), hb (false), hs (false), cc (false), co (false);
// Skip unknown/uninteresting members.
//
@@ -142,12 +142,22 @@ namespace brep
head_branch = *v;
}
else if (c (hs, "head_sha")) head_sha = p.next_expect_string ();
+ else if (c (cc, "latest_check_runs_count"))
+ check_runs_count = p.next_expect_number <size_t> ();
+ else if (c (co, "conclusion"))
+ {
+ string* v (p.next_expect_string_null ());
+ if (v != nullptr)
+ conclusion = *v;
+ }
else p.next_expect_value_skip ();
}
if (!ni) missing_member (p, "gh_check_suite", "node_id");
if (!hb) missing_member (p, "gh_check_suite", "head_branch");
if (!hs) missing_member (p, "gh_check_suite", "head_sha");
+ if (!cc) missing_member (p, "gh_check_suite", "latest_check_runs_count");
+ if (!co) missing_member (p, "gh_check_suite", "conclusion");
}
ostream&
@@ -155,7 +165,9 @@ namespace brep
{
os << "node_id: " << cs.node_id
<< ", head_branch: " << (cs.head_branch ? *cs.head_branch : "null")
- << ", head_sha: " << cs.head_sha;
+ << ", head_sha: " << cs.head_sha
+ << ", latest_check_runs_count: " << cs.check_runs_count
+ << ", conclusion: " << (cs.conclusion ? *cs.conclusion : "null");
return os;
}
diff --git a/mod/mod-ci-github-gh.hxx b/mod/mod-ci-github-gh.hxx
index 392c0e8..05c289e 100644
--- a/mod/mod-ci-github-gh.hxx
+++ b/mod/mod-ci-github-gh.hxx
@@ -51,6 +51,9 @@ namespace brep
optional<string> head_branch;
string head_sha;
+ size_t check_runs_count;
+ optional<string> conclusion;
+
explicit
gh_check_suite (json::parser&);
@@ -110,8 +113,8 @@ namespace brep
build_state
gh_from_status (const string&);
- // If warning_success is true, then map result_status::warning to SUCCESS
- // and to FAILURE otherwise.
+ // If warning_success is true, then map result_status::warning to `SUCCESS`
+ // and to `FAILURE` otherwise.
//
// Throw invalid_argument in case of unsupported result_status value
// (currently skip, interrupt).
diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx
index 394638a..14b3c00 100644
--- a/mod/mod-ci-github.cxx
+++ b/mod/mod-ci-github.cxx
@@ -669,10 +669,111 @@ namespace brep
//
// 2. Verify it is completed.
//
- // 3. Verify (like in build_built()) that all the check runs are
+ // 3. Verify the check run counts match.
+ //
+ // 4. Verify (like in build_built()) that all the check runs are
// completed.
//
- // 4. Verify the result matches what GitHub thinks it is (if easy).
+ // 5. Verify the result matches what GitHub thinks it is.
+
+ HANDLER_DIAG;
+
+ l3 ([&]{trace << "check_suite event { " << cs << " }";});
+
+ // Service id that uniquely identifies the CI tenant.
+ //
+ string sid (cs.repository.node_id + ':' + cs.check_suite.head_sha);
+
+ // The common log entry subject.
+ //
+ string sub ("check suite " + cs.check_suite.node_id + '/' + sid);
+
+ // Load the service data.
+ //
+ service_data sd;
+
+ if (optional<tenant_data> d = find (*build_db_, "ci-github", sid))
+ {
+ try
+ {
+ sd = service_data (*d->service.data);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "failed to parse service data: " << e;
+ }
+ }
+ else
+ {
+ error << sub << ": tenant_service does not exist";
+ return true;
+ }
+
+ // Verify the completed flag and the number of check runs.
+ //
+ if (!sd.completed)
+ {
+ error << sub << " service data complete flag is false";
+ return true;
+ }
+
+ // Received count will be one higher because we don't store the conclusion
+ // check run.
+ //
+ size_t check_runs_count (sd.check_runs.size () + 1);
+
+ if (check_runs_count == 1)
+ {
+ error << sub << ": no check runs in service data";
+ return true;
+ }
+
+ if (cs.check_suite.check_runs_count != check_runs_count)
+ {
+ error << sub << ": check runs count " << cs.check_suite.check_runs_count
+ << " does not match service data count " << check_runs_count;
+ return true;
+ }
+
+ // Verify that all the check runs are built and compute the summary
+ // conclusion.
+ //
+ result_status conclusion (result_status::success);
+
+ for (const check_run& cr: sd.check_runs)
+ {
+ if (cr.state == build_state::built)
+ {
+ assert (cr.status.has_value ());
+ conclusion |= *cr.status;
+ }
+ else
+ {
+ error << sub << ": unbuilt check run in service data";
+ return true;
+ }
+ }
+
+ // Verify the conclusion.
+ //
+ if (!cs.check_suite.conclusion)
+ {
+ error << sub << ": absent conclusion in completed check suite";
+ return true;
+ }
+
+ // Note that the case mismatch is due to GraphQL (gh_conclusion())
+ // requiring uppercase conclusion values while the received webhook values
+ // are lower case.
+ //
+ string gh_conclusion (gh_to_conclusion (conclusion, warning_success));
+
+ if (icasecmp (*cs.check_suite.conclusion, gh_conclusion) != 0)
+ {
+ error << sub << ": conclusion " << *cs.check_suite.conclusion
+ << " does not match service data conclusion " << gh_conclusion;
+ return true;
+ }
return true;
}
diff --git a/mod/mod-ci-github.hxx b/mod/mod-ci-github.hxx
index 104f889..f97bf05 100644
--- a/mod/mod-ci-github.hxx
+++ b/mod/mod-ci-github.hxx
@@ -90,6 +90,14 @@ namespace brep
bool
handle_check_suite_request (gh_check_suite_event, bool warning_success);
+ // Handle the check_suite event `completed` action.
+ //
+ // If warning_success is true, then map result_status::warning to SUCCESS
+ // and to FAILURE otherwise.
+ //
+ bool
+ handle_check_suite_completed (gh_check_suite_event, bool warning_success);
+
// Handle the check_run event `rerequested` action.
//
// If warning_success is true, then map result_status::warning to SUCCESS