aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mod/mod-ci-github-gh.cxx6
-rw-r--r--mod/mod-ci-github-gh.hxx8
-rw-r--r--mod/mod-ci-github-gq.cxx425
-rw-r--r--mod/mod-ci-github-gq.hxx8
-rw-r--r--mod/mod-ci-github-service-data.cxx10
-rw-r--r--mod/mod-ci-github-service-data.hxx12
-rw-r--r--mod/mod-ci-github.cxx40
-rw-r--r--mod/mod-ci-github.hxx2
-rw-r--r--mod/module.cli2
9 files changed, 417 insertions, 96 deletions
diff --git a/mod/mod-ci-github-gh.cxx b/mod/mod-ci-github-gh.cxx
index 2e886ac..42afe1b 100644
--- a/mod/mod-ci-github-gh.cxx
+++ b/mod/mod-ci-github-gh.cxx
@@ -211,9 +211,9 @@ namespace brep
// one can be null. It's unclear under what circumstances, but it
// shouldn't happen unless something is broken.
//
- string* v (p.next_expect_number_null ());
+ optional<uint64_t> v (p.next_expect_number_null<uint64_t> ());
- if (v == nullptr)
+ if (!v)
throw_json (p, "check_suite.app.id is null");
app_id = *v;
@@ -310,7 +310,7 @@ namespace brep
//
while (p.next_expect (event::name, event::end_object))
{
- if (c (ai, "id")) app_id = p.next_expect_number ();
+ if (c (ai, "id")) app_id = p.next_expect_number<uint64_t> ();
else p.next_expect_value_skip ();
}
diff --git a/mod/mod-ci-github-gh.hxx b/mod/mod-ci-github-gh.hxx
index 91f5bfe..5f6e5b7 100644
--- a/mod/mod-ci-github-gh.hxx
+++ b/mod/mod-ci-github-gh.hxx
@@ -66,7 +66,7 @@ namespace brep
size_t check_runs_count;
optional<string> conclusion;
- string app_id;
+ uint64_t app_id;
explicit
gh_check_suite_ex (json::parser&);
@@ -97,7 +97,7 @@ namespace brep
string details_url;
gh_check_suite check_suite;
- string app_id;
+ uint64_t app_id;
explicit
gh_check_run_ex (json::parser&);
@@ -129,7 +129,7 @@ namespace brep
// simplicity we emulate check_suite and check_run by storing the app-id
// webhook query parameter here.
//
- string app_id;
+ uint64_t app_id;
explicit
gh_pull_request (json::parser&);
@@ -256,7 +256,7 @@ namespace brep
// emulate check_suite and check_run by storing the app-id webhook query
// parameter here.
//
- string app_id;
+ uint64_t app_id;
explicit
gh_push_event (json::parser&);
diff --git a/mod/mod-ci-github-gq.cxx b/mod/mod-ci-github-gq.cxx
index 7abd709..763842c 100644
--- a/mod/mod-ci-github-gq.cxx
+++ b/mod/mod-ci-github-gq.cxx
@@ -19,6 +19,7 @@ namespace brep
static const string& gq_name (const string&);
static string gq_name (string&&);
static string gq_str (const string&);
+ static string gq_int (uint64_t);
static string gq_bool (bool);
static const string& gq_enum (const string&);
static string gq_enum (string&&);
@@ -187,7 +188,7 @@ namespace brep
// }
//
static vector<gh_check_run>
- gq_parse_response_check_runs (json::parser& p)
+ gq_parse_mutate_check_runs_response (json::parser& p)
{
using event = json::event;
@@ -223,20 +224,199 @@ namespace brep
return r;
}
- // Send a GraphQL mutation request `rq` that creates or updates one or more
- // check runs. The requested build state is taken from each check_run
- // object. Update the check runs in `crs` with the new data (state, node ID
- // if unset, and state_synced). Return false and issue diagnostics if the
- // request failed.
+ // Serialize a query that fetches the most recent check runs on a commit.
//
+ static string
+ gq_query_get_check_runs (uint64_t ai, // App id
+ const string& ri, // Repository id
+ const string& ci, // Commit id
+ size_t cn) // Check run count
+ {
+
+ ostringstream os;
+
+ os << "query {" << '\n';
+
+ // Get the repository node.
+ //
+ os << "node(id: " << gq_str (ri) << ") {" << '\n'
+ << "... on Repository {" << '\n';
+
+ // Get the commit object.
+ //
+ os << " object(oid: " << gq_str (ci) << ") {" << '\n'
+ << " ... on Commit {" << '\n';
+
+ // Get the check suites on the commit, filtering by our app id. (Note that
+ // as a result there should never be more than one check suite; see
+ // below.)
+ //
+ os << " checkSuites(first: 1" << '\n'
+ << " filterBy: {appId: " << gq_int (ai) << "}) {" << '\n'
+ << " edges { node {" << '\n';
+
+ // Get the check suite's last N check runs (last:).
+ //
+ // Filter by App id because apparently an App can create check runs in
+ // another App's check suite.
+ //
+ // Also ask for the latest check runs only (checkType: LATEST) otherwise
+ // we could receive multiple check runs with the same name. Although this
+ // appears to be the default it's not documented anywhere so best make it
+ // explicit.
+ //
+ // Note that the selection set (fields to be returned) must match that of
+ // the check run mutations (create/update) generated by
+ // gq_mutation_{create,update}_check_runs().
+ //
+ os << " checkRuns(last: " << gq_int (cn) << '\n'
+ << " filterBy: {appId: " << gq_int (ai) << '\n'
+ << " checkType: LATEST}) {" << '\n'
+ << " edges { node { node_id: id name status } }" << '\n'
+ << " }" /* checkRuns */ << '\n'
+ << " } }" /* node, edges */ << '\n'
+ << " }" /* checkSuites */ << '\n'
+ << " }" /* ... on Commit */ << '\n'
+ << " }" /* object */ << '\n'
+ << "}" /* ... on Repository */ << '\n'
+ << "}" /* node */ << '\n';
+
+ os << '}' /* query */ << '\n';
+
+ return os.str ();
+ }
+
+ // Parse a response to a "get check runs for repository/commit" GraphQL
+ // query as constructed by gq_query_get_check_runs().
+ //
+ // Note that there might be other check suites on this commit but they will
+ // all have been created by other apps (GitHub never creates more than one
+ // check suite per app). Therefore our query filters by app id and as a
+ // result there should never be more than one check suite in the response.
+ //
+ // Throw invalid_json_input.
+ //
+ // Example response (only the part we need to parse here):
+ //
+ // {
+ // "node": {
+ // "object":{
+ // "checkSuites":{
+ // "edges":[
+ // {"node":{
+ // "checkRuns":{
+ // "edges":[
+ // {"node":{"id":"CR_kwDOLc8CoM8AAAAImvJPfw",
+ // "name":"check_run0",
+ // "status":"QUEUED"}},
+ // {"node":{"id":"CR_kwDOLc8CoM8AAAAImvJP_Q",
+ // "name":"check_run1",
+ // "status":"QUEUED"}}
+ // ]
+ // }
+ // }
+ // }
+ // ]
+ // }
+ // }
+ // }
+ // }
+ //
+ static vector<gh_check_run>
+ gq_parse_get_check_runs_response (json::parser& p)
+ {
+ using event = json::event;
+
+ vector<gh_check_run> r;
+
+ gq_parse_response (p, [&r] (json::parser& p)
+ {
+ p.next_expect (event::begin_object); // Outermost {
+
+ p.next_expect_member_object ("node"); // Repository node
+ p.next_expect_member_object ("object"); // Commmit
+ p.next_expect_member_object ("checkSuites");
+ p.next_expect_member_array ("edges"); // Check suites array
+ p.next_expect (event::begin_object); // Check suite outer {
+ p.next_expect_member_object ("node");
+ p.next_expect_member_object ("checkRuns");
+ p.next_expect_member_array ("edges"); // Check runs array
+
+ // Parse the check run elements of the `edges` array. E.g.:
+ //
+ // {
+ // "node":{
+ // "node_id":"CR_kwDOLc8CoM8AAAAIobBFlA",
+ // "name":"CONCLUSION",
+ // "status":"IN_PROGRESS"
+ // }
+ // }
+ //
+ while (p.next_expect (event::begin_object, event::end_array))
+ {
+ p.next_expect_name ("node");
+ r.emplace_back (p); // Parse check run: { members... }
+ p.next_expect (event::end_object);
+ }
+
+ p.next_expect (event::end_object); // checkRuns
+ p.next_expect (event::end_object); // Check suite node
+ p.next_expect (event::end_object); // Check suite outer }
+ p.next_expect (event::end_array); // Check suites edges
+ p.next_expect (event::end_object); // checkSuites
+ p.next_expect (event::end_object); // Commit
+ p.next_expect (event::end_object); // Repository node
+
+ p.next_expect (event::end_object); // Outermost }
+ });
+
+ return r;
+ }
+
+ // Serialize a GraphQL operation (query/mutation) into a GraphQL request.
+ //
+ // This is essentially a JSON object with a "query" string member containing
+ // the GraphQL operation. For example:
+ //
+ // { "query": "mutation { cr0:createCheckRun(... }" }
+ //
+ static string
+ gq_serialize_request (const string& o)
+ {
+ string b;
+ json::buffer_serializer s (b);
+
+ s.begin_object ();
+ s.member ("query", o);
+ s.end_object ();
+
+ return b;
+ }
+
+ // Send a GraphQL mutation request `rq` that creates (create=true) or
+ // updates (create=false) one or more check runs. The requested build state
+ // is taken from each check_run object. Update the check runs in `crs` with
+ // the new data (state, node ID if unset, and state_synced). Return false
+ // and issue diagnostics if the request failed.
+ //
+ struct gq_create_data
+ {
+ uint64_t app_id;
+ reference_wrapper<const string> repository_id;
+ reference_wrapper<const string> head_sha;
+ };
+
static bool
gq_mutate_check_runs (const basic_mark& error,
- vector<check_run>& crs,
+ check_runs::iterator crs_b,
+ check_runs::iterator crs_e,
const string& iat,
- string rq)
+ string rq,
+ const optional<gq_create_data>& create_data)
{
- vector<gh_check_run> rcrs;
+ size_t crs_n (crs_e - crs_b);
+ const char* what (nullptr);
try
{
// Response type which parses a GraphQL response containing multiple
@@ -247,29 +427,104 @@ namespace brep
vector<gh_check_run> check_runs; // Received check runs.
resp (json::parser& p)
- : check_runs (gq_parse_response_check_runs (p)) {}
+ : check_runs (gq_parse_mutate_check_runs_response (p)) {}
resp () = default;
} rs;
+ what = create_data ? "create" : "update";
uint16_t sc (github_post (rs,
"graphql", // API Endpoint.
strings {"Authorization: Bearer " + iat},
move (rq)));
+ // Turns out it's not uncommon to not get a reply from GitHub if the
+ // number of check runs being created in build_queued() is large. The
+ // symptom is a 502 (Bad gateway) reply from GitHub and the theory being
+ // that their load balancer drops the connection if the request is not
+ // handled within a certain time. Note that if the number of check runs
+ // is under 100, they seem to still be created on GitHub, we just don't
+ // get the reply (and thus their node ids). So we try to re-query that
+ // information.
+ //
+ optional<uint16_t> sc1;
+ if (sc == 502 && create_data)
+ {
+ what = "re-query";
+
+ // GraphQL query which fetches the most recently-created check runs.
+ //
+ string rq (gq_serialize_request (
+ gq_query_get_check_runs (create_data->app_id,
+ create_data->repository_id,
+ create_data->head_sha,
+ crs_n)));
+
+ // Type that parses the result of the above GraphQL query.
+ //
+ struct resp
+ {
+ vector<gh_check_run> check_runs; // Received check runs.
+
+ resp (json::parser& p)
+ : check_runs (gq_parse_get_check_runs_response (p)) {}
+
+ resp () = default;
+ } rs1;
+
+ sc1 = github_post (rs1,
+ "graphql", // API Endpoint.
+ strings {"Authorization: Bearer " + iat},
+ move (rq));
+
+ if (*sc1 == 200)
+ {
+ if (rs1.check_runs.size () == crs_n)
+ {
+ // It's possible GitHub did not create all the checkruns we have
+ // requested. In which case it may return some unrelated checkruns
+ // (for example, from before re-request). So we verify we got the
+ // expected ones.
+ //
+ size_t i (0);
+ for (; i != crs_n; ++i)
+ {
+ const check_run& cr (*(crs_b + i));
+ const gh_check_run& gcr (rs1.check_runs[i]);
+
+ if (cr.name != gcr.name ||
+ cr.state != gh_from_status (gcr.status))
+ break;
+ }
+
+ if (i == crs_n)
+ {
+ rs.check_runs = move (rs1.check_runs);
+
+ // Reduce to as-if the create request succeeded.
+ //
+ what = "create";
+ sc = 200;
+ }
+ }
+ }
+ }
+
if (sc == 200)
{
- rcrs = move (rs.check_runs);
+ vector<gh_check_run>& rcrs (rs.check_runs);
- if (rcrs.size () == crs.size ())
+ if (rcrs.size () == crs_n)
{
- for (size_t i (0); i != rcrs.size (); ++i)
+ for (size_t i (0); i != crs_n; ++i)
{
+ check_run& cr (*(crs_b + i));
+
// Validate the check run in the response against the build.
//
const gh_check_run& rcr (rcrs[i]); // Received check run.
- build_state st (crs[i].state); // Requested state.
+ build_state st (cr.state); // Requested state.
build_state rst (gh_from_status (rcr.status)); // Received state.
// Note that GitHub won't allow us to change a built check run to
@@ -283,8 +538,6 @@ namespace brep
return false; // Fail because something is clearly very wrong.
}
- check_run& cr (crs[i]);
-
if (!cr.node_id)
cr.node_id = move (rcr.node_id);
@@ -298,57 +551,52 @@ namespace brep
error << "unexpected number of check_run objects in response";
}
else
- error << "failed to mutate check runs: error HTTP response status "
- << sc;
+ {
+ diag_record dr (error);
+
+ dr << "failed to " << what << " check runs: error HTTP response status "
+ << sc;
+
+ if (sc1)
+ {
+ if (*sc1 != 200)
+ dr << error << "failed to re-query check runs: error HTTP "
+ << "response status " << *sc1;
+ else
+ dr << error << "unexpected number of check_run objects in "
+ << "re-query response";
+ }
+ }
}
catch (const json::invalid_json_input& e) // struct resp (via github_post())
{
// Note: e.name is the GitHub API endpoint.
//
- error << "malformed JSON in response from " << e.name << ", line: "
- << e.line << ", column: " << e.column << ", byte offset: "
- << e.position << ", error: " << e;
+ error << "malformed JSON in " << what << " response from " << e.name
+ << ", line: " << e.line << ", column: " << e.column
+ << ", byte offset: " << e.position
+ << ", error: " << e;
}
catch (const invalid_argument& e) // github_post()
{
- error << "malformed header(s) in response: " << e;
+ error << "malformed header(s) in " << what << " response: " << e;
}
catch (const system_error& e) // github_post()
{
- error << "unable to mutate check runs (errno=" << e.code () << "): "
- << e.what ();
+ error << "unable to " << what << " check runs (errno=" << e.code ()
+ << "): " << e.what ();
}
- catch (const runtime_error& e) // gq_parse_response_check_runs()
+ catch (const runtime_error& e) // gq_parse_{mutate,get}_check_runs_response()
{
// GitHub response contained error(s) (could be ours or theirs at this
// point).
//
- error << "unable to mutate check runs: " << e;
+ error << "unable to " << what << " check runs: " << e;
}
return false;
}
- // Serialize a GraphQL operation (query/mutation) into a GraphQL request.
- //
- // This is essentially a JSON object with a "query" string member containing
- // the GraphQL operation. For example:
- //
- // { "query": "mutation { cr0:createCheckRun(... }" }
- //
- static string
- gq_serialize_request (const string& o)
- {
- string b;
- json::buffer_serializer s (b);
-
- s.begin_object ();
- s.member ("query", o);
- s.end_object ();
-
- return b;
- }
-
// Serialize `createCheckRun` mutations for one or more builds to GraphQL.
//
// The check run parameters (names, build states, details_urls, etc.) are
@@ -366,7 +614,8 @@ namespace brep
static string
gq_mutation_create_check_runs (const string& ri, // Repository ID
const string& hs, // Head SHA
- const vector<check_run>& crs)
+ brep::check_runs::iterator crs_b,
+ brep::check_runs::iterator crs_e)
{
ostringstream os;
@@ -374,9 +623,9 @@ namespace brep
// Serialize a `createCheckRun` for each build.
//
- for (size_t i (0); i != crs.size (); ++i)
+ for (brep::check_runs::iterator crs_i (crs_b); crs_i != crs_e; ++crs_i)
{
- const check_run& cr (crs[i]);
+ const check_run& cr (*crs_i);
assert (cr.state != build_state::built); // Not supported.
@@ -387,7 +636,7 @@ namespace brep
(!cr.description->title.empty () &&
!cr.description->summary.empty ()));
- string al ("cr" + to_string (i)); // Field alias.
+ string al ("cr" + to_string (crs_i - crs_b)); // Field alias.
os << gq_name (al) << ":createCheckRun(input: {" << '\n'
<< " name: " << gq_str (cr.name) << '\n'
@@ -496,7 +745,6 @@ namespace brep
return os.str ();
}
-
// Serialize an `updateCheckRun` mutation for one build to GraphQL.
//
// The `br` argument is required if the check run status is completed
@@ -568,8 +816,9 @@ namespace brep
bool
gq_create_check_runs (const basic_mark& error,
- vector<check_run>& crs,
+ brep::check_runs& crs,
const string& iat,
+ uint64_t ai,
const string& rid,
const string& hs)
{
@@ -580,16 +829,47 @@ namespace brep
assert (cr.state != build_state::built);
#endif
- string rq (
- gq_serialize_request (gq_mutation_create_check_runs (rid, hs, crs)));
+ // Trying to create a large number of check runs at once does not work.
+ // There are two failure modes:
+ //
+ // 1. Between about 40 - 60 we may get 502 (bad gateway) but the check
+ // runs are still created on GitHub. We handle this case be re-quering
+ // the check runs (see gq_mutate_check_runs() for details).
+ //
+ // 2. Above about 60 GitHub may not create all the check runs (while still
+ // responding with 502). We handle this here by batching the creation.
+ //
+ size_t n (crs.size ());
+ size_t b (n / 60 + (n % 60 != 0 ? 1 : 0));
+ size_t bn (n / b);
+
+ auto i (crs.begin ());
+ for (size_t j (0); j != b; )
+ {
+ auto e (++j != b ? (i + bn): crs.end ());
+
+ string rq (
+ gq_serialize_request (
+ gq_mutation_create_check_runs (rid, hs, i, e)));
- return gq_mutate_check_runs (error, crs, iat, move (rq));
+ if (!gq_mutate_check_runs (error,
+ i, e,
+ iat,
+ move (rq),
+ gq_create_data {ai, rid, hs}))
+ return false;
+
+ i += bn;
+ }
+
+ return true;
}
bool
gq_create_check_run (const basic_mark& error,
check_run& cr,
const string& iat,
+ uint64_t ai,
const string& rid,
const string& hs,
const optional<string>& du,
@@ -610,10 +890,14 @@ namespace brep
move (ti), move (su),
nullopt /* conclusion */)));
- vector<check_run> crs {move (cr)};
+ brep::check_runs crs {move (cr)};
crs[0].state = st;
- bool r (gq_mutate_check_runs (error, crs, iat, move (rq)));
+ bool r (gq_mutate_check_runs (error,
+ crs.begin (), crs.end (),
+ iat,
+ move (rq),
+ gq_create_data {ai, rid, hs}));
cr = move (crs[0]);
@@ -624,6 +908,7 @@ namespace brep
gq_create_check_run (const basic_mark& error,
check_run& cr,
const string& iat,
+ uint64_t ai,
const string& rid,
const string& hs,
const optional<string>& du,
@@ -639,10 +924,14 @@ namespace brep
move (br.title), move (br.summary),
move (br.conclusion))));
- vector<check_run> crs {move (cr)};
+ brep::check_runs crs {move (cr)};
crs[0].state = build_state::built;
- bool r (gq_mutate_check_runs (error, crs, iat, move (rq)));
+ bool r (gq_mutate_check_runs (error,
+ crs.begin (), crs.end (),
+ iat,
+ move (rq),
+ gq_create_data {ai, rid, hs}));
cr = move (crs[0]);
@@ -678,10 +967,14 @@ namespace brep
move (ti), move (su),
nullopt /* conclusion */)));
- vector<check_run> crs {move (cr)};
+ brep::check_runs crs {move (cr)};
crs[0].state = st;
- bool r (gq_mutate_check_runs (error, crs, iat, move (rq)));
+ bool r (gq_mutate_check_runs (error,
+ crs.begin (), crs.end (),
+ iat,
+ move (rq),
+ nullopt));
cr = move (crs[0]);
@@ -705,10 +998,14 @@ namespace brep
move (br.title), move (br.summary),
move (br.conclusion))));
- vector<check_run> crs {move (cr)};
+ brep::check_runs crs {move (cr)};
crs[0].state = build_state::built;
- bool r (gq_mutate_check_runs (error, crs, iat, move (rq)));
+ bool r (gq_mutate_check_runs (error,
+ crs.begin (), crs.end (),
+ iat,
+ move (rq),
+ nullopt));
cr = move (crs[0]);
@@ -944,7 +1241,6 @@ namespace brep
// Serialize an int to GraphQL.
//
-#if 0
static string
gq_int (uint64_t v)
{
@@ -953,7 +1249,6 @@ namespace brep
s.value (v);
return b;
}
-#endif
// Serialize a boolean to GraphQL.
//
diff --git a/mod/mod-ci-github-gq.hxx b/mod/mod-ci-github-gq.hxx
index 19c4924..5fc75aa 100644
--- a/mod/mod-ci-github-gq.hxx
+++ b/mod/mod-ci-github-gq.hxx
@@ -22,7 +22,8 @@ namespace brep
// Create a new check run on GitHub for each build with the build state,
// name, details_url, and output taken from each check_run object. Update
// `check_runs` with the new data (node id and state_synced). Return false
- // and issue diagnostics if the request failed.
+ // and issue diagnostics if the request failed. Note that in this case some
+ // elements in check_runs may still have been updated (due to batching).
//
// Throw invalid_argument if the passed data is invalid, missing, or
// inconsistent.
@@ -34,8 +35,9 @@ namespace brep
//
bool
gq_create_check_runs (const basic_mark& error,
- vector<check_run>& check_runs,
+ brep::check_runs& check_runs,
const string& installation_access_token,
+ uint64_t app_id,
const string& repository_id,
const string& head_sha);
@@ -56,6 +58,7 @@ namespace brep
gq_create_check_run (const basic_mark& error,
check_run& cr,
const string& installation_access_token,
+ uint64_t app_id,
const string& repository_id,
const string& head_sha,
const optional<string>& details_url,
@@ -77,6 +80,7 @@ namespace brep
gq_create_check_run (const basic_mark& error,
check_run& cr,
const string& installation_access_token,
+ uint64_t app_id,
const string& repository_id,
const string& head_sha,
const optional<string>& details_url,
diff --git a/mod/mod-ci-github-service-data.cxx b/mod/mod-ci-github-service-data.cxx
index c51f791..aa2e619 100644
--- a/mod/mod-ci-github-service-data.cxx
+++ b/mod/mod-ci-github-service-data.cxx
@@ -54,7 +54,7 @@ namespace brep
p.next_expect_name ("installation_access");
installation_access = gh_installation_access_token (p);
- app_id = p.next_expect_member_string ("app_id");
+ app_id = p.next_expect_member_number<uint64_t> ("app_id");
installation_id = p.next_expect_member_string ("installation_id");
repository_node_id = p.next_expect_member_string ("repository_node_id");
@@ -143,7 +143,7 @@ namespace brep
service_data (bool ws,
string iat_tok,
timestamp iat_ea,
- string aid,
+ uint64_t aid,
string iid,
string rid,
string rcu,
@@ -155,7 +155,7 @@ namespace brep
: kind (k), pre_check (pc), re_request (rr),
warning_success (ws),
installation_access (move (iat_tok), iat_ea),
- app_id (move (aid)),
+ app_id (aid),
installation_id (move (iid)),
repository_node_id (move (rid)),
repository_clone_url (move (rcu)),
@@ -171,7 +171,7 @@ namespace brep
service_data (bool ws,
string iat_tok,
timestamp iat_ea,
- string aid,
+ uint64_t aid,
string iid,
string rid,
string rcu,
@@ -185,7 +185,7 @@ namespace brep
: kind (k), pre_check (pc), re_request (rr),
warning_success (ws),
installation_access (move (iat_tok), iat_ea),
- app_id (move (aid)),
+ app_id (aid),
installation_id (move (iid)),
repository_node_id (move (rid)),
repository_clone_url (move (rcu)),
diff --git a/mod/mod-ci-github-service-data.hxx b/mod/mod-ci-github-service-data.hxx
index 5d36696..9aa512a 100644
--- a/mod/mod-ci-github-service-data.hxx
+++ b/mod/mod-ci-github-service-data.hxx
@@ -55,6 +55,8 @@ namespace brep
}
};
+ using check_runs = vector<check_run>;
+
// We have two kinds of service data that correspond to the following two
// typical scenarios (until/unless we add support for merge queues):
//
@@ -96,8 +98,8 @@ namespace brep
//
gh_installation_access_token installation_access;
- string app_id;
- string installation_id;
+ uint64_t app_id;
+ string installation_id; // @@ TMP Also actually an integer
string repository_node_id; // GitHub-internal opaque repository id.
@@ -123,7 +125,7 @@ namespace brep
//
string report_sha;
- vector<check_run> check_runs;
+ brep::check_runs check_runs;
// Flag indicating that all the elements in check_runs are built and this
// check suite is completed.
@@ -159,7 +161,7 @@ namespace brep
service_data (bool warning_success,
string iat_token,
timestamp iat_expires_at,
- string app_id,
+ uint64_t app_id,
string installation_id,
string repository_node_id,
string repository_clone_url,
@@ -174,7 +176,7 @@ namespace brep
service_data (bool warning_success,
string iat_token,
timestamp iat_expires_at,
- string app_id,
+ uint64_t app_id,
string installation_id,
string repository_node_id,
string repository_clone_url,
diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx
index b4babce..8242fc0 100644
--- a/mod/mod-ci-github.cxx
+++ b/mod/mod-ci-github.cxx
@@ -17,6 +17,8 @@
#include <mod/mod-ci-github-post.hxx>
#include <mod/mod-ci-github-service-data.hxx>
+#include <cerrno>
+#include <cstdlib> // strtoull()
#include <stdexcept>
// Resources:
@@ -261,7 +263,7 @@ namespace brep
// Process the `app-id` and `warning` webhook request query parameters.
//
- string app_id;
+ uint64_t app_id;
bool warning_success;
{
const name_values& rps (rq.parameters (1024, true /* url_only */));
@@ -281,7 +283,18 @@ namespace brep
badreq ("missing 'app-id' webhook query parameter value");
ai = true;
- app_id = *rp.value;
+
+ // Parse the app id value.
+ //
+ const char* b (rp.value->c_str ());
+ char* e (nullptr);
+ errno = 0; // We must clear it according to POSIX.
+ app_id = strtoull (b, &e, 10);
+ if (errno == ERANGE || e == b || *e != '\0')
+ {
+ badreq ("invalid 'app-id' webhook query parameter value: '" +
+ *rp.value + '\'');
+ }
}
else if (rp.name == "warning")
{
@@ -1296,7 +1309,7 @@ namespace brep
// a single request (unless something goes wrong) so store them together
// from the outset.
//
- vector<check_run> check_runs (2);
+ brep::check_runs check_runs (2);
check_run& bcr (check_runs[0]); // Build check run
check_run& ccr (check_runs[1]); // Conclusion check run
@@ -1517,7 +1530,7 @@ namespace brep
conclusion_building_summary};
if (gq_create_check_runs (error, check_runs, iat->token,
- repo_node_id, head_sha))
+ cr.check_run.app_id, repo_node_id, head_sha))
{
assert (bcr.state == build_state::queued);
assert (ccr.state == build_state::building);
@@ -1996,6 +2009,7 @@ namespace brep
if (gq_create_check_run (error,
cr,
iat->token,
+ sd.app_id,
sd.repository_node_id,
sd.report_sha,
details_url (tenant_id),
@@ -2314,7 +2328,7 @@ namespace brep
// The builds for which we will be creating check runs.
//
vector<reference_wrapper<const build>> bs;
- vector<check_run> crs; // Parallel to bs.
+ brep::check_runs crs; // Parallel to bs.
// Exclude the builds for which we won't be creating check runs.
//
@@ -2400,7 +2414,9 @@ namespace brep
if (gq_create_check_runs (error,
crs,
iat->token,
- sd.repository_node_id, sd.report_sha))
+ sd.app_id,
+ sd.repository_node_id,
+ sd.report_sha))
{
for (const check_run& cr: crs)
{
@@ -2519,7 +2535,10 @@ namespace brep
}
else
{
- // Network error during queued notification, ignore.
+ // Network error during queued notification (state unsynchronized),
+ // ignore.
+ //
+ l3 ([&]{trace << "unsynchronized check run " << bid;});
}
}
else
@@ -2880,6 +2899,7 @@ namespace brep
if (gq_create_check_run (error,
cr,
iat->token,
+ sd.app_id,
sd.repository_node_id,
sd.report_sha,
details_url (b),
@@ -3292,7 +3312,7 @@ namespace brep
}
optional<string> ci_github::
- generate_jwt (const string& app_id,
+ generate_jwt (uint64_t app_id,
const basic_mark& trace,
const basic_mark& error) const
{
@@ -3301,7 +3321,7 @@ namespace brep
{
// Look up the private key path for the app id and fail if not found.
//
- const map<string, dir_path>& pks (
+ const map<uint64_t, dir_path>& pks (
options_->ci_github_app_id_private_key ());
auto pk (pks.find (app_id));
@@ -3317,7 +3337,7 @@ namespace brep
//
jwt = brep::generate_jwt (
*options_,
- pk->second, app_id,
+ pk->second, to_string (app_id),
chrono::seconds (options_->ci_github_jwt_validity_period ()),
chrono::seconds (60));
diff --git a/mod/mod-ci-github.hxx b/mod/mod-ci-github.hxx
index 4cedc94..0c90bb1 100644
--- a/mod/mod-ci-github.hxx
+++ b/mod/mod-ci-github.hxx
@@ -139,7 +139,7 @@ namespace brep
details_url (const string& tenant) const;
optional<string>
- generate_jwt (const string& app_id,
+ generate_jwt (uint64_t app_id,
const basic_mark& trace,
const basic_mark& error) const;
diff --git a/mod/module.cli b/mod/module.cli
index ba2b986..57a5f31 100644
--- a/mod/module.cli
+++ b/mod/module.cli
@@ -858,7 +858,7 @@ namespace brep
Note: make sure to choose a strong (random) secret."
}
- std::map<string, dir_path> ci-github-app-id-private-key
+ std::map<uint64_t, dir_path> ci-github-app-id-private-key
{
"<id>=<path>",
"The private key used during GitHub API authentication for the