aboutsummaryrefslogtreecommitdiff
path: root/libbbot/manifest.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbbot/manifest.cxx')
-rw-r--r--libbbot/manifest.cxx499
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);