aboutsummaryrefslogtreecommitdiff
path: root/load/load.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'load/load.cxx')
-rw-r--r--load/load.cxx377
1 files changed, 317 insertions, 60 deletions
diff --git a/load/load.cxx b/load/load.cxx
index ed48910..474b443 100644
--- a/load/load.cxx
+++ b/load/load.cxx
@@ -20,6 +20,7 @@
#include <libbutl/pager.hxx>
#include <libbutl/sha256.hxx>
#include <libbutl/process.hxx>
+#include <libbutl/openssl.hxx>
#include <libbutl/fdstream.hxx>
#include <libbutl/filesystem.hxx>
#include <libbutl/tab-parser.hxx>
@@ -364,7 +365,8 @@ repository_info (const options& lo, const string& rl, const cstrings& options)
// the repository. Should be called once per repository.
//
static void
-load_packages (const shared_ptr<repository>& rp,
+load_packages (const options& lo,
+ const shared_ptr<repository>& rp,
const repository_location& cl,
database& db,
bool ignore_unknown,
@@ -405,8 +407,8 @@ load_packages (const shared_ptr<repository>& rp,
mp,
move (nv),
ignore_unknown,
- false /* complete_depends */,
- package_manifest_flags::forbid_incomplete_dependencies);
+ false /* complete_values */,
+ package_manifest_flags::forbid_incomplete_values);
}
else
pms = pkg_package_manifests (mp, ignore_unknown);
@@ -421,10 +423,12 @@ load_packages (const shared_ptr<repository>& rp,
using brep::dependency_alternative;
using brep::dependency_alternatives;
+ const string& tenant (rp->tenant);
+
for (package_manifest& pm: pms)
{
shared_ptr<package> p (
- db.find<package> (package_id (rp->tenant, pm.name, pm.version)));
+ db.find<package> (package_id (tenant, pm.name, pm.version)));
// sha256sum should always be present if the package manifest comes from
// the packages.manifest file belonging to the pkg repository.
@@ -433,67 +437,132 @@ load_packages (const shared_ptr<repository>& rp,
if (p == nullptr)
{
- if (rp->internal)
+ // Apply the package manifest overrides.
+ //
+ if (!overrides.empty ())
+ try
{
- if (!overrides.empty ())
- try
- {
- pm.override (overrides, overrides_name);
- }
- catch (const manifest_parsing& e)
- {
- cerr << "error: unable to override " << p << " manifest: " << e
- << endl;
+ pm.override (overrides, overrides_name);
+ }
+ catch (const manifest_parsing& e)
+ {
+ cerr << "error: unable to override " << pm.name << ' ' << pm.version
+ << " manifest: " << e << endl;
- throw failed ();
+ throw failed ();
+ }
+
+ // Convert the package manifest build configurations (contain public
+ // keys data) into the brep's build package configurations (contain
+ // public key object lazy pointers). Keep the bot key lists empty if
+ // the package is not buildable.
+ //
+ package_build_configs build_configs;
+
+ if (!pm.build_configs.empty ())
+ {
+ build_configs.reserve (pm.build_configs.size ());
+
+ for (bpkg::build_package_config& c: pm.build_configs)
+ {
+ build_configs.emplace_back (move (c.name),
+ move (c.arguments),
+ move (c.comment),
+ move (c.builds),
+ move (c.constraints),
+ move (c.auxiliaries),
+ package_build_bot_keys (),
+ move (c.email),
+ move (c.warning_email),
+ move (c.error_email));
}
+ }
+ if (rp->internal)
+ {
// Create internal package object.
//
- optional<string> dsc;
- optional<text_type> dst;
-
- if (pm.description)
+ // Return nullopt if the text is in a file (can happen if the
+ // repository is of a type other than pkg) or if the type is not
+ // recognized (can only happen in the "ignore unknown" mode).
+ //
+ auto to_typed_text = [&cl, ignore_unknown] (typed_text_file&& v)
{
+ optional<typed_text> r;
+
// The description value should not be of the file type if the
// package manifest comes from the pkg repository.
//
- assert (!pm.description->file || cl.type () != repository_type::pkg);
+ assert (!v.file || cl.type () != repository_type::pkg);
- if (!pm.description->file)
+ if (!v.file)
{
- dst = pm.effective_description_type (ignore_unknown);
+ // Cannot throw since the manifest parser has already verified the
+ // effective type in the same "ignore unknown" mode.
+ //
+ optional<text_type> t (v.effective_type (ignore_unknown));
// If the description type is unknown (which may be the case for
// some "transitional" period and only if --ignore-unknown is
// specified) we just silently drop the description.
//
- assert (dst || ignore_unknown);
+ assert (t || ignore_unknown);
- if (dst)
- dsc = move (pm.description->text);
+ if (t)
+ r = typed_text {move (v.text), *t};
}
- }
- string chn;
+ return r;
+ };
+
+ // Convert descriptions.
+ //
+ optional<typed_text> ds (
+ pm.description
+ ? to_typed_text (move (*pm.description))
+ : optional<typed_text> ());
+
+ optional<typed_text> pds (
+ pm.package_description
+ ? to_typed_text (move (*pm.package_description))
+ : optional<typed_text> ());
+
+ // Merge changes into a single typed text object.
+ //
+ // If the text type is not recognized for any changes entry or some
+ // entry refers to a file, then assume that no changes are specified.
+ //
+ optional<typed_text> chn;
+
for (auto& c: pm.changes)
{
- // The changes value should not be of the file type if the package
- // manifest comes from the pkg repository.
- //
- assert (!c.file || cl.type () != repository_type::pkg);
+ optional<typed_text> tc (to_typed_text (move (c)));
- if (!c.file)
+ if (!tc)
{
- if (chn.empty ())
- chn = move (c.text);
- else
- {
- if (chn.back () != '\n')
- chn += '\n'; // Always have a blank line as a separator.
+ chn = nullopt;
+ break;
+ }
- chn += "\n" + c.text;
- }
+ if (!chn)
+ {
+ chn = move (*tc);
+ }
+ else
+ {
+ // Should have failed while parsing the manifest otherwise.
+ //
+ assert (tc->type == chn->type);
+
+ string& v (chn->text);
+
+ assert (!v.empty ()); // Changes manifest value cannot be empty.
+
+ if (v.back () != '\n')
+ v += '\n'; // Always have a blank line as a separator.
+
+ v += '\n';
+ v += tc->text;
}
}
@@ -549,6 +618,7 @@ load_packages (const shared_ptr<repository>& rp,
td.type,
td.buildtime,
move (td.constraint),
+ move (td.enable),
move (td.reflect));
}
@@ -556,6 +626,107 @@ load_packages (const shared_ptr<repository>& rp,
//
package_name project (pm.effective_project ());
+ // If the package is buildable, then save the package manifest's
+ // common and build configuration-specific bot keys into the database
+ // and translate the key data lists into the lists of the public key
+ // object lazy pointers.
+ //
+ package_build_bot_keys bot_keys;
+
+ if (rp->buildable)
+ {
+ // Save the specified bot keys into the database as public key
+ // objects, unless they are already persisted. Translate these keys
+ // into the public key object lazy pointers.
+ //
+ auto keys_to_objects = [&lo,
+ &pm,
+ &tenant,
+ &db] (strings&& keys)
+ {
+ package_build_bot_keys r;
+
+ if (keys.empty ())
+ return r;
+
+ r.reserve (keys.size ());
+
+ for (string& key: keys)
+ {
+ // Calculate the key fingerprint.
+ //
+ string fp;
+
+ try
+ {
+ openssl os (path ("-"), path ("-"), 2,
+ lo.openssl (),
+ "pkey",
+ lo.openssl_option (), "-pubin", "-outform", "DER");
+
+ os.out << key;
+ os.out.close ();
+
+ fp = sha256 (os.in).string ();
+ os.in.close ();
+
+ if (!os.wait ())
+ {
+ cerr << "process " << lo.openssl () << ' ' << *os.exit
+ << endl;
+
+ throw io_error ("");
+ }
+ }
+ catch (const io_error&)
+ {
+ cerr << "error: unable to convert custom build bot public key "
+ << "for package " << pm.name << ' ' << pm.version << endl
+ << " info: key:" << endl
+ << key << endl;
+
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ cerr << "error: unable to convert custom build bot public key "
+ << "for package " << pm.name << ' ' << pm.version << ": "
+ << e << endl;
+
+ throw failed ();
+ }
+
+ // Try to find the public_key object for the calculated
+ // fingerprint. If it doesn't exist, then create and persist the
+ // new object.
+ //
+ public_key_id id (tenant, move (fp));
+ shared_ptr<public_key> k (db.find<public_key> (id));
+
+ if (k == nullptr)
+ {
+ k = make_shared<public_key> (move (id.tenant),
+ move (id.fingerprint),
+ move (key));
+
+ db.persist (k);
+ }
+
+ r.push_back (move (k));
+ }
+
+ return r;
+ };
+
+ bot_keys = keys_to_objects (move (pm.build_bot_keys));
+
+ assert (build_configs.size () == pm.build_configs.size ());
+
+ for (size_t i (0); i != build_configs.size (); ++i)
+ build_configs[i].bot_keys =
+ keys_to_objects (move (pm.build_configs[i].bot_keys));
+ }
+
p = make_shared<package> (
move (pm.name),
move (pm.version),
@@ -566,8 +737,8 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.license_alternatives),
move (pm.topics),
move (pm.keywords),
- move (dsc),
- move (dst),
+ move (ds),
+ move (pds),
move (chn),
move (pm.url),
move (pm.doc_url),
@@ -583,7 +754,9 @@ load_packages (const shared_ptr<repository>& rp,
move (ts),
move (pm.builds),
move (pm.build_constraints),
- move (pm.build_configs),
+ move (pm.build_auxiliaries),
+ move (bot_keys),
+ move (build_configs),
move (pm.location),
move (pm.fragment),
move (pm.sha256sum),
@@ -596,6 +769,8 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.version),
move (pm.builds),
move (pm.build_constraints),
+ move (pm.build_auxiliaries),
+ move (build_configs),
rp);
db.persist (p);
@@ -977,7 +1152,8 @@ load_repositories (const options& lo,
// We don't apply overrides to the external packages.
//
- load_packages (pr,
+ load_packages (lo,
+ pr,
!pr->cache_location.empty () ? pr->cache_location : cl,
db,
ignore_unknown,
@@ -1030,16 +1206,23 @@ find (const lazy_shared_ptr<repository>& r,
return false;
}
-// Resolve package run-time dependencies and external tests. Make sure that
-// the best matching dependency belongs to the package repositories, their
+// Resolve package regular dependencies and external tests. Make sure that the
+// best matching dependency belongs to the package repositories, their
// complements, recursively, or their immediate prerequisite repositories
-// (only for run-time dependencies). Set the buildable flag to false for the
-// resolved external tests packages. Fail if unable to resolve a dependency,
-// unless ignore_unresolved is true in which case leave this dependency
-// NULL. Should be called once per internal package.
+// (only for regular dependencies). Set the buildable flag to false for the
+// resolved external tests packages. Fail if unable to resolve a regular
+// dependency, unless ignore_unresolved is true in which case leave this
+// dependency NULL. Fail if unable to resolve an external test, unless
+// ignore_unresolved or ignore_unresolved_tests is true in which case leave
+// this dependency NULL, if ignore_unresolved_tests is false, and remove the
+// respective tests manifest entry otherwise. Should be called once per
+// internal package.
//
static void
-resolve_dependencies (package& p, database& db, bool ignore_unresolved)
+resolve_dependencies (package& p,
+ database& db,
+ bool ignore_unresolved,
+ bool ignore_unresolved_tests)
{
using brep::dependency;
using brep::dependency_alternative;
@@ -1141,10 +1324,10 @@ resolve_dependencies (package& p, database& db, bool ignore_unresolved)
return false;
};
- auto bail = [&p] (const dependency& d, const char* what)
+ auto bail = [&p] (const dependency& d, const string& what)
{
- cerr << "error: can't resolve " << what << " " << d << " for the package "
- << p.name << " " << p.version << endl
+ cerr << "error: can't resolve " << what << ' ' << d << " for the package "
+ << p.name << ' ' << p.version << endl
<< " info: repository " << p.internal_repository.load ()->location
<< " appears to be broken" << endl;
@@ -1168,10 +1351,23 @@ resolve_dependencies (package& p, database& db, bool ignore_unresolved)
}
}
- for (brep::test_dependency& td: p.tests)
+ for (auto i (p.tests.begin ()); i != p.tests.end (); )
{
- if (!resolve (td, true /* test */) && !ignore_unresolved)
- bail (td, td.name.string ().c_str ());
+ brep::test_dependency& td (*i);
+
+ if (!resolve (td, true /* test */))
+ {
+ if (!ignore_unresolved && !ignore_unresolved_tests)
+ bail (td, to_string (td.type));
+
+ if (ignore_unresolved_tests)
+ {
+ i = p.tests.erase (i);
+ continue;
+ }
+ }
+
+ ++i;
}
db.update (p); // Update the package state.
@@ -1454,6 +1650,40 @@ try
throw failed ();
}
+ // Verify the --service-* options.
+ //
+ if (ops.service_id_specified ())
+ {
+ if (!ops.tenant_specified ())
+ {
+ cerr << "error: --service-id requires --tenant" << endl;
+ throw failed ();
+ }
+
+ if (ops.service_type ().empty ())
+ {
+ cerr << "error: --service-id requires --service-type"
+ << endl;
+ throw failed ();
+ }
+ }
+ else
+ {
+ if (ops.service_type_specified ())
+ {
+ cerr << "error: --service-type requires --service-id"
+ << endl;
+ throw failed ();
+ }
+
+ if (ops.service_data_specified ())
+ {
+ cerr << "error: --service-data requires --service-id"
+ << endl;
+ throw failed ();
+ }
+ }
+
// Parse and validate overrides, if specified.
//
// Note that here we make sure that the overrides manifest is valid.
@@ -1535,6 +1765,7 @@ try
{
db.erase_query<package> ();
db.erase_query<repository> ();
+ db.erase_query<public_key> ();
db.erase_query<tenant> ();
}
else // Multi-tenant mode.
@@ -1547,17 +1778,39 @@ try
db.erase_query<repository> (
query<repository>::id.tenant.in_range (ts.begin (), ts.end ()));
+ db.erase_query<public_key> (
+ query<public_key>::id.tenant.in_range (ts.begin (), ts.end ()));
+
db.erase_query<tenant> (
query<tenant>::id.in_range (ts.begin (), ts.end ()));
}
// Persist the tenant.
//
+ // Note that if the tenant service is specified and some tenant with the
+ // same service id and type is already persisted, then we will end up with
+ // the `object already persistent` error and terminate with the exit code
+ // 1 (fatal error). We could potentially dedicate a special exit code for
+ // such a case, so that the caller may recognize it and behave accordingly
+ // (CI request handler can treat it as a client error rather than an
+ // internal error, etc). However, let's first see if it ever becomes a
+ // problem.
+ //
+ optional<tenant_service> service;
+
+ if (ops.service_id_specified ())
+ service = tenant_service (ops.service_id (),
+ ops.service_type (),
+ (ops.service_data_specified ()
+ ? ops.service_data ()
+ : optional<string> ()));
+
db.persist (tenant (tnt,
ops.private_ (),
(ops.interactive_specified ()
? ops.interactive ()
- : optional<string> ())));
+ : optional<string> ()),
+ move (service)));
// On the first pass over the internal repositories we load their
// certificate information and packages.
@@ -1582,7 +1835,8 @@ try
ir.buildable,
priority++));
- load_packages (r,
+ load_packages (ops,
+ r,
r->cache_location,
db,
ops.ignore_unknown (),
@@ -1619,7 +1873,10 @@ try
db.query<package> (
query::id.tenant == tnt &&
query::internal_repository.canonical_name.is_not_null ()))
- resolve_dependencies (p, db, ops.shallow ());
+ resolve_dependencies (p,
+ db,
+ ops.shallow (),
+ ops.ignore_unresolved_tests ());
if (!ops.shallow ())
{