aboutsummaryrefslogtreecommitdiff
path: root/load/load.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'load/load.cxx')
-rw-r--r--load/load.cxx527
1 files changed, 424 insertions, 103 deletions
diff --git a/load/load.cxx b/load/load.cxx
index 14b8374..2b2cd56 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>
@@ -32,6 +33,7 @@
#include <libbrep/database-lock.hxx>
#include <load/load-options.hxx>
+#include <load/options-types.hxx>
using std::cout;
using std::cerr;
@@ -364,7 +366,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,
@@ -421,10 +424,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,21 +438,49 @@ 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.
//
// Return nullopt if the text is in a file (can happen if the
@@ -594,6 +627,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),
@@ -622,7 +756,8 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.builds),
move (pm.build_constraints),
move (pm.build_auxiliaries),
- move (pm.build_configs),
+ move (bot_keys),
+ move (build_configs),
move (pm.location),
move (pm.fragment),
move (pm.sha256sum),
@@ -636,7 +771,7 @@ load_packages (const shared_ptr<repository>& rp,
move (pm.builds),
move (pm.build_constraints),
move (pm.build_auxiliaries),
- move (pm.build_configs),
+ move (build_configs),
rp);
db.persist (p);
@@ -670,8 +805,8 @@ load_packages (const shared_ptr<repository>& rp,
}
// A non-stub package is buildable if belongs to at least one
- // buildable repository (see libbrep/package.hxx for details).
- // Note that if this is an external test package it will be marked as
+ // buildable repository (see libbrep/package.hxx for details). Note
+ // that if this is an external test package it will be marked as
// unbuildable later (see resolve_dependencies() for details).
//
if (rp->buildable && !p->buildable && !p->stub ())
@@ -1018,7 +1153,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,
@@ -1071,35 +1207,25 @@ find (const lazy_shared_ptr<repository>& r,
return false;
}
-// 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 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.
+// Try to 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 regular dependencies). Set the buildable flag to
+// false for the resolved external tests packages. Leave the package member
+// NULL for unresolved dependencies.
//
static void
-resolve_dependencies (package& p,
- database& db,
- bool ignore_unresolved,
- bool ignore_unresolved_tests)
+resolve_dependencies (package& p, database& db)
{
using brep::dependency;
using brep::dependency_alternative;
using brep::dependency_alternatives;
+ using brep::test_dependency;
// Resolve dependencies for internal packages only.
//
assert (p.internal ());
- if (p.dependencies.empty () && p.tests.empty ())
- return;
-
auto resolve = [&p, &db] (dependency& d, bool test)
{
// Dependency should not be resolved yet.
@@ -1189,6 +1315,60 @@ resolve_dependencies (package& p,
return false;
};
+ // Update the package state if any dependency is resolved.
+ //
+ bool update (false);
+
+ for (dependency_alternatives& das: p.dependencies)
+ {
+ for (dependency_alternative& da: das)
+ {
+ for (dependency& d: da)
+ {
+ if (resolve (d, false /* test */))
+ update = true;
+ }
+ }
+ }
+
+ for (test_dependency& td: p.tests)
+ {
+ if (resolve (td, true /* test */))
+ update = true;
+ }
+
+ if (update)
+ db.update (p);
+}
+
+// Verify that the unresolved dependencies can be ignored.
+//
+// Specifically, fail for an unresolved regular dependency, unless
+// ignore_unresolved is true or this is a conditional dependency and either
+// ignore_unresolved_cond argument is 'all' or it is 'tests' and the specified
+// package is a tests, examples, or benchmarks package. Fail for an unresolved
+// external test, unless ignore_unresolved or ignore_unresolved_tests is
+// true. If ignore_unresolved_tests is true, then remove the unresolved tests
+// entry from the package manifest. Should be called once per internal package
+// after resolve_dependencies() is called for all of them.
+//
+static void
+verify_dependencies (
+ package& p,
+ database& db,
+ bool ignore_unresolved,
+ bool ignore_unresolved_tests,
+ optional<ignore_unresolved_conditional_dependencies> ignore_unresolved_cond)
+{
+ using brep::dependency;
+ using brep::dependency_alternative;
+ using brep::dependency_alternatives;
+ using brep::test_dependency;
+
+ // Verify dependencies for internal packages only.
+ //
+ assert (p.internal ());
+
auto bail = [&p] (const dependency& d, const string& what)
{
cerr << "error: can't resolve " << what << ' ' << d << " for the package "
@@ -1199,43 +1379,74 @@ resolve_dependencies (package& p,
throw failed ();
};
- for (dependency_alternatives& das: p.dependencies)
+ if (!ignore_unresolved)
{
- // Practically it is enough to resolve at least one dependency alternative
- // to build a package. Meanwhile here we consider an error specifying in
- // the manifest file an alternative which can't be resolved, unless
- // unresolved dependencies are allowed.
+ // There must always be a reason why a package is not buildable.
//
- for (dependency_alternative& da: das)
+ assert (p.buildable || p.unbuildable_reason);
+
+ bool test (!p.buildable &&
+ *p.unbuildable_reason == unbuildable_reason::test);
+
+ for (dependency_alternatives& das: p.dependencies)
{
- for (dependency& d: da)
+ for (dependency_alternative& da: das)
{
- if (!resolve (d, false /* test */) && !ignore_unresolved)
- bail (d, "dependency");
+ for (dependency& d: da)
+ {
+ if (d.package == nullptr)
+ {
+ if (da.enable && ignore_unresolved_cond)
+ {
+ switch (*ignore_unresolved_cond)
+ {
+ case ignore_unresolved_conditional_dependencies::all: continue;
+ case ignore_unresolved_conditional_dependencies::tests:
+ {
+ if (test)
+ continue;
+
+ break;
+ }
+ }
+ }
+
+ bail (d, "dependency");
+ }
+ }
}
}
}
- for (auto i (p.tests.begin ()); i != p.tests.end (); )
+ if (!ignore_unresolved || ignore_unresolved_tests)
{
- brep::test_dependency& td (*i);
+ // Update the package state if any test dependency is erased.
+ //
+ bool update (false);
- if (!resolve (td, true /* test */))
+ for (auto i (p.tests.begin ()); i != p.tests.end (); )
{
- if (!ignore_unresolved && !ignore_unresolved_tests)
- bail (td, to_string (td.type));
+ test_dependency& td (*i);
- if (ignore_unresolved_tests)
+ if (td.package == nullptr)
{
- i = p.tests.erase (i);
- continue;
+ if (!ignore_unresolved && !ignore_unresolved_tests)
+ bail (td, to_string (td.type));
+
+ if (ignore_unresolved_tests)
+ {
+ i = p.tests.erase (i);
+ update = true;
+ continue;
+ }
}
+
+ ++i;
}
- ++i;
+ if (update)
+ db.update (p);
}
-
- db.update (p); // Update the package state.
}
using package_ids = vector<package_id>;
@@ -1294,7 +1505,12 @@ detect_dependency_cycle (const package_id& id,
for (const auto& da: das)
{
for (const auto& d: da)
- detect_dependency_cycle (d.package.object_id (), chain, db);
+ {
+ // Skip unresolved dependencies.
+ //
+ if (d.package != nullptr)
+ detect_dependency_cycle (d.package.object_id (), chain, db);
+ }
}
}
@@ -1508,11 +1724,23 @@ try
//
const string& tnt (ops.tenant ());
- if (ops.tenant_specified () && tnt.empty ())
+ if (ops.tenant_specified ())
{
- cerr << "error: empty tenant" << endl
- << help_info << endl;
- throw failed ();
+ if (tnt.empty ())
+ {
+ cerr << "error: empty tenant" << endl
+ << help_info << endl;
+ throw failed ();
+ }
+ }
+ else
+ {
+ if (ops.existing_tenant ())
+ {
+ cerr << "error: --existing-tenant requires --tenant" << endl
+ << help_info << endl;
+ throw failed ();
+ }
}
// Verify the --service-* options.
@@ -1521,14 +1749,15 @@ try
{
if (!ops.tenant_specified ())
{
- cerr << "error: --service-id requires --tenant" << endl;
+ cerr << "error: --service-id requires --tenant" << endl
+ << help_info << endl;
throw failed ();
}
if (ops.service_type ().empty ())
{
- cerr << "error: --service-id requires --service-type"
- << endl;
+ cerr << "error: --service-id requires --service-type" << endl
+ << help_info << endl;
throw failed ();
}
}
@@ -1536,15 +1765,15 @@ try
{
if (ops.service_type_specified ())
{
- cerr << "error: --service-type requires --service-id"
- << endl;
+ cerr << "error: --service-type requires --service-id" << endl
+ << help_info << endl;
throw failed ();
}
if (ops.service_data_specified ())
{
- cerr << "error: --service-data requires --service-id"
- << endl;
+ cerr << "error: --service-data requires --service-id" << endl
+ << help_info << endl;
throw failed ();
}
}
@@ -1618,23 +1847,68 @@ try
if (ops.force () || changed (tnt, irs, db))
{
+ shared_ptr<tenant> t; // Not NULL in the --existing-tenant mode.
+
// Rebuild repositories persistent state from scratch.
//
// Note that in the single-tenant mode the tenant must be empty. In the
- // multi-tenant mode all tenants must be non-empty. So in the
- // single-tenant mode we erase all database objects (possibly from
- // multiple tenants). Otherwise, cleanup the specified and the empty
- // tenants only.
+ // multi-tenant mode all tenants, excluding the pre-created ones, must be
+ // non-empty. So in the single-tenant mode we erase all database objects
+ // (possibly from multiple tenants). Otherwise, cleanup the empty tenant
+ // and, unless in the --existing-tenant mode, the specified one.
//
if (tnt.empty ()) // Single-tenant mode.
{
db.erase_query<package> ();
db.erase_query<repository> ();
+ db.erase_query<public_key> ();
db.erase_query<tenant> ();
}
else // Multi-tenant mode.
{
- cstrings ts ({tnt.c_str (), ""});
+ // NOTE: don't forget to update ci_start::create() if changing anything
+ // here.
+ //
+ cstrings ts ({""});
+
+ // In the --existing-tenant mode make sure that the specified tenant
+ // exists, is not archived, not marked as unloaded, and is
+ // empty. Otherwise (not in the --existing-tenant mode), remove this
+ // tenant.
+ //
+ if (ops.existing_tenant ())
+ {
+ t = db.find<tenant> (tnt);
+
+ if (t == nullptr)
+ {
+ cerr << "error: unable to find tenant " << tnt << endl;
+ throw failed ();
+ }
+
+ if (t->archived)
+ {
+ cerr << "error: tenant " << tnt << " is archived" << endl;
+ throw failed ();
+ }
+
+ if (t->unloaded_timestamp)
+ {
+ cerr << "error: tenant " << tnt << " is marked as unloaded" << endl;
+ throw failed ();
+ }
+
+ size_t n (db.query_value<repository_count> (
+ query<repository_count>::id.tenant == tnt));
+
+ if (n != 0)
+ {
+ cerr << "error: tenant " << tnt << " is not empty" << endl;
+ throw failed ();
+ }
+ }
+ else
+ ts.push_back (tnt.c_str ());
db.erase_query<package> (
query<package>::id.tenant.in_range (ts.begin (), ts.end ()));
@@ -1642,36 +1916,75 @@ 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.
+ // Craft the tenant service object from the --service-* options.
//
- // 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.
+ // In the --existing-tenant mode make sure that the specified service
+ // matches the service associated with the pre-created tenant and update
+ // the service data, if specified.
//
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> ()),
- move (service)));
+ if (ops.existing_tenant ())
+ {
+ assert (t != nullptr);
+
+ if (!t->service)
+ {
+ cerr << "error: no service associated with tenant " << tnt << endl;
+ throw failed ();
+ }
+
+ if (t->service->id != service->id || t->service->type != service->type)
+ {
+ cerr << "error: associated service mismatch for tenant " << tnt << endl <<
+ " info: specified service: " << service->id << ' '
+ << service->type << endl <<
+ " info: associated service: " << t->service->id << ' '
+ << t->service->type << endl;
+ throw failed ();
+ }
+
+ if (service->data)
+ {
+ t->service->data = move (service->data);
+ db.update (t);
+ }
+ }
+ }
+
+ // 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.
+ //
+ if (!ops.existing_tenant ())
+ db.persist (tenant (tnt,
+ ops.private_ (),
+ (ops.interactive_specified ()
+ ? ops.interactive ()
+ : optional<string> ()),
+ move (service)));
// On the first pass over the internal repositories we load their
// certificate information and packages.
@@ -1696,7 +2009,8 @@ try
ir.buildable,
priority++));
- load_packages (r,
+ load_packages (ops,
+ r,
r->cache_location,
db,
ops.ignore_unknown (),
@@ -1722,29 +2036,36 @@ try
ops.shallow ());
}
- // Resolve internal packages dependencies and, unless this is a shallow
- // load, make sure there are no package dependency cycles.
+ // Try to resolve the internal packages dependencies and verify that the
+ // unresolved ones can be ignored. Unless this is a shallow load, make
+ // sure there are no package dependency cycles.
//
{
session s;
using query = query<package>;
- for (auto& p:
- db.query<package> (
- query::id.tenant == tnt &&
- query::internal_repository.canonical_name.is_not_null ()))
- resolve_dependencies (p,
- db,
- ops.shallow (),
- ops.ignore_unresolved_tests ());
+ query q (query::id.tenant == tnt &&
+ query::internal_repository.canonical_name.is_not_null ());
+
+ for (auto& p: db.query<package> (q))
+ resolve_dependencies (p, db);
+
+ for (auto& p: db.query<package> (q))
+ {
+ verify_dependencies (
+ p,
+ db,
+ ops.shallow (),
+ ops.ignore_unresolv_tests (),
+ (ops.ignore_unresolv_cond_specified ()
+ ? ops.ignore_unresolv_cond ()
+ : optional<ignore_unresolved_conditional_dependencies> ()));
+ }
if (!ops.shallow ())
{
package_ids chain;
- for (const auto& p:
- db.query<package> (
- query::id.tenant == tnt &&
- query::internal_repository.canonical_name.is_not_null ()))
+ for (const auto& p: db.query<package> (q))
detect_dependency_cycle (p.id, chain, db);
}
}