aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-build-result.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2023-04-28 22:14:14 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2023-05-17 19:02:14 +0300
commit9f5b820aec37ac0a929e074ae2c859229da33b0f (patch)
treeadd2dfb2b0de92bed914ec22fee9373e31874c97 /mod/mod-build-result.cxx
parent756d871cc55c56eed160a2cfe6ea5fe7de783bf3 (diff)
Add support for upload handlers and implement brep-upload-bindist handler
Diffstat (limited to 'mod/mod-build-result.cxx')
-rw-r--r--mod/mod-build-result.cxx534
1 files changed, 156 insertions, 378 deletions
diff --git a/mod/mod-build-result.cxx b/mod/mod-build-result.cxx
index 99bdc8d..7806cda 100644
--- a/mod/mod-build-result.cxx
+++ b/mod/mod-build-result.cxx
@@ -6,7 +6,6 @@
#include <odb/database.hxx>
#include <odb/transaction.hxx>
-#include <libbutl/openssl.hxx>
#include <libbutl/sendmail.hxx>
#include <libbutl/fdstream.hxx>
#include <libbutl/process-io.hxx>
@@ -38,10 +37,8 @@ using namespace odb::core;
//
brep::build_result::
build_result (const build_result& r)
- : database_module (r),
- build_config_module (r),
- options_ (r.initialized_ ? r.options_ : nullptr),
- use_openssl_pkeyutl_ (r.initialized_ ? r.use_openssl_pkeyutl_ : false)
+ : build_result_module (r),
+ options_ (r.initialized_ ? r.options_ : nullptr)
{
}
@@ -53,34 +50,12 @@ init (scanner& s)
options_ = make_shared<options::build_result> (
s, unknown_mode::fail, unknown_mode::fail);
- database_module::init (static_cast<const options::package_db&> (*options_),
- options_->package_db_retry ());
-
if (options_->build_config_specified ())
{
- database_module::init (static_cast<const options::build_db&> (*options_),
- options_->build_db_retry ());
-
- build_config_module::init (*options_);
- }
+ build_result_module::init (*options_, *options_);
- try
- {
- optional<openssl_info> oi (
- openssl::info ([&trace, this] (const char* args[], size_t n)
- {
- l2 ([&]{trace << process_args {args, n};});
- },
- 2,
- options_->openssl ()));
-
- use_openssl_pkeyutl_ = oi &&
- oi->name == "OpenSSL" &&
- oi->version >= semantic_version {3, 0, 0};
- }
- catch (const system_error& e)
- {
- fail << "unable to obtain openssl version: " << e;
+ database_module::init (static_cast<const options::package_db&> (*options_),
+ options_->package_db_retry ());
}
if (options_->root ().empty ())
@@ -129,149 +104,23 @@ handle (request& rq, response&)
throw invalid_request (400, e.what ());
}
- // Parse the task response session to obtain the build id and the timestamp,
- // and to make sure the session matches tenant and the result manifest's
- // package name, and version.
+ // Parse the task response session and make sure the session matches tenant
+ // and the result manifest's package name, and version.
//
- build_id id;
- timestamp session_timestamp;
+ parse_session_result session;
+ const build_id& id (session.id);
try
{
- const string& s (rqm.session);
-
- size_t p (s.find ('/')); // End of tenant.
-
- if (p == string::npos)
- throw invalid_argument ("no package name");
-
- if (tenant.compare (0, tenant.size (), s, 0, p) != 0)
- throw invalid_argument ("tenant mismatch");
-
- size_t b (p + 1); // Start of package name.
- p = s.find ('/', b); // End of package name.
-
- if (p == b)
- throw invalid_argument ("empty package name");
-
- if (p == string::npos)
- throw invalid_argument ("no package version");
-
- package_name& name (rqm.result.name);
- {
- const string& n (name.string ());
- if (n.compare (0, n.size (), s, b, p - b) != 0)
- throw invalid_argument ("package name mismatch");
- }
-
- b = p + 1; // Start of version.
- p = s.find ('/', b); // End of version.
-
- if (p == string::npos)
- throw invalid_argument ("no configuration name");
-
- auto parse_version = [&s, &b, &p] (const char* what) -> version
- {
- // Intercept exception handling to add the parsing error attribution.
- //
- try
- {
- return brep::version (string (s, b, p - b));
- }
- catch (const invalid_argument& e)
- {
- throw invalid_argument (string ("invalid ") + what + ": " + e.what ());
- }
- };
+ // Note: also verifies that the tenant matches the session.
+ //
+ session = parse_session (rqm.session);
- version package_version (parse_version ("package version"));
+ if (rqm.result.name != id.package.name)
+ throw invalid_argument ("package name mismatch");
- if (package_version != rqm.result.version)
+ if (rqm.result.version != session.package_version)
throw invalid_argument ("package version mismatch");
-
- b = p + 1; // Start of target.
- p = s.find ('/', b); // End of target.
-
- if (p == string::npos)
- throw invalid_argument ("no target configuration name");
-
- target_triplet target;
- try
- {
- target = target_triplet (string (s, b, p - b));
- }
- catch (const invalid_argument& e)
- {
- throw invalid_argument (string ("invalid target: ") + e.what ());
- }
-
- b = p + 1; // Start of target configuration name.
- p = s.find ('/', b); // End of target configuration name.
-
- if (p == string::npos)
- throw invalid_argument ("no package configuration name");
-
- string target_config (s, b, p - b);
-
- if (target_config.empty ())
- throw invalid_argument ("empty target configuration name");
-
- b = p + 1; // Start of package configuration name.
- p = s.find ('/', b); // End of package configuration name.
-
- if (p == string::npos)
- throw invalid_argument ("no toolchain name");
-
- string package_config (s, b, p - b);
-
- if (package_config.empty ())
- throw invalid_argument ("empty package configuration name");
-
- b = p + 1; // Start of toolchain name.
- p = s.find ('/', b); // End of toolchain name.
-
- if (p == string::npos)
- throw invalid_argument ("no toolchain version");
-
- string toolchain_name (s, b, p - b);
-
- if (toolchain_name.empty ())
- throw invalid_argument ("empty toolchain name");
-
- b = p + 1; // Start of toolchain version.
- p = s.find ('/', b); // End of toolchain version.
-
- if (p == string::npos)
- throw invalid_argument ("no timestamp");
-
- version toolchain_version (parse_version ("toolchain version"));
-
- id = build_id (package_id (move (tenant), move (name), package_version),
- move (target),
- move (target_config),
- move (package_config),
- move (toolchain_name),
- toolchain_version);
-
- try
- {
- size_t tsn;
- string ts (s, p + 1);
-
- session_timestamp = timestamp (
- chrono::duration_cast<timestamp::duration> (
- chrono::nanoseconds (stoull (ts, &tsn))));
-
- if (tsn != ts.size ())
- throw invalid_argument ("trailing junk");
- }
- // Handle invalid_argument or out_of_range (both derive from logic_error),
- // that can be thrown by stoull().
- //
- catch (const logic_error& e)
- {
- throw invalid_argument (string ("invalid timestamp: ") + e.what ());
- }
}
catch (const invalid_argument& e)
{
@@ -341,6 +190,12 @@ handle (request& rq, response&)
bool build_notify (false);
bool unforced (true);
+ // Note that if the session authentication fails (probably due to the
+ // authentication settings change), then we log this case with the warning
+ // severity and respond with the 200 HTTP code as if the challenge is
+ // valid. The thinking is that we shouldn't alarm a law-abaiding agent and
+ // shouldn't provide any information to a malicious one.
+ //
{
transaction t (build_db_->begin ());
@@ -348,251 +203,174 @@ handle (request& rq, response&)
shared_ptr<build> b;
if (!build_db_->query_one<package_build> (
query<package_build>::build::id == id, pb))
+ {
warn_expired ("no package build");
+ }
else if ((b = move (pb.build))->state != build_state::building)
+ {
warn_expired ("package configuration state is " + to_string (b->state));
- else if (b->timestamp != session_timestamp)
+ }
+ else if (b->timestamp != session.timestamp)
+ {
warn_expired ("non-matching timestamp");
- else
+ }
+ else if (authenticate_session (*options_, rqm.challenge, *b, rqm.session))
{
- // Check the challenge.
+ // If the build is interrupted, then revert it to the original built
+ // state if this is a rebuild and delete it from the database otherwise.
//
- // If the challenge doesn't match expectations (probably due to the
- // authentication settings change), then we log this case with the
- // warning severity and respond with the 200 HTTP code as if the
- // challenge is valid. The thinking is that we shouldn't alarm a
- // law-abaiding agent and shouldn't provide any information to a
- // malicious one.
- //
- auto warn_auth = [&rqm, &warn] (const string& d)
+ if (rqm.result.status == result_status::interrupt)
{
- warn << "session '" << rqm.session << "' authentication failed: " << d;
- };
+ if (b->status) // Is this a rebuild?
+ {
+ b->state = build_state::built;
- bool auth (false);
+ // Keep the force rebuild indication. Note that the forcing state is
+ // only valid for the building state.
+ //
+ if (b->force == force_state::forcing)
+ b->force = force_state::forced;
- // Must both be present or absent.
- //
- if (!b->agent_challenge != !rqm.challenge)
- warn_auth (rqm.challenge
- ? "unexpected challenge"
- : "challenge is expected");
- else if (bot_agent_key_map_ == nullptr) // Authentication is disabled.
- auth = true;
- else if (!b->agent_challenge) // Authentication is recently enabled.
- warn_auth ("challenge is required now");
- else
- {
- assert (b->agent_fingerprint && rqm.challenge);
- auto i (bot_agent_key_map_->find (*b->agent_fingerprint));
+ // Cleanup the interactive build login information.
+ //
+ b->interactive = nullopt;
- // The agent's key is recently replaced.
- //
- if (i == bot_agent_key_map_->end ())
- warn_auth ("agent's public key not found");
- else
- {
- try
- {
- openssl os (print_args,
- path ("-"), fdstream_mode::text, 2,
- process_env (options_->openssl (),
- options_->openssl_envvar ()),
- use_openssl_pkeyutl_ ? "pkeyutl" : "rsautl",
- options_->openssl_option (),
- use_openssl_pkeyutl_ ? "-verifyrecover" : "-verify",
- "-pubin",
- "-inkey",
- i->second);
-
- for (const auto& c: *rqm.challenge)
- os.out.put (c); // Sets badbit on failure.
-
- os.out.close ();
-
- string s;
- getline (os.in, s);
-
- bool v (os.in.eof ());
- os.in.close ();
-
- if (os.wait () && v)
- {
- auth = s == *b->agent_challenge;
-
- if (!auth)
- warn_auth ("challenge mismatched");
- }
- else // The signature is presumably meaningless.
- warn_auth ("unable to verify challenge");
- }
- catch (const system_error& e)
- {
- fail << "unable to verify challenge: " << e;
- }
+ // Cleanup the authentication data.
+ //
+ b->agent_fingerprint = nullopt;
+ b->agent_challenge = nullopt;
+
+ // Note that we are unable to restore the pre-rebuild timestamp
+ // since it has been overwritten when the build task was issued.
+ // That, however, feels ok and we just keep it unchanged.
+
+ build_db_->update (b);
}
+ else
+ build_db_->erase (b);
}
-
- if (auth)
+ else
{
- // If the build is interrupted, then revert it to the original built
- // state if this is a rebuild and delete it from the database
- // otherwise.
+ // Verify the result status/checksums.
//
- if (rqm.result.status == result_status::interrupt)
+ // Specifically, if the result status is skip, then it can only be in
+ // response to the soft rebuild task (all checksums are present in the
+ // build object) and the result checksums must match the build object
+ // checksums. On verification failure respond with the bad request
+ // HTTP code (400).
+ //
+ if (rqm.result.status == result_status::skip)
{
- if (b->status) // Is this a rebuild?
- {
- b->state = build_state::built;
+ if (!b->agent_checksum ||
+ !b->worker_checksum ||
+ !b->dependency_checksum)
+ throw invalid_request (400, "unexpected skip result status");
+
+ // Can only be absent for initial build, in which case the
+ // checksums are also absent and we would end up with the above
+ // 400 response.
+ //
+ assert (b->status);
- // Keep the force rebuild indication. Note that the forcing state
- // is only valid for the building state.
- //
- if (b->force == force_state::forcing)
- b->force = force_state::forced;
+ // Verify that the result checksum matches the build checksum and
+ // throw invalid_request(400) if that's not the case.
+ //
+ auto verify = [] (const string& build_checksum,
+ const optional<string>& result_checksum,
+ const char* what)
+ {
+ if (!result_checksum)
+ throw invalid_request (
+ 400,
+ string (what) + " checksum is expected for skip result status");
+
+ if (*result_checksum != build_checksum)
+ throw invalid_request (
+ 400,
+ string (what) + " checksum '" + build_checksum +
+ "' is expected instead of '" + *result_checksum +
+ "' for skip result status");
+ };
+
+ verify (*b->agent_checksum, rqm.agent_checksum, "agent");
+
+ verify (*b->worker_checksum,
+ rqm.result.worker_checksum,
+ "worker");
+
+ verify (*b->dependency_checksum,
+ rqm.result.dependency_checksum,
+ "dependency");
+ }
- // Cleanup the interactive build login information.
- //
- b->interactive = nullopt;
+ unforced = b->force == force_state::unforced;
- // Cleanup the authentication data.
- //
- b->agent_fingerprint = nullopt;
- b->agent_challenge = nullopt;
+ // Don't send email to the build-email address for the
+ // success-to-success status change, unless the build was forced.
+ //
+ build_notify = !(rqm.result.status == result_status::success &&
+ b->status &&
+ *b->status == rqm.result.status &&
+ unforced);
- // Note that we are unable to restore the pre-rebuild timestamp
- // since it has been overwritten when the build task was issued.
- // That, however, feels ok and we just keep it unchanged.
+ b->state = build_state::built;
+ b->force = force_state::unforced;
- build_db_->update (b);
- }
- else
- build_db_->erase (b);
- }
- else
- {
- // Verify the result status/checksums.
- //
- // Specifically, if the result status is skip, then it can only be
- // in response to the soft rebuild task (all checksums are present
- // in the build object) and the result checksums must match the
- // build object checksums. On verification failure respond with the
- // bad request HTTP code (400).
- //
- if (rqm.result.status == result_status::skip)
- {
- if (!b->agent_checksum ||
- !b->worker_checksum ||
- !b->dependency_checksum)
- throw invalid_request (400, "unexpected skip result status");
-
- // Can only be absent for initial build, in which case the
- // checksums are also absent and we would end up with the above
- // 400 response.
- //
- assert (b->status);
-
- // Verify that the result checksum matches the build checksum and
- // throw invalid_request(400) if that's not the case.
- //
- auto verify = [] (const string& build_checksum,
- const optional<string>& result_checksum,
- const char* what)
- {
- if (!result_checksum)
- throw invalid_request (
- 400,
- string (what) +
- " checksum is expected for skip result status");
-
- if (*result_checksum != build_checksum)
- throw invalid_request (
- 400,
- string (what) + " checksum '" + build_checksum +
- "' is expected instead of '" + *result_checksum +
- "' for skip result status");
- };
-
- verify (*b->agent_checksum, rqm.agent_checksum, "agent");
-
- verify (*b->worker_checksum,
- rqm.result.worker_checksum,
- "worker");
-
- verify (*b->dependency_checksum,
- rqm.result.dependency_checksum,
- "dependency");
- }
+ // Cleanup the interactive build login information.
+ //
+ b->interactive = nullopt;
- unforced = b->force == force_state::unforced;
+ // Cleanup the authentication data.
+ //
+ b->agent_fingerprint = nullopt;
+ b->agent_challenge = nullopt;
- // Don't send email to the build-email address for the
- // success-to-success status change, unless the build was forced.
- //
- build_notify = !(rqm.result.status == result_status::success &&
- b->status &&
- *b->status == rqm.result.status &&
- unforced);
+ b->timestamp = system_clock::now ();
+ b->soft_timestamp = b->timestamp;
- b->state = build_state::built;
- b->force = force_state::unforced;
+ // If the result status is other than skip, then save the status,
+ // results, and checksums and update the hard timestamp.
+ //
+ if (rqm.result.status != result_status::skip)
+ {
+ b->status = rqm.result.status;
+ b->hard_timestamp = b->soft_timestamp;
- // Cleanup the interactive build login information.
+ // Mark the section as loaded, so results are updated.
//
- b->interactive = nullopt;
+ b->results_section.load ();
+ b->results = move (rqm.result.results);
- // Cleanup the authentication data.
+ // Save the checksums.
//
- b->agent_fingerprint = nullopt;
- b->agent_challenge = nullopt;
+ b->agent_checksum = move (rqm.agent_checksum);
+ b->worker_checksum = move (rqm.result.worker_checksum);
+ b->dependency_checksum = move (rqm.result.dependency_checksum);
+ }
- b->timestamp = system_clock::now ();
- b->soft_timestamp = b->timestamp;
+ build_db_->update (b);
- // If the result status is other than skip, then save the status,
- // results, and checksums and update the hard timestamp.
- //
- if (rqm.result.status != result_status::skip)
- {
- b->status = rqm.result.status;
- b->hard_timestamp = b->soft_timestamp;
-
- // Mark the section as loaded, so results are updated.
- //
- b->results_section.load ();
- b->results = move (rqm.result.results);
-
- // Save the checksums.
- //
- b->agent_checksum = move (rqm.agent_checksum);
- b->worker_checksum = move (rqm.result.worker_checksum);
- b->dependency_checksum = move (rqm.result.dependency_checksum);
- }
-
- build_db_->update (b);
+ // Don't send the build notification email if the task result is
+ // `skip`, the configuration is hidden, or is now excluded by the
+ // package.
+ //
+ if (rqm.result.status != result_status::skip && belongs (*tc, "all"))
+ {
+ shared_ptr<build_package> p (
+ build_db_->load<build_package> (b->id.package));
- // Don't send the build notification email if the task result is
- // `skip`, the configuration is hidden, or is now excluded by the
- // package.
+ // The package configuration should be present (see mod-builds.cxx
+ // for details) but if it is not, let's log the warning.
//
- if (rqm.result.status != result_status::skip && belongs (*tc, "all"))
+ if (const build_package_config* pc = find (b->package_config_name,
+ p->configs))
{
- shared_ptr<build_package> p (
- build_db_->load<build_package> (b->id.package));
-
- // The package configuration should be present (see mod-builds.cxx
- // for details) but if it is not, let's log the warning.
- //
- if (const build_package_config* pc = find (b->package_config_name,
- p->configs))
- {
- if (!exclude (*pc, p->builds, p->constraints, *tc))
- bld = move (b);
- }
- else
- warn << "cannot find configuration '" << b->package_config_name
- << "' for package " << p->id.name << '/' << p->version;
+ if (!exclude (*pc, p->builds, p->constraints, *tc))
+ bld = move (b);
}
+ else
+ warn << "cannot find configuration '" << b->package_config_name
+ << "' for package " << p->id.name << '/' << p->version;
}
}
}