diff options
Diffstat (limited to 'libbbot/manifest.cxx')
-rw-r--r-- | libbbot/manifest.cxx | 499 |
1 files changed, 445 insertions, 54 deletions
diff --git a/libbbot/manifest.cxx b/libbbot/manifest.cxx index c01a84b..45a4668 100644 --- a/libbbot/manifest.cxx +++ b/libbbot/manifest.cxx @@ -7,20 +7,23 @@ #include <vector> #include <string> #include <cctype> // isxdigit() +#include <limits> // numeric_limits #include <cassert> #include <sstream> #include <cstddef> // size_t #include <utility> // move() #include <cstdint> // uint64_t +#include <cstdlib> // strtoull() +#include <algorithm> // find_if() #include <stdexcept> // invalid_argument -#include <libbutl/regex.mxx> -#include <libbutl/base64.mxx> -#include <libbutl/utility.mxx> // digit() -#include <libbutl/tab-parser.mxx> -#include <libbutl/string-parser.mxx> -#include <libbutl/manifest-parser.mxx> -#include <libbutl/manifest-serializer.mxx> +#include <libbutl/regex.hxx> +#include <libbutl/base64.hxx> +#include <libbutl/utility.hxx> // digit() +#include <libbutl/tab-parser.hxx> +#include <libbutl/string-parser.hxx> +#include <libbutl/manifest-parser.hxx> +#include <libbutl/manifest-serializer.hxx> using namespace std; using namespace butl; @@ -36,6 +39,29 @@ namespace bbot using butl::optional; + // machine_role + // + string + to_string (machine_role r) + { + switch (r) + { + case machine_role::build: return "build"; + case machine_role::auxiliary: return "auxiliary"; + } + + assert (false); + return string (); + } + + machine_role + to_machine_role (const string& s) + { + if (s == "build") return machine_role::build; + else if (s == "auxiliary") return machine_role::auxiliary; + else throw invalid_argument ("invalid machine role '" + s + '\''); + } + // result_status // string @@ -43,11 +69,13 @@ namespace bbot { switch (s) { - case result_status::success: return "success"; - case result_status::warning: return "warning"; - case result_status::error: return "error"; - case result_status::abort: return "abort"; - case result_status::abnormal: return "abnormal"; + case result_status::skip: return "skip"; + case result_status::success: return "success"; + case result_status::warning: return "warning"; + case result_status::error: return "error"; + case result_status::abort: return "abort"; + case result_status::abnormal: return "abnormal"; + case result_status::interrupt: return "interrupt"; } assert (false); @@ -57,12 +85,39 @@ namespace bbot result_status to_result_status (const string& s) { - if (s == "success") return result_status::success; - else if (s == "warning") return result_status::warning; - else if (s == "error") return result_status::error; - else if (s == "abort") return result_status::abort; - else if (s == "abnormal") return result_status::abnormal; - else throw invalid_argument ("invalid result status '" + s + "'"); + if (s == "skip") return result_status::skip; + else if (s == "success") return result_status::success; + else if (s == "warning") return result_status::warning; + else if (s == "error") return result_status::error; + else if (s == "abort") return result_status::abort; + else if (s == "abnormal") return result_status::abnormal; + else if (s == "interrupt") return result_status::interrupt; + else throw invalid_argument ("invalid result status '" + s + '\''); + } + + // interactive_mode + // + string + to_string (interactive_mode m) + { + switch (m) + { + case interactive_mode::false_: return "false"; + case interactive_mode::true_: return "true"; + case interactive_mode::both: return "both"; + } + + assert (false); + return string (); + } + + interactive_mode + to_interactive_mode (const string& s) + { + if (s == "false") return interactive_mode::false_; + else if (s == "true") return interactive_mode::true_; + else if (s == "both") return interactive_mode::both; + else throw invalid_argument ("invalid interactive mode '" + s + '\''); } // Utility functions @@ -104,6 +159,29 @@ namespace bbot return true; } + // Return nullopt if the string is not a valid 64-bit unsigned integer. + // + static optional<uint64_t> + parse_uint64 (const string& s) + { + if (!s.empty () && s[0] != '-' && s[0] != '+') // strtoull() allows these. + { + const char* b (s.c_str ()); + char* e (nullptr); + errno = 0; // We must clear it according to POSIX. + uint64_t v (strtoull (b, &e, 10)); // Can't throw. + + if (errno != ERANGE && + e == b + s.size () && + v <= numeric_limits<uint64_t>::max ()) + { + return static_cast<uint64_t> (v); + } + } + + return nullopt; + } + // machine_header_manifest // machine_header_manifest:: @@ -177,6 +255,42 @@ namespace bbot summary = move (v); } + else if (n == "role") + { + if (role) + bad_name ("machine role redefinition"); + + try + { + role = to_machine_role (v); + } + catch (const invalid_argument& e) + { + bad_value (e.what ()); + } + } + else if (n == "ram-minimum") + { + if (ram_minimum) + bad_name ("machine minimum RAM redefinition"); + + ram_minimum = parse_uint64 (v); + + if (!ram_minimum || *ram_minimum == 0) + bad_value ( + "machine minimum RAM should be non-zero 64-bit unsigned integer"); + } + else if (n == "ram-maximum") + { + if (ram_maximum) + bad_name ("machine maximum RAM redefinition"); + + ram_maximum = parse_uint64 (v); + + if (!ram_maximum || *ram_maximum == 0) + bad_value ( + "machine maximum RAM should be non-zero 64-bit unsigned integer"); + } else { switch (m) @@ -217,6 +331,21 @@ namespace bbot s.next ("name", name); s.next ("summary", summary); + if (role) + s.next ("role", to_string (*role)); + + if (ram_minimum) + { + assert (*ram_minimum != 0); + s.next ("ram-minimum", std::to_string (*ram_minimum)); + } + + if (ram_maximum) + { + assert (*ram_maximum != 0); + s.next ("ram-maximum", std::to_string (*ram_maximum)); + } + if (end_of_manifest) s.next ("", ""); // End of manifest. } @@ -246,6 +375,11 @@ namespace bbot if (nv.value != "1") bad_value ("unsupported format version"); + // Cache the interactive login manifest value and validate whether it's + // allowed later, after the interactive mode is parsed. + // + optional<name_value> interactive_login_nv; + // Parse the task request manifest. // for (nv = p.next (); !nv.empty (); nv = p.next ()) @@ -288,6 +422,31 @@ namespace bbot e.what ()); } } + else if (n == "interactive-mode") + { + if (interactive_mode) + bad_name ("task request interactive mode redefinition"); + + try + { + interactive_mode = to_interactive_mode (v); + } + catch (const invalid_argument&) + { + bad_value ( + string ("invalid task request interactive mode '" + v + '\'')); + } + } + else if (n == "interactive-login") + { + if (interactive_login_nv) + bad_name ("task request interactive login redefinition"); + + if (v.empty ()) + bad_value ("empty task request interactive login"); + + interactive_login_nv = move (nv); + } else if (n == "fingerprint") { if (fingerprint) @@ -298,6 +457,17 @@ namespace bbot fingerprint = move (v); } + else if (n == "auxiliary-ram") + { + if (auxiliary_ram) + bad_name ("auxiliary machines RAM limit redefinition"); + + auxiliary_ram = parse_uint64 (v); + + if (!auxiliary_ram) + bad_value ( + "auxiliary machines RAM limit should be 64-bit unsigned integer"); + } else if (!iu) bad_name ("unknown name '" + n + "' in task request manifest"); } @@ -313,6 +483,22 @@ namespace bbot if (toolchain_version.empty ()) bad_value ("no task request toolchain version specified"); + if (effective_interactive_mode () != interactive_mode_type::false_) + { + if (!interactive_login_nv) + bad_value ("no task request interactive login specified"); + + interactive_login = move (interactive_login_nv->value); + } + else if (interactive_login_nv) + { + // Restore as bad_name() uses its line/column. + // + nv = move (*interactive_login_nv); + + bad_name ("interactive login specified for non-interactive mode"); + } + // Parse machine header manifests. // for (nv = p.next (); !nv.empty (); nv = p.next ()) @@ -338,9 +524,18 @@ namespace bbot s.next ("toolchain-name", toolchain_name); s.next ("toolchain-version", toolchain_version.string ()); + if (interactive_mode) + s.next ("interactive-mode", to_string (*interactive_mode)); + + if (interactive_login) + s.next ("interactive-login", *interactive_login); + if (fingerprint) s.next ("fingerprint", *fingerprint); + if (auxiliary_ram) + s.next ("auxiliary-ram", std::to_string (*auxiliary_ram)); + s.next ("", ""); // End of manifest. for (const machine_header_manifest& m: machines) @@ -451,6 +646,11 @@ namespace bbot optional<name_value> repo_url; optional<repository_type> repo_type; + // We will also cache the requires values to parse them later, after the + // package name is parsed. + // + vector<name_value> reqs; + for (nv = p.next (); !nv.empty (); nv = p.next ()) { string& n (nv.name); @@ -511,7 +711,7 @@ namespace bbot } catch (const invalid_argument&) { - bad_value ("invalid task repository type '" + v + "'"); + bad_value ("invalid task repository type '" + v + '\''); } } else if (n == "trust") @@ -521,37 +721,31 @@ namespace bbot trust.emplace_back (move (v)); } - else if (n == "test-exclude") + else if (n == "requires") + { + reqs.push_back (move (nv)); + } + else if (n == "tests" || n == "examples" || n == "benchmarks") { - size_t p (v.find ('/')); - if (p == string::npos) - bad_value ("invalid test exclusion package: '/' is expected"); - - package_name pn; - try { - pn = package_name (string (v, 0, p)); + tests.push_back (test_dependency (move (v), + to_test_dependency_type (n))); } catch (const invalid_argument& e) { - bad_value (string ("invalid test exclusion package name: ") + - e.what ()); + bad_value (e.what ()); } + } + else if (n == "dependency-checksum") + { + if (dependency_checksum) + bad_name ("task dependency checksum redefinition"); - bpkg::version pv; - - try - { - pv = bpkg::version (string (v, p + 1)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid test exclusion package version: ") + - e.what ()); - } + if (v.empty ()) + bad_value ("empty task dependency checksum"); - test_exclusions.push_back (package {move (pn), move (pv)}); + dependency_checksum = move (v); } else if (n == "machine") { @@ -563,6 +757,26 @@ namespace bbot machine = move (v); } + else if (n == "auxiliary-machine" || + (n.size () > 18 && n.compare (0, 18, "auxiliary-machine-") == 0)) + { + if (v.empty ()) + bad_value ("empty task auxiliary machine name"); + + auxiliary_machine m {move (v), + n.size () > 18 ? string (n, 18) : string ()}; + + if (find_if (auxiliary_machines.begin (), auxiliary_machines.end (), + [&m] (const auxiliary_machine& am) + { + return am.environment_name == m.environment_name; + }) != auxiliary_machines.end ()) + { + bad_name ("task auxiliary machine environment redefinition"); + } + + auxiliary_machines.push_back (move (m)); + } else if (n == "target") { if (!target.empty ()) @@ -587,15 +801,45 @@ namespace bbot environment = move (v); } - else if (n == "config") + else if (n == "auxiliary-environment") { - if (!config.empty ()) - bad_name ("task configuration redefinition"); + if (auxiliary_environment) + bad_name ("task auxiliary environment redefinition"); - config = parse_tab (v, [](const string&){}, "configuration"); + if (v.empty ()) + bad_value ("empty task auxiliary environment"); - if (config.empty ()) - bad_value ("empty task configuration"); + auxiliary_environment = move (v); + } + else if (n == "config" || // @@ TMP Until toolchain 0.16.0 is released. + n == "target-config") + { + if (!target_config.empty ()) + bad_name ("task target configuration redefinition"); + + target_config = parse_tab (v, [](const string&){}, "configuration"); + + if (target_config.empty ()) + bad_value ("empty task target configuration"); + } + else if (n == "package-config") + { + if (!package_config.empty ()) + bad_name ("task package configuration redefinition"); + + package_config = move (v); + } + else if (n == "host") + { + if (host) + bad_name ("task host value redefinition"); + + if (v == "true") + host = true; + else if (v == "false") + host = false; + else + bad_value ("invalid task host value '" + v + '\''); } else if (n == "warning-regex") { @@ -607,6 +851,26 @@ namespace bbot if (warning_regex.empty ()) bad_value ("empty task warning regex"); } + else if (n == "interactive") + { + if (interactive) + bad_name ("task interactive value redefinition"); + + if (v.empty ()) + bad_value ("empty task interactive value"); + + interactive = move (v); + } + else if (n == "worker-checksum") + { + if (worker_checksum) + bad_name ("task worker checksum redefinition"); + + if (v.empty ()) + bad_value ("empty task worker checksum"); + + worker_checksum = move (v); + } else if (!iu) bad_name ("unknown name '" + n + "' in task manifest"); } @@ -642,6 +906,18 @@ namespace bbot nv = move (*repo_url); // Restore as bad_value() uses its line/column. bad_value (string ("invalid task repository URL: ") + e.what ()); } + + // Parse the requirements. + // + for (const name_value& r: reqs) + { + requirements.push_back ( + requirement_alternatives (r.value, + name, + p.name (), + r.value_line, + r.value_column)); + } } void task_manifest:: @@ -672,15 +948,31 @@ namespace bbot for (const string& v: trust) s.next ("trust", v); - for (const package& te: test_exclusions) - s.next ("test-exclude", te.name.string () + '/' + te.version.string ()); + for (const requirement_alternatives& r: requirements) + s.next ("requires", r.string ()); + + for (const test_dependency& t: tests) + s.next (to_string (t.type), t.string ()); + + if (dependency_checksum) + s.next ("dependency-checksum", *dependency_checksum); s.next ("machine", machine); + + for (const auxiliary_machine& am: auxiliary_machines) + s.next ((!am.environment_name.empty () + ? "auxiliary-machine-" + am.environment_name + : "auxiliary-machine"), + am.name); + s.next ("target", target.string ()); if (environment) s.next ("environment", *environment); + if (auxiliary_environment) + s.next ("auxiliary-environment", *auxiliary_environment); + // Serialize an optional value of the strings type as a space-separated // string list. // @@ -701,16 +993,35 @@ namespace bbot } }; - serialize_list ("config", config); + // @@ TMP Always use 'target-config' name and always serialize + // package_config after toolchain 0.16.0 is released. + // + if (!package_config.empty ()) + { + serialize_list ("target-config", target_config); + s.next ("package-config", package_config); + } + else + serialize_list ("config", target_config); + + if (host) + s.next ("host", *host ? "true" : "false"); + serialize_list ("warning-regex", warning_regex); + if (interactive) + s.next ("interactive", *interactive); + + if (worker_checksum) + s.next ("worker-checksum", *worker_checksum); + s.next ("", ""); // End of manifest. } strings task_manifest:: - unquoted_config () const + unquoted_target_config () const { - return string_parser::unquote (config); + return string_parser::unquote (target_config); } strings task_manifest:: @@ -798,6 +1109,33 @@ namespace bbot result_url = move (v); } + else if (n.size () > 11 && + n.compare (n.size () - 11, 11, "-upload-url") == 0) + { + n.resize (n.size () - 11); + + if (find_if (upload_urls.begin (), upload_urls.end (), + [&n] (const upload_url& u) {return u.type == n;}) != + upload_urls.end ()) + { + bad_name ("task response upload url redefinition"); + } + + if (v.empty ()) + bad_value ("empty task response upload url"); + + upload_urls.emplace_back (move (v), move (n)); + } + else if (n == "agent-checksum") + { + if (agent_checksum) + bad_name ("task response agent checksum redefinition"); + + if (v.empty ()) + bad_value ("empty task response agent checksum"); + + agent_checksum = move (v); + } else if (!iu) bad_name ("unknown name '" + n + "' in task response manifest"); } @@ -825,10 +1163,16 @@ namespace bbot if (result_url) bad_value ("unexpected task response result url"); + + if (!upload_urls.empty ()) + bad_value ("unexpected task response upload url"); + + if (agent_checksum) + bad_value ("unexpected task response agent checksum"); } - // If session is not empty then the task manifest must follow, otherwise it - // shouldn't. + // If session is not empty then the task manifest must follow, otherwise + // it shouldn't. // nv = p.next (); @@ -864,6 +1208,12 @@ namespace bbot if (result_url) s.next ("result-url", *result_url); + for (const upload_url& u: upload_urls) + s.next (u.type + "-upload-url", u.url); + + if (agent_checksum) + s.next ("agent-checksum", *agent_checksum); + s.next ("", ""); // End of manifest. if (task) @@ -1030,6 +1380,26 @@ namespace bbot // results[nlog++].log = move (v); } + else if (n == "worker-checksum") + { + if (worker_checksum) + bad_name ("result worker checksum redefinition"); + + if (v.empty ()) + bad_value ("empty result worker checksum"); + + worker_checksum = move (v); + } + else if (n == "dependency-checksum") + { + if (dependency_checksum) + bad_name ("result dependency checksum redefinition"); + + if (v.empty ()) + bad_value ("empty result dependency checksum"); + + dependency_checksum = move (v); + } else if (!iu) bad_name ("unknown name '" + n + "' in result manifest"); } @@ -1086,6 +1456,12 @@ namespace bbot for (const auto& r: results) s.next (r.operation + "-log", r.log); + if (worker_checksum) + s.next ("worker-checksum", *worker_checksum); + + if (dependency_checksum) + s.next ("dependency-checksum", *dependency_checksum); + s.next ("", ""); // End of manifest. } @@ -1148,6 +1524,16 @@ namespace bbot bad_value ("invalid result request challenge"); } } + else if (n == "agent-checksum") + { + if (!agent_checksum.empty ()) + bad_name ("result request agent checksum redefinition"); + + if (v.empty ()) + bad_value ("empty result request agent checksum"); + + agent_checksum = move (v); + } else if (!iu) bad_name ("unknown name '" + n + "' in result request manifest"); } @@ -1157,6 +1543,9 @@ namespace bbot if (session.empty ()) bad_value ("no result request session specified"); + if (agent_checksum.empty ()) + bad_value ("no result request agent checksum specified"); + nv = p.next (); if (nv.empty ()) bad_value ("result manifest expected"); @@ -1183,6 +1572,8 @@ namespace bbot if (challenge) s.next ("challenge", base64_encode (*challenge)); + s.next ("agent-checksum", agent_checksum); + s.next ("", ""); // End of manifest. result.serialize (s); |