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