diff options
Diffstat (limited to 'bbot/manifest.cxx')
-rw-r--r-- | bbot/manifest.cxx | 988 |
1 files changed, 0 insertions, 988 deletions
diff --git a/bbot/manifest.cxx b/bbot/manifest.cxx deleted file mode 100644 index eab563b..0000000 --- a/bbot/manifest.cxx +++ /dev/null @@ -1,988 +0,0 @@ -// file : bbot/manifest.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <bbot/manifest> - -#include <vector> -#include <string> -#include <cctype> // isxdigit() -#include <cassert> -#include <sstream> -#include <cstddef> // size_t -#include <utility> // move() -#include <cstdint> // uint64_t -#include <stdexcept> // invalid_argument - -#include <butl/utility> // digit() -#include <butl/tab-parser> -#include <butl/string-parser> -#include <butl/manifest-parser> -#include <butl/manifest-serializer> - -using namespace std; -using namespace butl; -using namespace bpkg; - -namespace bbot -{ - using parser = manifest_parser; - using parsing = manifest_parsing; - using serializer = manifest_serializer; - using serialization = manifest_serialization; - using name_value = manifest_name_value; - - using strings = vector<string>; - - // result_status - // - string - to_string (result_status s) - { - 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"; - } - - assert (false); - return string (); - } - - 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 + "'"); - } - - // Utility functions - // - inline static bool - valid_sha256 (const string& s) noexcept - { - if (s.size () != 64) - return false; - - for (const auto& c: s) - { - if ((c < 'a' || c > 'f' ) && !digit (c)) - return false; - } - - return true; - } - - inline static bool - valid_fingerprint (const string& f) noexcept - { - size_t n (f.size ()); - if (n != 32 * 3 - 1) - return false; - - for (size_t i (0); i < n; ++i) - { - char c (f[i]); - if ((i + 1) % 3 == 0) - { - if (c != ':') - return false; - } - else if (!isxdigit (c)) - return false; - } - - return true; - } - - // machine_header_manifest - // - machine_header_manifest:: - machine_header_manifest (parser& p, bool iu) - : machine_header_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single machine header manifest expected"); - } - - machine_header_manifest:: - machine_header_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of machine header manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "id") - { - if (!id.empty ()) - bad_name ("machine id redefinition"); - - if (v.empty ()) - bad_value ("empty machine id"); - - id = move (v); - } - else if (n == "name") - { - if (!name.empty ()) - bad_name ("machine name redefinition"); - - if (v.empty ()) - bad_value ("empty machine name"); - - name = move (v); - } - else if (n == "summary") - { - if (!summary.empty ()) - bad_name ("machine summary redefinition"); - - if (v.empty ()) - bad_value ("empty machine summary"); - - summary = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in machine header manifest"); - } - - // Verify all non-optional values were specified. - // - if (id.empty ()) - bad_value ("no machine id specified"); - - if (name.empty ()) - bad_value ("no machine name specified"); - - if (summary.empty ()) - bad_value ("no machine summary specified"); - } - - void machine_header_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("id", id); - s.next ("name", name); - s.next ("summary", summary); - s.next ("", ""); // End of manifest. - } - - // task_request_manifest - // - task_request_manifest:: - task_request_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task request manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task request manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "agent") - { - if (!agent.empty ()) - bad_name ("task request agent redefinition"); - - if (v.empty ()) - bad_value ("empty task request agent"); - - agent = move (v); - } - else if (n == "fingerprint") - { - if (fingerprint) - bad_name ("task request fingerprint redefinition"); - - if (!valid_sha256 (v)) - bad_value ("invalid task request fingerprint"); - - fingerprint = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task request manifest"); - } - - // Verify all non-optional values were specified. - // - if (agent.empty ()) - bad_value ("no task request agent specified"); - - // Parse machine header manifests. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - machines.emplace_back (machine_header_manifest (p, nv, iu)); - - if (machines.empty ()) - bad_value ("no task request machines specified"); - } - - void task_request_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("agent", agent); - - if (fingerprint) - s.next ("fingerprint", *fingerprint); - - s.next ("", ""); // End of manifest. - - for (const machine_header_manifest& m: machines) - m.serialize (s); - - s.next ("", ""); // End of stream. - } - - // task_manifest - // - task_manifest:: - task_manifest (parser& p, bool iu) - : task_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single task manifest expected"); - } - - task_manifest:: - task_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - // Offsets are used to tie an error to the specific position inside a - // manifest value (possibly a multiline one). - // - auto bad_value = [&p, &nv] ( - const string& d, uint64_t column_offset = 0, uint64_t line_offset = 0) - { - throw parsing (p.name (), - nv.value_line + line_offset, - (line_offset == 0 ? nv.value_column : 1) + column_offset, - d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "name") - { - if (!name.empty ()) - bad_name ("task package name redefinition"); - - if (v.empty ()) - bad_value ("empty task package name"); - - name = move (v); - } - else if (n == "version") - { - if (!version.empty ()) - bad_name ("task package version redefinition"); - - try - { - version = bpkg::version (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task package version: ") + e.what ()); - } - - // Versions like 1.2.3- are forbidden in manifest as intended to be - // used for version constrains rather than actual releases. - // - if (version.release && version.release->empty ()) - bad_value ("invalid task package version release"); - } - else if (n == "repository") - { - if (!repository.empty ()) - bad_name ("task repository redefinition"); - - if (v.empty ()) - bad_value ("empty task repository"); - - try - { - // Call remote/absolute repository location constructor (throws - // invalid_argument for relative location). - // - repository = repository_location (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task repository: ") + e.what ()); - } - } - else if (n == "trust") - { - if (v != "yes" && !valid_fingerprint (v)) - bad_value ("invalid repository certificate fingerprint"); - - trust.emplace_back (move (v)); - } - else if (n == "machine") - { - if (!machine.empty ()) - bad_name ("task machine redefinition"); - - if (v.empty ()) - bad_value ("empty task machine"); - - machine = move (v); - } - else if (n == "target") - { - if (target) - bad_name ("task target redefinition"); - - try - { - target = target_triplet (v); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task target: ") + e.what ()); - } - } - else if (n == "config") - { - if (!config.empty ()) - bad_name ("task configuration redefinition"); - - // Note that when reporting errors we combine the manifest value - // position with the respective field and error positions. - // - try - { - istringstream is (v); - tab_parser parser (is, ""); - - // Here we naturally support multiline config manifest. - // - tab_fields tl; - while (!(tl = parser.next ()).empty ()) - { - for (auto& tf: tl) - { - try - { - check_config (tf.value); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid task configuration: ") + e.what (), - tf.column - 1, - tl.line - 1); - } - - config.emplace_back (move (tf.value)); - } - } - } - catch (const tab_parsing& e) - { - bad_value ("invalid task configuration: " + e.description, - e.column - 1, - e.line - 1); - } - - if (config.empty ()) - bad_value ("empty task configuration"); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task manifest"); - } - - // Verify all non-optional values were specified. - // - if (name.empty ()) - bad_value ("no task package name specified"); - - if (version.empty ()) - bad_value ("no task package version specified"); - - if (repository.empty ()) - bad_value ("no task repository specified"); - - if (machine.empty ()) - bad_value ("no task machine specified"); - } - - void task_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("name", name); - s.next ("version", version.string ()); - s.next ("repository", repository.string ()); - - for (const auto& v: trust) - s.next ("trust", v); - - s.next ("machine", machine); - - if (target) - s.next ("target", target->string ()); - - // Recompose config string as a space-separated variable list, - // - if (!config.empty ()) - { - string v; - for (auto b (config.cbegin ()), i (b), e (config.cend ()); i != e; ++i) - { - if (i != b) - v += ' '; - - v += *i; - } - - s.next ("config", v); - } - - s.next ("", ""); // End of manifest. - } - - strings task_manifest:: - unquoted_config () const - { - return string_parser::unquote (config); - } - - void task_manifest:: - check_config (const string& s) - { - auto i (s.begin ()); - auto e (s.end ()); - - // Iterate until the variable name end and check that it contains no - // whitespaces. - // - for (; i != e; ++i) - { - char c (*i); - - if (c == ' ' || c == '\t') // Whitespace in name. - throw invalid_argument ("expected variable assignment"); - else if (c == '=') - break; - } - - if (i == e) - throw invalid_argument ("no variable value"); - } - - // task_response_manifest - // - task_response_manifest:: - task_response_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of task response manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the task response manifest. - // - // Note that we need to distinguish an empty and absent session. - // - optional<string> sess; - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "session") - { - if (sess) - bad_name ("task response session redefinition"); - - sess = move (v); - } - else if (n == "challenge") - { - if (challenge) - bad_name ("task response challenge redefinition"); - - if (v.empty ()) - bad_value ("empty task response challenge"); - - challenge = move (v); - } - else if (n == "result-url") - { - if (result_url) - bad_name ("task response result url redefinition"); - - if (v.empty ()) - bad_value ("empty task response result url"); - - result_url = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in task response manifest"); - } - - // Verify all non-optional values were specified, and all values are - // expected. - // - if (!sess) - bad_value ("no task response session specified"); - - session = move (*sess); - - // If session is not empty then the challenge may, and the result url - // must, be present, otherwise they shouldn't. - // - if (!session.empty ()) - { - if (!result_url) - bad_value ("no task response result url specified"); - } - else - { - if (challenge) - bad_value ("unexpected task response challenge"); - - if (result_url) - bad_value ("unexpected task response result url"); - } - - // If session is not empty then the task manifest must follow, otherwise it - // shouldn't. - // - nv = p.next (); - - if (!session.empty ()) - { - if (nv.empty ()) - bad_value ("task manifest expected"); - - task = task_manifest (p, nv, iu); - - nv = p.next (); - } - - // Make sure this is the end. - // - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single task response manifest expected"); - } - - void task_response_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("session", session); - - if (challenge) - s.next ("challenge", *challenge); - - if (result_url) - s.next ("result-url", *result_url); - - s.next ("", ""); // End of manifest. - - if (task) - task->serialize (s); - - s.next ("", ""); // End of stream. - } - - // result_manifest - // - result_manifest:: - result_manifest (parser& p, bool iu) - : result_manifest (p, p.next (), iu) - { - // Make sure this is the end. - // - name_value nv (p.next ()); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single result manifest expected"); - } - - result_manifest:: - result_manifest (parser& p, name_value nv, bool iu) - { - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d, size_t offset = 0) - { - throw parsing (p.name (), nv.value_line, nv.value_column + offset, d); - }; - - auto result_stat = - [&bad_value] (const string& v, const string& what) -> result_status - { - try - { - return to_result_status (v); - } - catch (const invalid_argument&) - { - bad_value ("invalid " + what); - } - - // Can't be here. Would be redundant if it were possible to declare - // lambda with the [[noreturn]] attribute. Note that GCC (non-portably) - // supports that. - // - return result_status::abnormal; - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of result manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the result manifest. - // - optional<result_status> stat; - - // Number of parsed *-log values. Also denotes the next expected log type. - // - size_t nlog (0); - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "name") - { - if (!name.empty ()) - bad_name ("result package name redefinition"); - - if (v.empty ()) - bad_value ("empty result package name"); - - name = move (v); - } - else if (n == "version") - { - if (!version.empty ()) - bad_name ("result package version redefinition"); - - try - { - version = bpkg::version (move (v)); - } - catch (const invalid_argument& e) - { - bad_value (string ("invalid result package version: ") + e.what ()); - } - - // Versions like 1.2.3- are forbidden in manifest as intended to be - // used for version constrains rather than actual releases. - // - if (version.release && version.release->empty ()) - bad_value ("invalid result package version release"); - } - else if (n == "status") - { - if (stat) - bad_name ("result status redefinition"); - - stat = result_stat (v, "result status"); - } - else - { - size_t nn (n.size ()); // Name length. - - // Note: returns false if nothing preceeds a suffix. - // - auto suffix = [&n, nn] (const char* s, size_t sn) -> bool - { - return nn > sn && n.compare (nn - sn, sn, s) == 0; - }; - - size_t sn; - if (suffix ("-status", sn = 7)) - { - if (!stat) - bad_name ("result status must appear first"); - - if (nlog > 0) // Some logs have already been parsed. - bad_name (n + " after operations logs"); - - string op (n, 0, nn - sn); - - // Make sure the operation result status is not redefined. - // - for (const auto& r: results) - { - if (r.operation == op) - bad_name ("result " + n + " redefinition"); - } - - // Add the operation result (log will come later). - // - results.push_back ({move (op), result_stat (v, n), string ()}); - } - else if (suffix ("-log", sn = 4)) - { - string op (n, 0, nn - sn); - - // Check that specifically this operation log is expected. - // - if (nlog >= results.size ()) - bad_name ("unexpected " + n); - - if (results[nlog].operation != op) - bad_name (results[nlog].operation + "-log is expected"); - - // Save operation log. - // - results[nlog++].log = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in result manifest"); - } - } - - // Verify all non-optional values were specified. - // - if (name.empty ()) - bad_value ("no result package name specified"); - - if (version.empty ()) - bad_value ("no result package version specified"); - - if (!stat) - bad_value ("no result status specified"); - - // @@ Checking that the result status is consistent with operations - // statuses is a bit hairy, so let's postpone for now. - // - status = move (*stat); - - // Check that we have log for every operation result status. - // - if (nlog < results.size ()) - bad_name ("no result " + results[nlog].operation + "-log specified"); - } - - void result_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("name", name); - s.next ("version", version.string ()); - s.next ("status", to_string (status)); - - // Serialize *-status values. - // - for (const auto& r: results) - s.next (r.operation + "-status", to_string (r.status)); - - // Serialize *-log values. - // - for (const auto& r: results) - s.next (r.operation + "-log", r.log); - - s.next ("", ""); // End of manifest. - } - - // result_request_manifest - // - result_request_manifest:: - result_request_manifest (parser& p, bool iu) - { - name_value nv (p.next ()); - - auto bad_name = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.name_line, nv.name_column, d); - }; - - auto bad_value = [&p, &nv] (const string& d) - { - throw parsing (p.name (), nv.value_line, nv.value_column, d); - }; - - // Make sure this is the start and we support the version. - // - if (!nv.name.empty ()) - bad_name ("start of result request manifest expected"); - - if (nv.value != "1") - bad_value ("unsupported format version"); - - // Parse the result request manifest. - // - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - string& n (nv.name); - string& v (nv.value); - - if (n == "session") - { - if (!session.empty ()) - bad_name ("result request session redefinition"); - - if (v.empty ()) - bad_value ("empty result request session"); - - session = move (v); - } - else if (n == "challenge") - { - if (challenge) - bad_name ("result request challenge redefinition"); - - if (v.empty ()) - bad_value ("empty result request challenge"); - - challenge = move (v); - } - else if (!iu) - bad_name ("unknown name '" + n + "' in result request manifest"); - } - - // Verify all non-optional values were specified. - // - if (session.empty ()) - bad_value ("no result request session specified"); - - nv = p.next (); - if (nv.empty ()) - bad_value ("result manifest expected"); - - result = result_manifest (p, nv, iu); - - // Make sure this is the end. - // - nv = p.next (); - if (!nv.empty ()) - throw parsing (p.name (), nv.name_line, nv.name_column, - "single result request manifest expected"); - } - - void result_request_manifest:: - serialize (serializer& s) const - { - // @@ Should we check that all non-optional values are specified and all - // values are valid? - // - s.next ("", "1"); // Start of manifest. - s.next ("session", session); - - if (challenge) - s.next ("challenge", *challenge); - - s.next ("", ""); // End of manifest. - - result.serialize (s); - s.next ("", ""); // End of stream. - } -} |