diff options
author | Francois Kritzinger <francois@codesynthesis.com> | 2024-06-03 09:55:36 +0200 |
---|---|---|
committer | Francois Kritzinger <francois@codesynthesis.com> | 2024-12-10 16:34:15 +0200 |
commit | dd0fbb6e941bf77204c7172f2e1498d9d5d7a3b3 (patch) | |
tree | def61787bc0881d5c5dde27d3d7e580c502db2c9 | |
parent | 99a76da2a6c6b9ea4db63e1eba08d59869f6133c (diff) |
Create conclusion CR for check suites and update it in build_built()
-rw-r--r-- | mod/mod-ci-github-service-data.cxx | 15 | ||||
-rw-r--r-- | mod/mod-ci-github-service-data.hxx | 12 | ||||
-rw-r--r-- | mod/mod-ci-github.cxx | 261 |
3 files changed, 230 insertions, 58 deletions
diff --git a/mod/mod-ci-github-service-data.cxx b/mod/mod-ci-github-service-data.cxx index 561752d..e2ed904 100644 --- a/mod/mod-ci-github-service-data.cxx +++ b/mod/mod-ci-github-service-data.cxx @@ -70,7 +70,14 @@ namespace brep build_state s (to_build_state (p.next_expect_member_string ("state"))); bool ss (p.next_expect_member_boolean<bool> ("state_synced")); - check_runs.emplace_back (move (bid), move (nm), move (nid), s, ss); + optional<result_status> rs; + { + string* v (p.next_expect_member_string_null ("status")); + if (v != nullptr) + rs = bbot::to_result_status (*v); + } + + check_runs.emplace_back (move (bid), move (nm), move (nid), s, ss, rs); p.next_expect (event::end_object); } @@ -176,6 +183,12 @@ namespace brep s.member ("state", to_string (cr.state)); s.member ("state_synced", cr.state_synced); + s.member_name ("status"); + if (cr.status) + s.value (to_string (*cr.status)); + else + s.value (nullptr); + s.end_object (); } s.end_array (); diff --git a/mod/mod-ci-github-service-data.hxx b/mod/mod-ci-github-service-data.hxx index 21c8a8f..5573d90 100644 --- a/mod/mod-ci-github-service-data.hxx +++ b/mod/mod-ci-github-service-data.hxx @@ -24,12 +24,14 @@ namespace brep // struct check_run { - string build_id; // Full build id. - string name; // Potentially shortened build id. - optional<string> node_id; // GitHub id. + string build_id; // Full build id. + string name; // Potentially shortened build id. + optional<string> node_id; // GitHub id. - build_state state; - bool state_synced; + build_state state; + bool state_synced; + + optional<result_status> status; // Only present if state is built. string state_string () const diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx index ae736ca..d5131cf 100644 --- a/mod/mod-ci-github.cxx +++ b/mod/mod-ci-github.cxx @@ -391,6 +391,51 @@ namespace brep } } + // Let's capitalize the synthetic check run names to make them easier to + // distinguish from the regular ones. + // + static string merge_check_run_name ("MERGE-COMMIT"); + static string conclusion_check_run_name ("CONCLUSION"); + + // Return the colored circle corresponding to a result_status. + // + static string + circle (result_status rs) + { + switch (rs) + { + case result_status::success: return "\U0001F7E2"; // Green circle. + case result_status::warning: return "\U0001F7E0"; // Orange circle. + case result_status::error: + case result_status::abort: + case result_status::abnormal: return "\U0001F534"; // Red circle. + + // Valid values we should never encounter. + // + case result_status::skip: + case result_status::interrupt: + throw invalid_argument ("unexpected result_status value: " + + to_string (rs)); + } + + return ""; // Should never reach. + } + + // Make a check run summary from a CI start_result. + // + static string + to_check_run_summary (const optional<ci_start::start_result>& r) + { + string s; + + s = "```\n"; + if (r) s += r->message; + else s += "Internal service error"; + s += "\n```"; + + return s; + } + bool ci_github:: handle_check_suite_request (gh_check_suite_event cs, bool warning_success) { @@ -412,20 +457,48 @@ namespace brep l3 ([&]{trace << "installation_access_token { " << *iat << " }";}); - // Submit the CI request. + service_data sd (warning_success, + iat->token, + iat->expires_at, + cs.installation.id, + move (cs.repository.node_id), + move (cs.check_suite.head_sha)); + + // Create the conclusion check run. + // + { + check_run cr; + cr.name = conclusion_check_run_name; + + if (gq_create_check_run (error, + cr, + iat->token, + sd.repository_node_id, + sd.report_sha, + nullopt /* details_url */, + build_state::building)) + { + l3 ([&]{trace << "created check_run { " << cr << " }";}); + + sd.conclusion_node_id = move (cr.node_id); + } + else + { + // We could try to carry on in this case by either updating or + // creating this conclusion check run later. But let's not complicate + // things for now. + // + fail << "check suite " << cs.check_suite.node_id + << ": unable to create conclusion check run"; + } + } + + // Start CI for the check suite. // repository_location rl (cs.repository.clone_url + '#' + cs.check_suite.head_branch, repository_type::git); - string sd (service_data (warning_success, - move (iat->token), - iat->expires_at, - cs.installation.id, - move (cs.repository.node_id), - move (cs.check_suite.head_sha)) - .json ()); - // @@ What happens if we call this functions with an already existing // node_id (e.g., replay attack). See the UUID header above. // @@ -433,16 +506,48 @@ namespace brep start (error, warn, verb_ ? &trace : nullptr, - tenant_service (move (cs.check_suite.node_id), - "ci-github", - move (sd)), + tenant_service (cs.check_suite.node_id, "ci-github", sd.json ()), move (rl), vector<package> {}, nullopt, /* client_ip */ nullopt /* user_agent */)); - if (!r) - fail << "unable to submit CI request"; + if (!r || r->status != 200) + { + // Update the conclusion check run with failure. + // + result_status rs (result_status::error); + + optional<gq_built_result> br ( + gq_built_result (gh_to_conclusion (rs, sd.warning_success), + circle (rs) + ' ' + ucase (to_string (rs)), + to_check_run_summary (r))); + + check_run cr; + + // Set some fields for display purposes. + // + cr.node_id = *sd.conclusion_node_id; + cr.name = conclusion_check_run_name; + + if (gq_update_check_run (error, + cr, + iat->token, + sd.repository_node_id, + *sd.conclusion_node_id, + nullopt /* details_url */, + build_state::built, + move (br))) + { + l3 ([&]{trace << "updated check_run { " << cr << " }";}); + } + else + { + fail << "check suite " << cs.check_suite.node_id + << ": unable to update conclusion check_run " + << *sd.conclusion_node_id; + } + } return true; } @@ -594,41 +699,14 @@ namespace brep chrono::seconds (0) /* delay */)); if (!tid) - fail << "unable to create unloaded CI request"; - - return true; - } - - // Return the colored circle corresponding to a result_status. - // - static string - circle (result_status rs) - { - switch (rs) { - case result_status::success: return "\U0001F7E2"; // Green circle. - case result_status::warning: return "\U0001F7E0"; // Orange circle. - case result_status::error: - case result_status::abort: - case result_status::abnormal: return "\U0001F534"; // Red circle. - - // Valid values we should never encounter. - // - case result_status::skip: - case result_status::interrupt: - throw invalid_argument ("unexpected result_status value: " + - to_string (rs)); + fail << "pull request " << pr.pull_request.node_id + << ": unable to create unloaded CI request"; } - return ""; // Should never reach. + return true; } - // Let's capitalize the synthetic check run names to make them easier to - // distinguish from the regular ones. - // - static string merge_check_run_name ("MERGE-COMMIT"); - static string conclusion_check_run_name ("CONCLUSION"); - function<optional<string> (const tenant_service&)> ci_github:: build_unloaded (tenant_service&& ts, const diag_epilogue& log_writer) const noexcept @@ -945,16 +1023,10 @@ namespace brep if (!r || r->status != 200) { - string msg; - msg = "```\n"; - if (r) msg += r->message; - else msg += "Internal service error"; - msg += "\n```"; - if (auto cr = update_synthetic_cr (conclusion_node_id, conclusion_check_run_name, result_status::error, - move (msg))) + to_check_run_summary (r))) { l3 ([&]{trace << "updated check_run { " << *cr << " }";}); } @@ -1415,11 +1487,38 @@ namespace brep return nullptr; } + // Absent if have any unbuilt check runs. + // + optional<result_status> conclusion (*b.status); + check_run cr; // Updated check run. { string bid (gh_check_run_name (b)); // Full Build ID. - if (check_run* scr = sd.find_check_run (bid)) + optional<check_run> scr; + for (check_run& cr: sd.check_runs) + { + if (cr.build_id == bid) + { + assert (!scr); + scr = move (cr); + } + else + { + if (cr.state == build_state::built) + { + if (conclusion) + *conclusion |= *cr.status; + } + else + conclusion = nullopt; + } + + if (scr && !conclusion.has_value ()) + break; + } + + if (scr) { if (scr->state != build_state::building) { @@ -1432,6 +1531,8 @@ namespace brep if (scr->state == build_state::built) return nullptr; + // Don't move from scr because we search sd.check_runs below. + // cr = move (*scr); } else @@ -1615,6 +1716,62 @@ namespace brep l3 ([&]{trace << "created check_run { " << cr << " }";}); } } + + if (cr.state == build_state::built) + { + // Check run was created/updated successfully to built. + // + // @@ TMP Feels like this should also be done inside + // gq_{create,update}_check_run() -- where cr.state is set if the + // create/update succeeds -- but I think we didn't want to pass a + // result_status into a gq_ function because converting to a GitHub + // conclusion/title/summary is reasonably complicated. + // + cr.status = b.status; + + // Update the conclusion check run if all check runs are now built. + // + if (conclusion) + { + assert (sd.conclusion_node_id); + + // Update the conclusion check run with success. + // + result_status rs (*conclusion); + + optional<gq_built_result> br ( + gq_built_result (gh_to_conclusion (rs, sd.warning_success), + circle (rs) + ' ' + ucase (to_string (rs)), + "All configurations are built")); + + check_run cr; + + // Set some fields for display purposes. + // + cr.node_id = *sd.conclusion_node_id; + cr.name = conclusion_check_run_name; + + if (gq_update_check_run (error, + cr, + iat->token, + sd.repository_node_id, + *sd.conclusion_node_id, + nullopt /* details_url */, + build_state::built, + move (br))) + { + l3 ([&]{trace << "updated check_run { " << cr << " }";}); + } + else + { + // Nothing we can do here except log the error. + // + error << "check suite " << ts.id + << ": unable to update conclusion check run " + << *sd.conclusion_node_id; + } + } + } } return [iat = move (new_iat), |