aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-ci-github.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'mod/mod-ci-github.cxx')
-rw-r--r--mod/mod-ci-github.cxx561
1 files changed, 63 insertions, 498 deletions
diff --git a/mod/mod-ci-github.cxx b/mod/mod-ci-github.cxx
index 179eeba..b9b24f4 100644
--- a/mod/mod-ci-github.cxx
+++ b/mod/mod-ci-github.cxx
@@ -3,14 +3,16 @@
#include <mod/mod-ci-github.hxx>
-#include <libbutl/curl.hxx>
#include <libbutl/json/parser.hxx>
-#include <libbutl/json/serializer.hxx>
#include <mod/jwt.hxx>
#include <mod/hmac.hxx>
#include <mod/module-options.hxx>
+#include <mod/mod-ci-github-gq.hxx>
+#include <mod/mod-ci-github-post.hxx>
+#include <mod/mod-ci-github-service-data.hxx>
+
#include <stdexcept>
// @@ TODO
@@ -71,8 +73,6 @@ using namespace brep::cli;
namespace brep
{
- using namespace gh;
-
ci_github::
ci_github (tenant_service_map& tsm)
: tenant_service_map_ (tsm)
@@ -263,12 +263,12 @@ namespace brep
//
if (event == "check_suite")
{
- check_suite_event cs;
+ gh_check_suite_event cs;
try
{
json::parser p (body.data (), body.size (), "check_suite event");
- cs = check_suite_event (p);
+ cs = gh_check_suite_event (p);
}
catch (const json::invalid_json_input& e)
{
@@ -330,7 +330,7 @@ namespace brep
}
bool ci_github::
- handle_check_suite_request (check_suite_event cs)
+ handle_check_suite_request (gh_check_suite_event cs)
{
HANDLER_DIAG;
@@ -340,7 +340,7 @@ namespace brep
if (!jwt)
throw server_error ();
- optional<installation_access_token> iat (
+ optional<gh_installation_access_token> iat (
obtain_installation_access_token (cs.installation.id,
move (*jwt),
error));
@@ -406,7 +406,7 @@ namespace brep
// thus would cause a spurious backwards state transition.
//
vector<reference_wrapper<const build>> bs;
- vector<service_data::check_run> crs; // Parallel to bs.
+ vector<check_run> crs; // Parallel to bs.
// Exclude builds for which this is an out of order notification.
//
@@ -423,9 +423,9 @@ namespace brep
//
// Note: never go back on the built state.
//
- string bid (check_run_name (b)); // Full Build ID.
+ string bid (gh_check_run_name (b)); // Full Build ID.
- const service_data::check_run* scr (sd.find_check_run (bid));
+ const check_run* scr (sd.find_check_run (bid));
if (scr == nullptr)
{
@@ -459,8 +459,8 @@ namespace brep
// Get a new installation access token if the current one has expired.
//
- const installation_access_token* iat (nullptr);
- optional<installation_access_token> new_iat;
+ const gh_installation_access_token* iat (nullptr);
+ optional<gh_installation_access_token> new_iat;
if (system_clock::now () > sd.installation_access.expires_at)
{
@@ -483,20 +483,15 @@ namespace brep
//
// Queue a check_run for each build.
//
- string rq (graphql_request (create_check_runs (sd.repository_id,
- sd.head_sha,
- bs,
- build_state::queued,
- &hs)));
-
- if (mutate_check_runs (crs,
- bs,
- iat->token,
- move (rq),
- build_state::queued,
- error))
- {
- for (service_data::check_run& cr: crs)
+ if (gq_create_check_runs (crs,
+ iat->token,
+ sd.repository_id, sd.head_sha,
+ bs,
+ build_state::queued,
+ hs,
+ error))
+ {
+ for (check_run& cr: crs)
l3 ([&] { trace << "created check_run { " << cr << " }"; });
}
}
@@ -529,14 +524,14 @@ namespace brep
//
for (size_t i (0); i != bs.size (); ++i)
{
- const service_data::check_run& cr (crs[i]);
+ const check_run& cr (crs[i]);
// Note that this service data may not be the same as what we observed
// in the build_queued() function above. For example, some check runs
// that we have queued may have already transitioned to building. So
// we skip any check runs that are already present.
//
- if (service_data::check_run* scr = sd.find_check_run (cr.build_id))
+ if (check_run* scr = sd.find_check_run (cr.build_id))
{
warn << cr << " state " << scr->state_string ()
<< " was stored before notified state " << cr.state_string ()
@@ -579,43 +574,12 @@ namespace brep
return nullptr;
}
- service_data::check_run cr; // Updated check run.
-
- // Create a new check run on GitHub.
- //
- auto create = [&cr, &b, &hs, &sd, &error] (const string& iat)
- {
- string rq (graphql_request (create_check_runs (sd.repository_id,
- sd.head_sha,
- {b},
- build_state::building,
- &hs)));
-
- return mutate_check_run (cr,
- {b},
- iat,
- move (rq),
- build_state::building,
- error);
- };
-
- // Update a check run that already exists on GitHub.
- //
- auto update =
- [&cr, &b, &sd, &error] (const string& iat,
- const string& nid,
- build_state st = build_state::building)
- {
- string rq (graphql_request (
- update_check_run (sd.repository_id, nid, build_state::building)));
-
- return mutate_check_run (cr, {b}, iat, move (rq), st, error);
- };
+ check_run cr; // Updated check run.
// Get a new installation access token if the current one has expired.
//
- const installation_access_token* iat (nullptr);
- optional<installation_access_token> new_iat;
+ const gh_installation_access_token* iat (nullptr);
+ optional<gh_installation_access_token> new_iat;
if (system_clock::now () > sd.installation_access.expires_at)
{
@@ -633,11 +597,11 @@ namespace brep
if (iat != nullptr)
{
- string bid (check_run_name (b)); // Full Build ID.
+ string bid (gh_check_run_name (b)); // Full Build ID.
// Stored check run.
//
- const service_data::check_run* scr (sd.find_check_run (bid));
+ const check_run* scr (sd.find_check_run (bid));
if (scr != nullptr && scr->node_id)
{
@@ -683,7 +647,13 @@ namespace brep
build_state st (scr->state ? build_state::building
: build_state::built);
- if (update (iat->token, *cr.node_id, st))
+ if (gq_update_check_run (cr,
+ iat->token,
+ sd.repository_id,
+ *cr.node_id,
+ b,
+ st,
+ error))
{
// @@ TODO If !scr->state and GH had built then we probably don't
// want to run the lambda either but currently it will run
@@ -743,11 +713,11 @@ namespace brep
// Fetch the check run by name from GitHub.
//
- pair<optional<check_run>, bool> pr (
- fetch_check_run (iat->token,
- ts.id,
- check_run_name (b, &hs),
- error));
+ pair<optional<gh_check_run>, bool> pr (
+ gq_fetch_check_run (iat->token,
+ ts.id,
+ gh_check_run_name (b, &hs),
+ error));
if (pr.second) // No errors.
{
@@ -762,12 +732,20 @@ namespace brep
// @@ TODO Create with whatever the failed state was if we decide
// to store it.
//
- if (create (iat->token))
+ if (gq_create_check_run (cr,
+ iat->token,
+ sd.repository_id, sd.head_sha,
+ b,
+ build_state::queued,
+ hs,
+ error))
+ {
l3 ([&]{trace << "created check_run { " << cr << " }";});
+ }
}
else // Check run exists on GitHub.
{
- if (pr.first->status == to_string_gh (build_state::queued))
+ if (pr.first->status == gh_to_status (build_state::queued))
{
if (scr != nullptr)
{
@@ -775,8 +753,16 @@ namespace brep
cr.state = nullopt;
}
- if (update (iat->token, pr.first->node_id))
+ if (gq_update_check_run (cr,
+ iat->token,
+ sd.repository_id,
+ pr.first->node_id,
+ b,
+ build_state::building,
+ error))
+ {
l3 ([&]{trace << "updated check_run { " << cr << " }";});
+ }
}
else
{
@@ -823,7 +809,7 @@ namespace brep
if (iat)
sd.installation_access = *iat;
- if (service_data::check_run* scr = sd.find_check_run (cr.build_id))
+ if (check_run* scr = sd.find_check_run (cr.build_id))
{
// Update existing check run.
//
@@ -929,12 +915,12 @@ namespace brep
// repos covered by the installation if installed on an organisation for
// example.
//
- optional<installation_access_token> ci_github::
+ optional<gh_installation_access_token> ci_github::
obtain_installation_access_token (uint64_t iid,
string jwt,
const basic_mark& error) const
{
- installation_access_token iat;
+ gh_installation_access_token iat;
try
{
// API endpoint.
@@ -988,425 +974,4 @@ namespace brep
return iat;
}
-
- static string
- to_iso8601 (timestamp t)
- {
- return butl::to_string (t,
- "%Y-%m-%dT%TZ",
- false /* special */,
- false /* local */);
- }
-
- static timestamp
- from_iso8601 (const string& s)
- {
- return butl::from_string (s.c_str (), "%Y-%m-%dT%TZ", false /* local */);
- }
-
- using event = json::event;
-
- service_data::
- service_data (const string& json)
- {
- json::parser p (json.data (), json.size (), "service_data");
-
- p.next_expect (event::begin_object);
-
- // Throw if the schema version is not supported.
- //
- version = p.next_expect_member_number<uint64_t> ("version");
- if (version != 1)
- {
- throw invalid_argument ("unsupported service_data schema version: " +
- to_string (version));
- }
-
- // Installation access token.
- //
- p.next_expect_member_object ("installation_access");
- installation_access.token = p.next_expect_member_string ("token");
- installation_access.expires_at =
- from_iso8601 (p.next_expect_member_string ("expires_at"));
- p.next_expect (event::end_object);
-
- installation_id =
- p.next_expect_member_number<uint64_t> ("installation_id");
- repository_id = p.next_expect_member_string ("repository_id");
- head_sha = p.next_expect_member_string ("head_sha");
-
- p.next_expect_member_array ("check_runs");
- while (p.next_expect (event::begin_object, event::end_array))
- {
- string bid (p.next_expect_member_string ("build_id"));
-
- optional<string> nid;
- {
- string* v (p.next_expect_member_string_null ("node_id"));
- if (v != nullptr)
- nid = *v;
- }
-
- optional<build_state> s;
- {
- string* v (p.next_expect_member_string_null ("state"));
- if (v != nullptr)
- s = to_build_state (*v);
- }
-
- check_runs.emplace_back (move (bid), move (nid), s);
-
- p.next_expect (event::end_object);
- }
-
- p.next_expect (event::end_object);
- }
-
- service_data::
- service_data (string iat_tok,
- timestamp iat_ea,
- uint64_t iid,
- string rid,
- string hs)
- : installation_access (move (iat_tok), iat_ea),
- installation_id (iid),
- repository_id (move (rid)),
- head_sha (move (hs))
- {
- }
-
- string service_data::
- json () const
- {
- string b;
- json::buffer_serializer s (b);
-
- s.begin_object ();
-
- s.member ("version", 1);
-
- // Installation access token.
- //
- s.member_begin_object ("installation_access");
- s.member ("token", installation_access.token);
- s.member ("expires_at", to_iso8601 (installation_access.expires_at));
- s.end_object ();
-
- s.member ("installation_id", installation_id);
- s.member ("repository_id", repository_id);
- s.member ("head_sha", head_sha);
-
- s.member_begin_array ("check_runs");
- for (const check_run& cr: check_runs)
- {
- s.begin_object ();
- s.member ("build_id", cr.build_id);
-
- s.member_name ("node_id");
- if (cr.node_id)
- s.value (*cr.node_id);
- else
- s.value (nullptr);
-
- s.member_name ("state");
- if (cr.state)
- s.value (to_string (*cr.state));
- else
- s.value (nullptr);
-
- s.end_object ();
- }
- s.end_array ();
-
- s.end_object ();
-
- return b;
- }
-
- service_data::check_run* service_data::
- find_check_run (const string& bid)
- {
- for (check_run& cr: check_runs)
- {
- if (cr.build_id == bid)
- return &cr;
- }
- return nullptr;
- }
-
- ostream&
- operator<< (ostream& os, const service_data::check_run& cr)
- {
- os << "node_id: " << cr.node_id.value_or ("null")
- << ", build_id: " << cr.build_id
- << ", state: " << (cr.state ? to_string (*cr.state) : "null");
-
- return os;
- }
-
- // The rest is GitHub request/response type parsing and printing.
- //
-
- // Throw invalid_json_input when a required member `m` is missing from a
- // JSON object `o`.
- //
- [[noreturn]] static void
- missing_member (const json::parser& p, const char* o, const char* m)
- {
- throw json::invalid_json_input (
- p.input_name,
- p.line (), p.column (), p.position (),
- o + string (" object is missing member '") + m + '\'');
- }
-
- // check_suite
- //
- gh::check_suite::
- check_suite (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool ni (false), hb (false), hs (false), bf (false), at (false);
-
- // Skip unknown/uninteresting members.
- //
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (ni, "node_id")) node_id = p.next_expect_string ();
- else if (c (hb, "head_branch")) head_branch = p.next_expect_string ();
- else if (c (hs, "head_sha")) head_sha = p.next_expect_string ();
- else if (c (bf, "before")) before = p.next_expect_string ();
- else if (c (at, "after")) after = p.next_expect_string ();
- else p.next_expect_value_skip ();
- }
-
- if (!ni) missing_member (p, "check_suite", "node_id");
- if (!hb) missing_member (p, "check_suite", "head_branch");
- if (!hs) missing_member (p, "check_suite", "head_sha");
- if (!bf) missing_member (p, "check_suite", "before");
- if (!at) missing_member (p, "check_suite", "after");
- }
-
- ostream& gh::
- operator<< (ostream& os, const check_suite& cs)
- {
- os << "node_id: " << cs.node_id
- << ", head_branch: " << cs.head_branch
- << ", head_sha: " << cs.head_sha
- << ", before: " << cs.before
- << ", after: " << cs.after;
-
- return os;
- }
-
- // check_run
- //
- gh::check_run::
- check_run (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool ni (false), nm (false), st (false);
-
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (ni, "id")) node_id = p.next_expect_string ();
- else if (c (nm, "name")) name = p.next_expect_string ();
- else if (c (st, "status")) status = p.next_expect_string ();
- }
-
- if (!ni) missing_member (p, "check_run", "id");
- if (!nm) missing_member (p, "check_run", "name");
- if (!st) missing_member (p, "check_run", "status");
- }
-
- ostream& gh::
- operator<< (ostream& os, const check_run& cr)
- {
- os << "node_id: " << cr.node_id
- << ", name: " << cr.name
- << ", status: " << cr.status;
-
- return os;
- }
-
- // repository
- //
- gh::repository::
- repository (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool ni (false), nm (false), fn (false), db (false), cu (false);
-
- // Skip unknown/uninteresting members.
- //
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (ni, "node_id")) node_id = p.next_expect_string ();
- else if (c (nm, "name")) name = p.next_expect_string ();
- else if (c (fn, "full_name")) full_name = p.next_expect_string ();
- else if (c (db, "default_branch")) default_branch = p.next_expect_string ();
- else if (c (cu, "clone_url")) clone_url = p.next_expect_string ();
- else p.next_expect_value_skip ();
- }
-
- if (!ni) missing_member (p, "repository", "node_id");
- if (!nm) missing_member (p, "repository", "name");
- if (!fn) missing_member (p, "repository", "full_name");
- if (!db) missing_member (p, "repository", "default_branch");
- if (!cu) missing_member (p, "repository", "clone_url");
- }
-
- ostream& gh::
- operator<< (ostream& os, const repository& rep)
- {
- os << "node_id: " << rep.node_id
- << ", name: " << rep.name
- << ", full_name: " << rep.full_name
- << ", default_branch: " << rep.default_branch
- << ", clone_url: " << rep.clone_url;
-
- return os;
- }
-
- // installation
- //
- gh::installation::
- installation (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool i (false);
-
- // Skip unknown/uninteresting members.
- //
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (i, "id")) id = p.next_expect_number<uint64_t> ();
- else p.next_expect_value_skip ();
- }
-
- if (!i) missing_member (p, "installation", "id");
- }
-
- ostream& gh::
- operator<< (ostream& os, const installation& i)
- {
- os << "id: " << i.id;
-
- return os;
- }
-
- // check_suite_event
- //
- gh::check_suite_event::
- check_suite_event (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool ac (false), cs (false), rp (false), in (false);
-
- // Skip unknown/uninteresting members.
- //
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (ac, "action")) action = p.next_expect_string ();
- else if (c (cs, "check_suite")) check_suite = gh::check_suite (p);
- else if (c (rp, "repository")) repository = gh::repository (p);
- else if (c (in, "installation")) installation = gh::installation (p);
- else p.next_expect_value_skip ();
- }
-
- if (!ac) missing_member (p, "check_suite_event", "action");
- if (!cs) missing_member (p, "check_suite_event", "check_suite");
- if (!rp) missing_member (p, "check_suite_event", "repository");
- if (!in) missing_member (p, "check_suite_event", "installation");
- }
-
- ostream& gh::
- operator<< (ostream& os, const check_suite_event& cs)
- {
- os << "action: " << cs.action;
- os << ", check_suite { " << cs.check_suite << " }";
- os << ", repository { " << cs.repository << " }";
- os << ", installation { " << cs.installation << " }";
-
- return os;
- }
-
- // installation_access_token
- //
- // Example JSON:
- //
- // {
- // "token": "ghs_Py7TPcsmsITeVCAWeVtD8RQs8eSos71O5Nzp",
- // "expires_at": "2024-02-15T16:16:38Z",
- // ...
- // }
- //
- gh::installation_access_token::
- installation_access_token (json::parser& p)
- {
- p.next_expect (event::begin_object);
-
- bool tk (false), ea (false);
-
- // Skip unknown/uninteresting members.
- //
- while (p.next_expect (event::name, event::end_object))
- {
- auto c = [&p] (bool& v, const char* s)
- {
- return p.name () == s ? (v = true) : false;
- };
-
- if (c (tk, "token")) token = p.next_expect_string ();
- else if (c (ea, "expires_at")) expires_at = from_iso8601 (p.next_expect_string ());
- else p.next_expect_value_skip ();
- }
-
- if (!tk) missing_member (p, "installation_access_token", "token");
- if (!ea) missing_member (p, "installation_access_token", "expires_at");
- }
-
- gh::installation_access_token::
- installation_access_token (string tk, timestamp ea)
- : token (move (tk)), expires_at (ea)
- {
- }
-
- ostream& gh::
- operator<< (ostream& os, const installation_access_token& t)
- {
- os << "token: " << t.token << ", expires_at: ";
- butl::operator<< (os, t.expires_at);
-
- return os;
- }
}