diff options
author | Francois Kritzinger <francois@codesynthesis.com> | 2024-12-03 14:51:16 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2024-12-10 16:44:55 +0200 |
commit | e28291b10fa2fbe12d33eba5acfc7de62b0f1dcc (patch) | |
tree | f6b2de1c2ee00c38822c60cd012202cbb417a245 | |
parent | c05051582afa6d778edf544bf8ccd9392ef64bb0 (diff) |
Handle completed check_suite webhook events
-rw-r--r-- | mod/mod-ci-github-gh.cxx | 16 | ||||
-rw-r--r-- | mod/mod-ci-github-gh.hxx | 7 | ||||
-rw-r--r-- | mod/mod-ci-github.cxx | 105 | ||||
-rw-r--r-- | mod/mod-ci-github.hxx | 8 |
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 |