aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancois Kritzinger <francois@codesynthesis.com>2024-06-03 09:55:36 +0200
committerFrancois Kritzinger <francois@codesynthesis.com>2024-12-10 16:34:15 +0200
commitdd0fbb6e941bf77204c7172f2e1498d9d5d7a3b3 (patch)
treedef61787bc0881d5c5dde27d3d7e580c502db2c9
parent99a76da2a6c6b9ea4db63e1eba08d59869f6133c (diff)
Create conclusion CR for check suites and update it in build_built()
-rw-r--r--mod/mod-ci-github-service-data.cxx15
-rw-r--r--mod/mod-ci-github-service-data.hxx12
-rw-r--r--mod/mod-ci-github.cxx261
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),