From 70c1cdfd8f34472761fe5ec97f0713990c1b4f5b Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 5 Sep 2018 21:23:41 +0300 Subject: Add multi-tenancy support --- brep/handler/ci/ci-load.in | 22 ++----- clean/clean.cxx | 4 +- libbrep/build-extra.sql | 8 ++- libbrep/build-package.hxx | 46 ++++++++------ libbrep/build.cxx | 7 ++- libbrep/build.hxx | 63 ++++++++++--------- libbrep/build.xml | 6 ++ libbrep/common.hxx | 91 ++++++++++++++++++++++++--- libbrep/package-extra.sql | 57 ++++++++++------- libbrep/package.cxx | 70 +++++++++++---------- libbrep/package.hxx | 40 +++++++----- libbrep/package.xml | 107 +++++++++++++++++++++++++------- libbrep/types.hxx | 2 + load/load.cli | 6 ++ load/load.cxx | 84 +++++++++++++++++++------ mod/build-config.cxx | 9 ++- mod/mod-build-force.cxx | 3 +- mod/mod-build-log.cxx | 18 +++++- mod/mod-build-result.cxx | 23 ++++--- mod/mod-build-task.cxx | 69 ++++++++++++--------- mod/mod-builds.cxx | 119 ++++++++++++++++++++++-------------- mod/mod-ci.cxx | 2 +- mod/mod-package-details.cxx | 32 ++++++---- mod/mod-package-search.cxx | 20 +++--- mod/mod-package-version-details.cxx | 17 +++--- mod/mod-repository-details.cxx | 13 ++-- mod/mod-repository-root.cxx | 18 +++++- mod/mod-submit.cxx | 2 +- mod/module.hxx | 10 ++- mod/page.cxx | 31 +++++++--- mod/page.hxx | 59 ++++++++++++------ mod/utility.hxx | 25 ++++++++ tests/ci/ci-load.testscript | 13 ++-- tests/load/driver.cxx | 104 +++++++++++++++++++------------ 34 files changed, 810 insertions(+), 390 deletions(-) create mode 100644 mod/utility.hxx diff --git a/brep/handler/ci/ci-load.in b/brep/handler/ci/ci-load.in index da5fb91..ad96943 100644 --- a/brep/handler/ci/ci-load.in +++ b/brep/handler/ci/ci-load.in @@ -128,7 +128,7 @@ spec="$spec$repository" message_suffix= if [ -n "$result_url" ]; then - message_suffix=": $result_url" + message_suffix=": $result_url/@$reference" # Append the tenant id. fi # Exit with the 'CI request is queued' response if simulating. @@ -169,28 +169,16 @@ display_name="$(sed -r \ loadtab="$data_dir/loadtab" run echo "$repository $display_name cache:cache" >"$loadtab" -# Load the repository into the brep package database. +# Load the repository into the brep package database for the tenant identified +# by the reference. # # Note that for now we load all the packages the repository contains without # regard to the request manifest package values. Later, we could add filtering # of the packages.manifest file against the request manifest values. While at # it, we could also deduce the repository display name (see above). @@ TODO # -run "$loader" "${loader_options[@]}" --force --shallow "$loadtab" - -# @@ TMP TENANCY: before we add tenancy support we also need to manually -# cleanup the build database. We will assume the cleanup code to optionally -# be present in a separate bash script which path is deduced from the -# handler's own path by adding the -clean suffix. -# -cleaner="$0-clean" -if [ -f "$cleaner" ]; then - - # Let's pass the CI request data directory to the cleaner, so it can create - # temporary files in this directory. - # - run "$cleaner" "$data_dir" -fi +run "$loader" "${loader_options[@]}" --force --shallow --tenant "$reference" \ +"$loadtab" # Remove the no longer needed CI request data directory. # diff --git a/clean/clean.cxx b/clean/clean.cxx index 1071123..102b165 100644 --- a/clean/clean.cxx +++ b/clean/clean.cxx @@ -134,7 +134,9 @@ try using prep_bld_query = prepared_query; size_t offset (0); - bld_query bq ("ORDER BY" + bld_query::id.package.name + + bld_query bq ("ORDER BY" + + bld_query::id.package.tenant + "," + + bld_query::id.package.name + order_by_version_desc (bld_query::id.package.version, false) + "OFFSET" + bld_query::_ref (offset) + "LIMIT 100"); diff --git a/libbrep/build-extra.sql b/libbrep/build-extra.sql index e0aa92a..cc43239 100644 --- a/libbrep/build-extra.sql +++ b/libbrep/build-extra.sql @@ -13,7 +13,8 @@ DROP FOREIGN TABLE IF EXISTS build_repository; -- -- CREATE FOREIGN TABLE build_repository ( - name TEXT NOT NULL, + tenant TEXT NOT NULL, + canonical_name TEXT NOT NULL, location_url TEXT NOT NULL, location_type TEXT NOT NULL, certificate_fingerprint TEXT NULL) @@ -23,6 +24,7 @@ SERVER package_server OPTIONS (table_name 'repository'); -- -- CREATE FOREIGN TABLE build_package ( + tenant TEXT NOT NULL, name CITEXT NOT NULL, version_epoch INTEGER NOT NULL, version_canonical_upstream TEXT NOT NULL, @@ -30,7 +32,8 @@ CREATE FOREIGN TABLE build_package ( version_revision INTEGER NOT NULL, version_upstream TEXT NOT NULL, version_release TEXT NULL, - internal_repository TEXT NULL) + internal_repository_tenant TEXT NULL, + internal_repository_canonical_name TEXT NULL) SERVER package_server OPTIONS (table_name 'package'); -- The foreign table for the build_package object constraints member (that is @@ -38,6 +41,7 @@ SERVER package_server OPTIONS (table_name 'package'); -- -- CREATE FOREIGN TABLE build_package_constraints ( + tenant TEXT NOT NULL, name CITEXT NOT NULL, version_epoch INTEGER NOT NULL, version_canonical_upstream TEXT NOT NULL, diff --git a/libbrep/build-package.hxx b/libbrep/build-package.hxx index ca65dbf..0fed500 100644 --- a/libbrep/build-package.hxx +++ b/libbrep/build-package.hxx @@ -29,21 +29,25 @@ namespace brep class build_repository { public: - string name; // Object id (canonical name). + repository_id id; + + const string& canonical_name; // Tracks id.canonical_name. repository_location location; optional certificate_fingerprint; // Database mapping. // - #pragma db member(name) id + #pragma db member(id) id column("") - #pragma db member(location) \ - set(this.location = std::move (?); \ - assert (this.name == this.location.canonical_name ())) + #pragma db member(canonical_name) transient + + #pragma db member(location) \ + set(this.location = std::move (?); \ + assert (this.canonical_name == this.location.canonical_name ())) private: friend class odb::access; - build_repository () = default; + build_repository (): canonical_name (id.canonical_name) {} }; // "Foreign" value type that is mapped to a subset of the build_constraint @@ -85,12 +89,16 @@ namespace brep // Packages that can potentially be built (internal non-stub). // - #pragma db view \ - object(build_package) \ - object(build_repository inner: \ - build_package::internal_repository == build_repository::name && \ - brep::compare_version_ne (build_package::id.version, \ - brep::wildcard_version, \ + // Note that ADL can't find the equal operator, so we use the function call + // notation. + // + #pragma db view \ + object(build_package) \ + object(build_repository inner: \ + brep::operator== (build_package::internal_repository, \ + build_repository::id) && \ + brep::compare_version_ne (build_package::id.version, \ + brep::wildcard_version, \ false)) struct buildable_package { @@ -102,12 +110,13 @@ namespace brep #pragma db member(version) set(this.version.init (this.id.version, (?))) }; - #pragma db view \ - object(build_package) \ - object(build_repository inner: \ - build_package::internal_repository == build_repository::name && \ - brep::compare_version_ne (build_package::id.version, \ - brep::wildcard_version, \ + #pragma db view \ + object(build_package) \ + object(build_repository inner: \ + brep::operator== (build_package::internal_repository, \ + build_repository::id) && \ + brep::compare_version_ne (build_package::id.version, \ + brep::wildcard_version, \ false)) struct buildable_package_count { @@ -128,6 +137,7 @@ namespace brep table("build_package_constraints" = "c") \ object(build_package = package inner: \ "c.exclusion AND " \ + "c.tenant = " + package::id.tenant + "AND" + \ "c.name = " + package::id.name + "AND" + \ "c.version_epoch = " + package::id.version.epoch + "AND" + \ "c.version_canonical_upstream = " + \ diff --git a/libbrep/build.cxx b/libbrep/build.cxx index 6ed711c..c4b32d0 100644 --- a/libbrep/build.cxx +++ b/libbrep/build.cxx @@ -55,13 +55,16 @@ namespace brep // build // build:: - build (package_name_type pnm, version pvr, + build (string tnt, + package_name_type pnm, + version pvr, string cfg, string tnm, version tvr, optional afp, optional ach, string mnm, string msm, butl::target_triplet trg) - : id (package_id (move (pnm), pvr), move (cfg), tvr), + : id (package_id (move (tnt), move (pnm), pvr), move (cfg), tvr), + tenant (id.package.tenant), package_name (id.package.name), package_version (move (pvr)), configuration (id.configuration), diff --git a/libbrep/build.hxx b/libbrep/build.hxx index c72269c..d3c2051 100644 --- a/libbrep/build.hxx +++ b/libbrep/build.hxx @@ -74,19 +74,25 @@ namespace brep template inline auto operator== (const T& x, const build_id& y) - -> decltype (x.package == y.package) + -> decltype (x.package == y.package && + x.configuration == y.configuration && + x.toolchain_version.epoch == y.toolchain_version.epoch) { - return x.package == y.package && x.configuration == y.configuration && - compare_version_eq (x.toolchain_version, y.toolchain_version, true); + return x.package == y.package && + x.configuration == y.configuration && + compare_version_eq (x.toolchain_version, y.toolchain_version, true); } template inline auto operator!= (const T& x, const build_id& y) - -> decltype (x.package == y.package) + -> decltype (x.package == y.package && + x.configuration == y.configuration && + x.toolchain_version.epoch == y.toolchain_version.epoch) { - return x.package != y.package || x.configuration != y.configuration || - compare_version_ne (x.toolchain_version, y.toolchain_version, true); + return x.package != y.package || + x.configuration != y.configuration || + compare_version_ne (x.toolchain_version, y.toolchain_version, true); } // build_state @@ -167,7 +173,9 @@ namespace brep // Create the build object with the building state, non-existent status, // the timestamp set to now and the force state set to unforced. // - build (package_name_type, version, + build (string tenant, + package_name_type, + version, string configuration, string toolchain_name, version toolchain_version, optional agent_fingerprint, @@ -177,6 +185,7 @@ namespace brep build_id id; + string& tenant; // Tracks id.package.tenant. package_name_type& package_name; // Tracks id.package.name. upstream_version package_version; // Original of id.package.version. string& configuration; // Tracks id.configuration. @@ -215,6 +224,7 @@ namespace brep // #pragma db member(id) id column("") + #pragma db member(tenant) transient #pragma db member(package_name) transient #pragma db member(package_version) \ set(this.package_version.init (this.id.package.version, (?))) @@ -232,20 +242,13 @@ namespace brep private: friend class odb::access; - build () - : package_name (id.package.name), configuration (id.configuration) {} - }; - - #pragma db view object(build) - struct build_count - { - size_t result; - operator size_t () const {return result;} - - // Database mapping. - // - #pragma db member(result) column("count(" + build::package_name + ")") + build () + : tenant (id.package.tenant), + package_name (id.package.name), + configuration (id.configuration) + { + } }; #pragma db view object(build) query(distinct) @@ -290,21 +293,21 @@ namespace brep // Note that ADL can't find the equal operator, so we use the function call // notation. // - #pragma db view \ - object(build) \ - object(build_package inner: \ - brep::operator== (build::id.package, build_package::id) && \ - build_package::internal_repository.is_not_null ()) + #pragma db view \ + object(build) \ + object(build_package inner: \ + brep::operator== (build::id.package, build_package::id) && \ + build_package::internal_repository.canonical_name.is_not_null ()) struct package_build { shared_ptr build; }; - #pragma db view \ - object(build) \ - object(build_package inner: \ - brep::operator== (build::id.package, build_package::id) && \ - build_package::internal_repository.is_not_null ()) + #pragma db view \ + object(build) \ + object(build_package inner: \ + brep::operator== (build::id.package, build_package::id) && \ + build_package::internal_repository.canonical_name.is_not_null ()) struct package_build_count { size_t result; diff --git a/libbrep/build.xml b/libbrep/build.xml index 0fd4154..0116374 100644 --- a/libbrep/build.xml +++ b/libbrep/build.xml @@ -1,6 +1,7 @@ + @@ -26,6 +27,7 @@ + @@ -39,6 +41,7 @@
+ @@ -54,6 +57,7 @@ + @@ -65,6 +69,7 @@ + @@ -78,6 +83,7 @@ + diff --git a/libbrep/common.hxx b/libbrep/common.hxx index 0163ca0..b9adee8 100644 --- a/libbrep/common.hxx +++ b/libbrep/common.hxx @@ -203,12 +203,14 @@ namespace brep #pragma db value struct package_id { + string tenant; package_name name; canonical_version version; package_id () = default; - package_id (package_name n, const brep::version& v) - : name (move (n)), + package_id (string t, package_name n, const brep::version& v) + : tenant (move (t)), + name (move (n)), version { v.epoch, v.canonical_upstream, v.canonical_release, v.revision} { @@ -252,6 +254,19 @@ namespace brep : (?).type ()}) \ from(brep::repository_location (std::move ((?).url), (?).type)) + // repository_id + // + #pragma db value + struct repository_id + { + string tenant; + string canonical_name; + + repository_id () = default; + repository_id (string t, string n) + : tenant (move (t)), canonical_name (move (n)) {} + }; + // Version comparison operators. // // They allow comparing objects that have epoch, canonical_upstream, @@ -381,35 +396,93 @@ namespace brep + x.revision + "DESC"; } + template + inline auto + order_by_version ( + const T& x, + bool first = true) -> //decltype ("ORDER BY" + x.epoch) + decltype (x.epoch == 0) + { + return (first ? "ORDER BY" : ", ") + + x.epoch + "," + + x.canonical_upstream + "," + + x.canonical_release + "," + + x.revision; + } + // Package id comparison operators. // inline bool operator< (const package_id& x, const package_id& y) { + if (int r = x.tenant.compare (y.tenant)) + return r < 0; + if (int r = x.name.compare (y.name)) return r < 0; return compare_version_lt (x.version, y.version, true); } - // They allow comparing objects that have name and version data members. The - // idea is that this works for both query members of package id types (in - // particular in join conditions) as well as for values of package_id type. + // They allow comparing objects that have tenant, name, and version data + // members. The idea is that this works for both query members of package id + // types (in particular in join conditions) as well as for values of + // package_id type. + // + template + inline auto + operator== (const T1& x, const T2& y) + -> decltype (x.tenant == y.tenant && + x.name == y.name && + x.version.epoch == y.version.epoch) + { + return x.tenant == y.tenant && + x.name == y.name && + compare_version_eq (x.version, y.version, true); + } + + template + inline auto + operator!= (const T1& x, const T2& y) + -> decltype (x.tenant == y.tenant && + x.name == y.name && + x.version.epoch == y.version.epoch) + { + return x.tenant != y.tenant || + x.name != y.name || + compare_version_ne (x.version, y.version, true); + } + + // Repository id comparison operators. + // + inline bool + operator< (const repository_id& x, const repository_id& y) + { + if (int r = x.tenant.compare (y.tenant)) + return r < 0; + + return x.canonical_name.compare (y.canonical_name) < 0; + } + + // They allow comparing objects that have tenant and canonical_name data + // members. The idea is that this works for both query members of repository + // id types (in particular in join conditions) as well as for values of + // repository_id type. // template inline auto operator== (const T1& x, const T2& y) - -> decltype (x.name == y.name && x.version.epoch == y.version.epoch) + -> decltype (x.tenant == y.tenant && x.canonical_name == y.canonical_name) { - return x.name == y.name && compare_version_eq (x.version, y.version, true); + return x.tenant == y.tenant && x.canonical_name == y.canonical_name; } template inline auto operator!= (const T1& x, const T2& y) - -> decltype (x.name == y.name && x.version.epoch == y.version.epoch) + -> decltype (x.tenant == y.tenant && x.canonical_name == y.canonical_name) { - return x.name != y.name || compare_version_ne (x.version, y.version, true); + return x.tenant != y.tenant || x.canonical_name != y.canonical_name; } } diff --git a/libbrep/package-extra.sql b/libbrep/package-extra.sql index bd5a27b..d9930aa 100644 --- a/libbrep/package-extra.sql +++ b/libbrep/package-extra.sql @@ -17,10 +17,15 @@ -- -- DROP FUNCTION IF EXISTS to_tsvector(IN document weighted_text); -- -DROP FUNCTION IF EXISTS search_packages(IN query tsquery, INOUT name CITEXT); -DROP FUNCTION IF EXISTS search_latest_packages(IN query tsquery); -DROP FUNCTION IF EXISTS latest_package(INOUT name CITEXT); -DROP FUNCTION IF EXISTS latest_packages(); +DROP FUNCTION IF EXISTS search_packages(IN query tsquery, + IN tenant TEXT, + IN name CITEXT); + +DROP FUNCTION IF EXISTS search_latest_packages(IN query tsquery, + IN tenant TEXT); + +DROP FUNCTION IF EXISTS latest_package(IN tenant TEXT, IN name CITEXT); +DROP FUNCTION IF EXISTS latest_packages(IN tenant TEXT); DROP TYPE IF EXISTS weighted_text CASCADE; CREATE TYPE weighted_text AS (a TEXT, b TEXT, c TEXT, d TEXT); @@ -28,12 +33,14 @@ CREATE TYPE weighted_text AS (a TEXT, b TEXT, c TEXT, d TEXT); -- Return the latest versions of internal packages as a set of package rows. -- CREATE FUNCTION -latest_packages() +latest_packages(IN tenant TEXT) RETURNS SETOF package AS $$ SELECT p1.* FROM package p1 LEFT JOIN package p2 ON ( - p1.internal_repository IS NOT NULL AND p1.name = p2.name AND - p2.internal_repository IS NOT NULL AND + p1.internal_repository_canonical_name IS NOT NULL AND + p1.tenant = p2.tenant AND + p1.name = p2.name AND + p2.internal_repository_canonical_name IS NOT NULL AND (p1.version_epoch < p2.version_epoch OR p1.version_epoch = p2.version_epoch AND (p1.version_canonical_upstream < p2.version_canonical_upstream OR @@ -42,23 +49,26 @@ RETURNS SETOF package AS $$ p1.version_canonical_release = p2.version_canonical_release AND p1.version_revision < p2.version_revision)))) WHERE - p1.internal_repository IS NOT NULL AND p2.name IS NULL; + p1.tenant = latest_packages.tenant AND + p1.internal_repository_canonical_name IS NOT NULL AND + p2.name IS NULL; $$ LANGUAGE SQL STABLE; --- Find the latest version of an internal package having the specified name. --- Return a single row containing the package id, empty row set if the package --- not found. +-- Find the latest version of an internal package having the specified tenant +-- and name. Return a single row containing the package id, empty row set if +-- the package not found. -- CREATE FUNCTION -latest_package(INOUT name CITEXT, +latest_package(INOUT tenant TEXT, + INOUT name CITEXT, OUT version_epoch INTEGER, OUT version_canonical_upstream TEXT, OUT version_canonical_release TEXT, OUT version_revision INTEGER) RETURNS SETOF record AS $$ - SELECT name, version_epoch, version_canonical_upstream, + SELECT tenant, name, version_epoch, version_canonical_upstream, version_canonical_release, version_revision - FROM latest_packages() + FROM latest_packages(latest_package.tenant) WHERE name = latest_package.name; $$ LANGUAGE SQL STABLE; @@ -69,6 +79,7 @@ $$ LANGUAGE SQL STABLE; -- CREATE FUNCTION search_latest_packages(IN query tsquery, + INOUT tenant TEXT, OUT name CITEXT, OUT version_epoch INTEGER, OUT version_canonical_upstream TEXT, @@ -76,23 +87,25 @@ search_latest_packages(IN query tsquery, OUT version_revision INTEGER, OUT rank real) RETURNS SETOF record AS $$ - SELECT name, version_epoch, version_canonical_upstream, + SELECT tenant, name, version_epoch, version_canonical_upstream, version_canonical_release, version_revision, CASE WHEN query IS NULL THEN 0 -- Weight mapping: D C B A ELSE ts_rank_cd('{0.05, 0.2, 0.9, 1.0}', search_index, query) END AS rank - FROM latest_packages() + FROM latest_packages(search_latest_packages.tenant) WHERE query IS NULL OR search_index @@ query; $$ LANGUAGE SQL STABLE; --- Search for packages matching the search query and having the specified name. --- Return a set of rows containing the package id and search rank. If query --- is NULL, then match all packages and return 0 rank for all rows. +-- Search for packages matching the search query and having the specified +-- tenant and name. Return a set of rows containing the package id and search +-- rank. If query is NULL, then match all packages and return 0 rank for all +-- rows. -- CREATE FUNCTION search_packages(IN query tsquery, + INOUT tenant TEXT, INOUT name CITEXT, OUT version_epoch INTEGER, OUT version_canonical_upstream TEXT, @@ -100,7 +113,7 @@ search_packages(IN query tsquery, OUT version_revision INTEGER, OUT rank real) RETURNS SETOF record AS $$ - SELECT name, version_epoch, version_canonical_upstream, + SELECT tenant, name, version_epoch, version_canonical_upstream, version_canonical_release, version_revision, CASE WHEN query IS NULL THEN 0 @@ -109,7 +122,9 @@ RETURNS SETOF record AS $$ END AS rank FROM package WHERE - internal_repository IS NOT NULL AND name = search_packages.name AND + tenant = search_packages.tenant AND + name = search_packages.name AND + internal_repository_canonical_name IS NOT NULL AND (query IS NULL OR search_index @@ query); $$ LANGUAGE SQL STABLE; diff --git a/libbrep/package.cxx b/libbrep/package.cxx index 6d4550d..41dd4e2 100644 --- a/libbrep/package.cxx +++ b/libbrep/package.cxx @@ -64,32 +64,32 @@ namespace brep optional fr, optional sh, shared_ptr rp) - : id (move (nm), vr), - version (move (vr)), - project (move (pn)), - priority (move (pr)), - summary (move (sm)), - license_alternatives (move (la)), - tags (move (tg)), - description (move (ds)), - changes (move (ch)), - url (move (ur)), - doc_url (move (du)), - src_url (move (su)), - package_url (move (pu)), - email (move (em)), - package_email (move (pe)), - build_email (move (be)), - dependencies (move (dp)), - requirements (move (rq)), - build_constraints ( - version.compare (wildcard_version, true) != 0 - ? move (bc) - : build_constraints_type ()), - internal_repository (move (rp)), - location (move (lc)), - fragment (move (fr)), - sha256sum (move (sh)) + : id (rp->tenant, move (nm), vr), + name (id.name), + version (move (vr)), + project (move (pn)), + priority (move (pr)), + summary (move (sm)), + license_alternatives (move (la)), + tags (move (tg)), + description (move (ds)), + changes (move (ch)), + url (move (ur)), + doc_url (move (du)), + src_url (move (su)), + package_url (move (pu)), + email (move (em)), + package_email (move (pe)), + build_email (move (be)), + dependencies (move (dp)), + requirements (move (rq)), + build_constraints (version.compare (wildcard_version, true) != 0 + ? move (bc) + : build_constraints_type ()), + internal_repository (move (rp)), + location (move (lc)), + fragment (move (fr)), + sha256sum (move (sh)) { assert (internal_repository->internal); } @@ -98,7 +98,8 @@ namespace brep package (package_name nm, version_type vr, shared_ptr rp) - : id (move (nm), vr), + : id (rp->tenant, move (nm), vr), + name (id.name), version (move (vr)) { assert (!rp->internal); @@ -121,7 +122,7 @@ namespace brep // Probably drop-box would be better as also tells what are // the available internal repositories. // - string k (project.string () + " " + id.name.string () + " " + + string k (project.string () + " " + name.string () + " " + version.string () + " " + version.string (true)); // Add tags to keywords. @@ -151,12 +152,15 @@ namespace brep // repository // repository:: - repository (repository_location l, + repository (string t, + repository_location l, string d, repository_location h, optional c, uint16_t r) - : name (l.canonical_name ()), + : id (move (t), l.canonical_name ()), + tenant (id.tenant), + canonical_name (id.canonical_name), location (move (l)), display_name (move (d)), priority (r), @@ -167,8 +171,10 @@ namespace brep } repository:: - repository (repository_location l) - : name (l.canonical_name ()), + repository (string t, repository_location l) + : id (move (t), l.canonical_name ()), + tenant (id.tenant), + canonical_name (id.canonical_name), location (move (l)), priority (0), internal (false) diff --git a/libbrep/package.hxx b/libbrep/package.hxx index 3d281b0..4dbde29 100644 --- a/libbrep/package.hxx +++ b/libbrep/package.hxx @@ -171,6 +171,8 @@ namespace brep #pragma db value(build_constraint) definition + // certificate + // #pragma db value class certificate { @@ -191,7 +193,8 @@ namespace brep // Create internal repository. // - repository (repository_location, + repository (string tenant, + repository_location, string display_name, repository_location cache_location, optional, @@ -200,9 +203,12 @@ namespace brep // Create external repository. // explicit - repository (repository_location); + repository (string tenant, repository_location); + + repository_id id; - string name; // Object id (canonical name). + const string& tenant; // Tracks id.tenant. + const string& canonical_name; // Tracks id.canonical_name. repository_location location; // Note: foreign-mapped in build. string display_name; @@ -243,21 +249,24 @@ namespace brep // Database mapping. // - #pragma db member(name) id + #pragma db member(id) id column("") - #pragma db member(location) \ - set(this.location = std::move (?); \ - assert (this.name == this.location.canonical_name ())) + #pragma db member(tenant) transient + #pragma db member(canonical_name) transient - #pragma db member(complements) id_column("repository") \ - value_column("complement") value_not_null + #pragma db member(location) \ + set(this.location = std::move (?); \ + assert (this.canonical_name == this.location.canonical_name ())) - #pragma db member(prerequisites) id_column("repository") \ - value_column("prerequisite") value_not_null + #pragma db member(complements) id_column("repository_") \ + value_column("complement_") value_not_null + + #pragma db member(prerequisites) id_column("repository_") \ + value_column("prerequisite_") value_not_null private: friend class odb::access; - repository () = default; + repository (): tenant (id.tenant), canonical_name (id.canonical_name) {} }; // The 'to' expression calls the PostgreSQL to_tsvector(weighted_text) @@ -336,6 +345,8 @@ namespace brep // Manifest data. // package_id id; + + const package_name& name; // Tracks id.name. upstream_version version; // Matches the package name if the project name is not specified in @@ -384,6 +395,7 @@ namespace brep // Database mapping. // #pragma db member(id) id column("") + #pragma db member(name) transient #pragma db member(version) set(this.version.init (this.id.version, (?))) // license @@ -453,7 +465,7 @@ namespace brep // other_repositories // #pragma db member(other_repositories) \ - id_column("") value_column("repository") value_not_null + id_column("") value_column("repository_") value_not_null // search_index // @@ -464,7 +476,7 @@ namespace brep private: friend class odb::access; - package () = default; + package (): name (id.name) {} // Save keywords, summary, description, and changes to weighted_text // a, b, c, d members, respectively. So a word found in keywords will diff --git a/libbrep/package.xml b/libbrep/package.xml index 55baae9..7b74349 100644 --- a/libbrep/package.xml +++ b/libbrep/package.xml @@ -1,7 +1,8 @@
- + + @@ -22,56 +23,72 @@ - + +
- + + - + + - + + - + + - + + - + + - + +
- + + - + + - + + - + + - + + - + + - + +
+ @@ -99,12 +116,14 @@ - + + + @@ -112,9 +131,11 @@ - + + - + + @@ -122,6 +143,7 @@
+ @@ -130,12 +152,14 @@ + + @@ -144,6 +168,7 @@ + @@ -155,6 +180,7 @@
+ @@ -164,12 +190,14 @@ + + @@ -178,6 +206,7 @@ + @@ -186,6 +215,7 @@
+ @@ -194,12 +224,14 @@ + + @@ -208,6 +240,7 @@ + @@ -219,6 +252,7 @@
+ @@ -229,12 +263,14 @@ + + @@ -243,6 +279,7 @@ + @@ -254,6 +291,7 @@
+ @@ -276,18 +314,21 @@ + + + @@ -296,6 +337,7 @@ + @@ -303,12 +345,14 @@ + + @@ -318,6 +362,7 @@
+ @@ -328,12 +373,14 @@ + + @@ -342,6 +389,7 @@ + @@ -353,6 +401,7 @@
+ @@ -362,12 +411,14 @@ + + @@ -376,6 +427,7 @@ + @@ -384,6 +436,7 @@
+ @@ -395,12 +448,14 @@ + + @@ -409,6 +464,7 @@ + @@ -420,20 +476,24 @@
+ - + + + + @@ -442,6 +502,7 @@ + @@ -452,9 +513,11 @@ - + + - + +
diff --git a/libbrep/types.hxx b/libbrep/types.hxx index 13d3d09..7c7b6ec 100644 --- a/libbrep/types.hxx +++ b/libbrep/types.hxx @@ -87,6 +87,8 @@ namespace brep using paths = std::vector; using dir_paths = std::vector; + using butl::path_cast; + // // using butl::system_clock; diff --git a/load/load.cli b/load/load.cli index bbe54ff..62c3204 100644 --- a/load/load.cli +++ b/load/load.cli @@ -52,6 +52,12 @@ class options repositories." }; + std::string --tenant + { + "Tenant the package and repository information should be loaded in. If not + specified, then the single-tenant mode is assumed." + }; + std::string --db-user|-u { "", diff --git a/load/load.cxx b/load/load.cxx index 01d77c2..24a3bf3 100644 --- a/load/load.cxx +++ b/load/load.cxx @@ -272,13 +272,16 @@ load_repositories (path p) // package model, including search related objects, settles down. // static bool -changed (const internal_repositories& repos, database& db) +changed (const string& tenant, + const internal_repositories& repos, + database& db) { strings names; for (auto& r: repos) { shared_ptr pr ( - db.find (r.location.canonical_name ())); + db.find (repository_id (tenant, + r.location.canonical_name ()))); if (pr == nullptr || r.location.string () != pr->location.string () || r.display_name != pr->display_name || @@ -298,8 +301,10 @@ changed (const internal_repositories& repos, database& db) // return !db.query ( - query::internal && - !query::name.in_range (names.begin (), names.end ())).empty (); + query::id.tenant == tenant && + query::internal && + !query::id.canonical_name.in_range (names.begin (), + names.end ())).empty (); } // Start 'bpkg rep-info [options] ' process. @@ -388,7 +393,7 @@ load_packages (const shared_ptr& rp, database& db) for (auto& pm: pms) { shared_ptr p ( - db.find (package_id (pm.name, pm.version))); + db.find (package_id (rp->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. @@ -515,7 +520,7 @@ load_packages (const shared_ptr& rp, database& db) if (!p->sha256sum) p->sha256sum = move (pm.sha256sum); else if (*pm.sha256sum != *p->sha256sum) - cerr << "warning: sha256sum mismatch for package " << p->id.name + cerr << "warning: sha256sum mismatch for package " << p->name << " " << p->version << endl << " info: " << p->internal_repository.load ()->location << " has " << *p->sha256sum << endl @@ -551,9 +556,12 @@ load_repositories (const shared_ptr& rp, // assert (!rp->cache_location.empty ()); + const string& tenant (rp->tenant); + // Repository is already persisted by the load_packages() function call. // - assert (db.find (rp->name) != nullptr); + assert (db.find ( + repository_id (tenant, rp->canonical_name)) != nullptr); pkg_repository_manifests rpm; @@ -680,16 +688,17 @@ load_repositories (const shared_ptr& rp, ? rp->prerequisites : rp->complements); - rs.emplace_back (db, cn); + rs.emplace_back (db, repository_id (tenant, cn)); - shared_ptr pr (db.find (cn)); + shared_ptr pr ( + db.find (repository_id (tenant, cn))); if (pr != nullptr) // The prerequisite repository is already loaded. // continue; - pr = make_shared (move (rl)); + pr = make_shared (tenant, move (rl)); // If the prerequsite repository location is a relative path, then // calculate its cache location. @@ -834,7 +843,7 @@ resolve_dependencies (package& p, database& db) if (d.package == nullptr) { cerr << "error: can't resolve dependency " << d << " of the package " - << p.id.name << " " << p.version << endl + << p.name << " " << p.version << endl << " info: repository " << p.internal_repository.load ()->location << " appears to be broken" << endl; @@ -886,7 +895,7 @@ detect_dependency_cycle (const package_id& id, ? p->internal_repository.load () : p->other_repositories[0].load ()); - cerr << id.name << " " << p->version << " (" << r->name << ")"; + cerr << p->name << " " << p->version << " (" << r->canonical_name << ")"; }; for (; i != chain.end (); ++i) @@ -1089,6 +1098,18 @@ try return 1; } + // By default the tenant is empty and assumes a single-tenant mode. Let's + // require the specified tenant to be non-empty. + // + const string& tenant (ops.tenant ()); + + if (ops.tenant_specified () && tenant.empty ()) + { + cerr << "error: empty tenant" << endl + << help_info << endl; + return 1; + } + odb::pgsql::database db ( ops.db_user (), ops.db_password (), @@ -1119,12 +1140,31 @@ try // internal_repositories irs (load_repositories (path (argv[1]))); - if (ops.force () || changed (irs, db)) + if (ops.force () || changed (tenant, irs, db)) { // Rebuild repositories persistent state from scratch. // - db.erase_query (); - db.erase_query (); + // 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. + // + if (tenant.empty ()) // Single-tenant mode. + { + db.erase_query (); + db.erase_query (); + } + else // Multi-tenant mode. + { + cstrings ts ({tenant.c_str (), ""}); + + db.erase_query ( + query::id.tenant.in_range (ts.begin (), ts.end ())); + + db.erase_query ( + query::id.tenant.in_range (ts.begin (), ts.end ())); + } // On the first pass over the internal repositories we load their // certificate information and packages. @@ -1141,7 +1181,8 @@ try ir.fingerprint); shared_ptr r ( - make_shared (ir.location, + make_shared (tenant, + ir.location, move (ir.display_name), move (ir.cache_location), move (cert), @@ -1157,7 +1198,8 @@ try for (const auto& ir: irs) { shared_ptr r ( - db.load (ir.location.canonical_name ())); + db.load (repository_id (tenant, + ir.location.canonical_name ()))); load_repositories (r, db, ops.shallow ()); } @@ -1170,14 +1212,18 @@ try using query = query; for (auto& p: - db.query (query::internal_repository.is_not_null ())) + db.query ( + query::id.tenant == tenant && + query::internal_repository.canonical_name.is_not_null ())) resolve_dependencies (p, db); // Make sure there is no package dependency cycles. // package_ids chain; for (const auto& p: - db.query (query::internal_repository.is_not_null ())) + db.query ( + query::id.tenant == tenant && + query::internal_repository.canonical_name.is_not_null ())) detect_dependency_cycle (p.id, chain, db); } } diff --git a/mod/build-config.cxx b/mod/build-config.cxx index 4913555..e838a59 100644 --- a/mod/build-config.cxx +++ b/mod/build-config.cxx @@ -14,6 +14,8 @@ #include +#include + namespace brep { using namespace std; @@ -117,7 +119,7 @@ namespace brep // needs to be url-encoded, and only in the query part of the URL. We embed // the package version into the URL path part and so don't encode it. // - string url (host + root.representation () + + string url (host + tenant_dir (root, b.tenant).representation () + mime_url_encode (b.package_name.string (), false) + '/' + b.package_version.string () + "/log/" + mime_url_encode (b.configuration, false) + '/' + @@ -140,7 +142,7 @@ namespace brep // we embed the package version into the URL query part, where it is not // encoded by design. // - return host + root.string () + + return host + tenant_dir (root, b.tenant).string () + "?build-force&pn=" + mime_url_encode (b.package_name.string ()) + "&pv=" + b.package_version.string () + "&cf=" + mime_url_encode (b.configuration) + @@ -148,7 +150,8 @@ namespace brep } bool - match (const string& config_pattern, const optional& target_pattern, + match (const string& config_pattern, + const optional& target_pattern, const build_config& c) { return path_match (config_pattern, c.name) && diff --git a/mod/mod-build-force.cxx b/mod/mod-build-force.cxx index b6514ce..ddc1301 100644 --- a/mod/mod-build-force.cxx +++ b/mod/mod-build-force.cxx @@ -122,7 +122,7 @@ handle (request& rq, response& rs) if (c.empty ()) throw invalid_argument ("no configuration name"); - id = build_id (package_id (move (p), package_version), + id = build_id (package_id (move (tenant), move (p), package_version), move (c), toolchain_version); } @@ -168,6 +168,7 @@ handle (request& rq, response& rs) build_db_->update (b); l1 ([&]{trace << "force rebuild for " + << b->tenant << ' ' << b->package_name << '/' << b->package_version << ' ' << b->configuration << ' ' << b->toolchain_name << '-' << b->toolchain_version diff --git a/mod/mod-build-log.cxx b/mod/mod-build-log.cxx index 57135b6..70e2c7e 100644 --- a/mod/mod-build-log.cxx +++ b/mod/mod-build-log.cxx @@ -78,13 +78,22 @@ handle (request& rq, response& rs) path lpath (rq.path ().leaf (options_->root ())); + // If the tenant is not empty then it is contained in the leftmost path + // component (see repository_root for details). Strip it if that's the case. + // + if (!tenant.empty ()) + { + assert (!lpath.empty ()); + lpath = path (++lpath.begin (), lpath.end ()); + } + + assert (!lpath.empty ()); + try { auto i (lpath.begin ()); - assert (i != lpath.end ()); package_name name; - try { name = package_name (*i++); @@ -127,7 +136,7 @@ handle (request& rq, response& rs) version toolchain_version (parse_version (*i++, "toolchain version")); - id = build_id (package_id (move (name), package_version), + id = build_id (package_id (tenant, move (name), package_version), move (config), toolchain_version); @@ -164,6 +173,7 @@ handle (request& rq, response& rs) auto config_expired = [&trace, &lpath, this] (const string& d) { l2 ([&]{trace << "package build configuration for " << lpath + << (!tenant.empty () ? "(" + tenant + ")" : "") << " expired: " << d;}); throw invalid_request (404, "package build configuration expired: " + d); @@ -204,6 +214,8 @@ handle (request& rq, response& rs) auto print_header = [&os, &b] () { + // @@ Should we print the tenant? How to call it if that's the case? + // os << "package: " << b->package_name << endl << "version: " << b->package_version << endl << "toolchain: " << b->toolchain_name << '-' << b->toolchain_version diff --git a/mod/mod-build-result.cxx b/mod/mod-build-result.cxx index 65e8425..1ba1bec 100644 --- a/mod/mod-build-result.cxx +++ b/mod/mod-build-result.cxx @@ -106,8 +106,8 @@ handle (request& rq, response&) } // Parse the task response session to obtain the build configuration name and - // the timestamp, and to make sure the session matches the result manifest's - // package name and version. + // the timestamp, and to make sure the session matches tenant and the result + // manifest's package name, and version. // build_id id; timestamp session_timestamp; @@ -116,9 +116,18 @@ handle (request& rq, response&) { const string& s (rqm.session); - size_t p (s.find ('/')); // End of package name. + size_t p (s.find ('/')); // End of tenant. - if (p == 0) + if (p == string::npos) + throw invalid_argument ("no package name"); + + if (tenant.compare (0, tenant.size (), s, 0, p) != 0) + throw invalid_argument ("tenant mismatch"); + + size_t b (p + 1); // Start of package name. + p = s.find ('/', b); // End of package name. + + if (p == b) throw invalid_argument ("empty package name"); if (p == string::npos) @@ -127,11 +136,11 @@ handle (request& rq, response&) package_name& name (rqm.result.name); { const string& n (name.string ()); - if (n.compare (0, n.size (), s, 0, p) != 0) + if (n.compare (0, n.size (), s, b, p - b) != 0) throw invalid_argument ("package name mismatch"); } - size_t b (p + 1); // Start of version. + b = p + 1; // Start of version. p = s.find ('/', b); // End of version. if (p == string::npos) @@ -172,7 +181,7 @@ handle (request& rq, response&) version toolchain_version (parse_version ("toolchain version")); - id = build_id (package_id (move (name), package_version), + id = build_id (package_id (move (tenant), move (name), package_version), move (config), toolchain_version); diff --git a/mod/mod-build-task.cxx b/mod/mod-build-task.cxx index 4e56d02..77652ce 100644 --- a/mod/mod-build-task.cxx +++ b/mod/mod-build-task.cxx @@ -188,13 +188,15 @@ handle (request& rq, response& rs) chrono::duration_cast ( b->timestamp.time_since_epoch ()).count ()); - string session (b->package_name.string () + '/' + - b->package_version.string () + - '/' + b->configuration + - '/' + b->toolchain_version.string () + - '/' + to_string (ts)); - - string result_url (options_->host () + options_->root ().string () + + string session (b->tenant + '/' + + b->package_name.string () + '/' + + b->package_version.string () + '/' + + b->configuration + '/' + + b->toolchain_version.string () + '/' + + to_string (ts)); + + string result_url (options_->host () + + tenant_dir (options_->root (), b->tenant).string () + "?build-result"); lazy_shared_ptr r (p->internal_repository); @@ -319,6 +321,10 @@ handle (request& rq, response& rs) // harmful in that: updates are infrequent and missed packages will be // picked up on the next request. // + // Also note that we disregard the request tenant and operate on the whole + // set of the packages and builds. In future we may add support for + // building packages for a specific tenant. + // using pkg_query = query; using prep_pkg_query = prepared_query; @@ -330,18 +336,17 @@ handle (request& rq, response& rs) if (!rp.empty ()) pq = pq && - pkg_query::build_repository::name.in_range (rp.begin (), rp.end ()); + pkg_query::build_repository::id.canonical_name.in_range (rp.begin (), + rp.end ()); // Specify the portion. // size_t offset (0); pq += "ORDER BY" + - pkg_query::build_package::id.name + "," + - pkg_query::build_package::id.version.epoch + "," + - pkg_query::build_package::id.version.canonical_upstream + "," + - pkg_query::build_package::id.version.canonical_release + "," + - pkg_query::build_package::id.version.revision + + pkg_query::build_package::id.tenant + "," + + pkg_query::build_package::id.name + + order_by_version (pkg_query::build_package::id.version, false) + "OFFSET" + pkg_query::_ref (offset) + "LIMIT 50"; connection_ptr conn (build_db_->connection ()); @@ -368,20 +373,23 @@ handle (request& rq, response& rs) const auto& qv (bld_query::id.package.version); bld_query bq ( - bld_query::id.package.name == bld_query::_ref (id.name) && + bld_query::id.package.tenant == bld_query::_ref (id.tenant) && + + bld_query::id.package.name == bld_query::_ref (id.name) && - qv.epoch == bld_query::_ref (id.version.epoch) && + qv.epoch == bld_query::_ref (id.version.epoch) && qv.canonical_upstream == - bld_query::_ref (id.version.canonical_upstream) && - qv.canonical_release == bld_query::_ref (id.version.canonical_release) && - qv.revision == bld_query::_ref (id.version.revision) && + bld_query::_ref (id.version.canonical_upstream) && + qv.canonical_release == + bld_query::_ref (id.version.canonical_release) && + qv.revision == bld_query::_ref (id.version.revision) && bld_query::id.configuration.in_range (cfg_names.begin (), - cfg_names.end ()) && + cfg_names.end ()) && compare_version_eq (bld_query::id.toolchain_version, toolchain_version, - true) && + true) && (bld_query::state == "built" || ((bld_query::force == "forcing" && @@ -467,13 +475,14 @@ handle (request& rq, response& rs) shared_ptr b (build_db_->find (bid)); optional cl (challenge ()); - // If build configuration doesn't exist then create the new one and - // persist. Otherwise put it into the building state, refresh the - // timestamp and update. + // If build configuration doesn't exist then create the new one + // and persist. Otherwise put it into the building state, refresh + // the timestamp and update. // if (b == nullptr) { - b = make_shared (move (bid.package.name), + b = make_shared (move (bid.package.tenant), + move (bid.package.name), move (bp.version), move (bid.configuration), move (tqm.toolchain_name), @@ -493,9 +502,9 @@ handle (request& rq, response& rs) // // Note that in both cases we keep the status intact to be able // to compare it with the final one in the result request - // handling in order to decide if to send the notification email. - // The same is true for the forced flag (in the sense that we - // don't set the force state to unforced). + // handling in order to decide if to send the notification + // email. The same is true for the forced flag (in the sense + // that we don't set the force state to unforced). // // Load the section to assert the above statement. // @@ -558,8 +567,7 @@ handle (request& rq, response& rs) // 2: overall status // 3: timestamp (less is preferred) // - auto cmp = [] (const shared_ptr& x, - const shared_ptr& y) -> bool + auto cmp = [] (const shared_ptr& x, const shared_ptr& y) { if (x->force != y->force) return x->force > y->force; // Forced goes first. @@ -611,7 +619,8 @@ handle (request& rq, response& rs) shared_ptr p ( build_db_->find (b->id.package)); - if (p != nullptr && p->internal_repository != nullptr && + if (p != nullptr && + p->internal_repository != nullptr && !exclude (*p, *cm.config)) { assert (b->status); diff --git a/mod/mod-builds.cxx b/mod/mod-builds.cxx index 19d187c..d2e3a25 100644 --- a/mod/mod-builds.cxx +++ b/mod/mod-builds.cxx @@ -92,7 +92,9 @@ transform (const string& s) template static inline query -build_query (const brep::cstrings& configs, const brep::params::builds& params) +build_query (const brep::cstrings& configs, + const brep::params::builds& params, + const brep::optional& tenant) { using namespace brep; using query = query; @@ -102,6 +104,11 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) ? qb::id.configuration.in_range (configs.begin (), configs.end ()) : query (true)); + const auto& pid (qb::id.package); + + if (tenant) + q = q && pid.tenant == *tenant; + // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no package builds match such a // query. @@ -111,13 +118,13 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) // Package name. // if (!params.name ().empty ()) - q = q && qb::id.package.name.like ( - package_name (transform (params.name ()), package_name::raw_string)); + q = q && pid.name.like (package_name (transform (params.name ()), + package_name::raw_string)); // Package version. // if (!params.version ().empty () && params.version () != "*") - q = q && compare_version_eq (qb::id.package.version, + q = q && compare_version_eq (pid.version, version (params.version ()), // May throw. true); @@ -200,12 +207,12 @@ build_query (const brep::cstrings& configs, const brep::params::builds& params) template ::build_package> static inline query -package_query (const brep::params::builds& params) +package_query (const brep::params::builds& params, const string& tenant) { using namespace brep; using query = query; - query q (true); + query q (P::id.tenant == tenant); // Note that there is no error reported if the filter parameters parsing // fails. Instead, it is considered that no packages match such a query. @@ -240,22 +247,24 @@ package_id_eq (const ID& x, const brep::package_id& y) using query = query; const auto& qv (x.version); - return x.name == query::_ref (y.name) && - qv.epoch == query::_ref (y.version.epoch) && + return + x.tenant == query::_ref (y.tenant) && + x.name == query::_ref (y.name) && + qv.epoch == query::_ref (y.version.epoch) && qv.canonical_upstream == query::_ref (y.version.canonical_upstream) && - qv.canonical_release == query::_ref (y.version.canonical_release) && + qv.canonical_release == query::_ref (y.version.canonical_release) && qv.revision == query::_ref (y.version.revision); } static const vector> build_results ({ - {"unbuilt", ""}, - {"*", "*"}, - {"pending", "pending"}, + {"unbuilt", ""}, + {"*", "*"}, + {"pending", "pending"}, {"building", "building"}, - {"success", "success"}, - {"warning", "warning"}, - {"error", "error"}, - {"abort", "abort"}, + {"success", "success"}, + {"warning", "warning"}, + {"error", "error"}, + {"abort", "abort"}, {"abnormal", "abnormal"}}); bool brep::builds:: @@ -309,7 +318,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); // Return the list of distinct toolchain name/version pairs. The build db @@ -323,6 +332,7 @@ handle (request& rq, response& rs) toolchains r; for (auto& t: build_db_->query ( + (query::id.package.tenant == tenant) + "ORDER BY" + query::toolchain_name + order_by_version_desc (query::id.toolchain_version, false))) r.emplace_back (move (t.name), move (t.version)); @@ -356,11 +366,11 @@ handle (request& rq, response& rs) } // The 'action' attribute is optional in HTML5. While the standard - // doesn't specify browser behavior explicitly for the case the attribute - // is omitted, the only reasonable behavior is to default it to the - // current document URL. Note that we specify the function name using the - // "hidden" element since the action url must not contain the - // query part. + // doesn't specify browser behavior explicitly for the case the + // attribute is omitted, the only reasonable behavior is to default it + // to the current document URL. Note that we specify the function name + // using the "hidden" element since the action url must not + // contain the query part. // s << FORM << TABLE(ID="filter", CLASS="proplist") @@ -418,7 +428,7 @@ handle (request& rq, response& rs) transaction t (build_db_->begin ()); count = build_db_->query_value ( - build_query (*build_conf_names_, params)); + build_query (*build_conf_names_, params, tenant)); // Print the filter form. // @@ -433,8 +443,8 @@ handle (request& rq, response& rs) // s << DIV; for (auto& pb: build_db_->query ( - build_query (*build_conf_names_, params) + - "ORDER BY" + query::timestamp + "DESC" + + build_query (*build_conf_names_, params, tenant) + + "ORDER BY" + query::build::timestamp + "DESC" + "OFFSET" + to_string (page * page_configs) + "LIMIT" + to_string (page_configs))) { @@ -451,8 +461,8 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist build") << TBODY - << TR_NAME (b.package_name, string (), root) - << TR_VERSION (b.package_name, b.package_version, root) + << TR_NAME (b.package_name, string (), root, tenant) + << TR_VERSION (b.package_name, b.package_version, root, tenant) << TR_VALUE ("toolchain", b.toolchain_name + '-' + b.toolchain_version.string ()) @@ -470,8 +480,8 @@ handle (request& rq, response& rs) } else // Print unbuilt package configurations. { - // Parameters to use for package build configurations queries. Note that we - // cleanup the machine and the result filter arguments, as they are + // Parameters to use for package build configurations queries. Note that + // we cleanup the machine and the result filter arguments, as they are // irrelevant for unbuilt configurations. // params::builds bld_params (params); @@ -506,8 +516,8 @@ handle (request& rq, response& rs) } }; - // Note that config_toolchains contains shallow references to the toolchain - // names and versions. + // Note that config_toolchains contains shallow references to the + // toolchain names and versions. // set config_toolchains; { @@ -576,12 +586,15 @@ handle (request& rq, response& rs) // due to the build configuration target change. We should deduct such // builds count from the number of existing package builds. // - size_t nmax (config_toolchains.size () * - build_db_->query_value ( - package_query (params))); + size_t nmax ( + config_toolchains.size () * + build_db_->query_value ( + package_query (params, tenant))); size_t ncur = build_db_->query_value ( - build_query (*build_conf_names_, bld_params)); + build_query (*build_conf_names_, + bld_params, + tenant)); // From now we will be using specific package name and version for each // build database query. @@ -602,11 +615,18 @@ handle (request& rq, response& rs) package_id id; string config; + const auto& bid (bld_query::build::id); + bld_query bq ( - package_id_eq ( - bld_query::build::id.package, id) && - bld_query::build::id.configuration == bld_query::_ref (config) && - build_query (cstrings (), bld_params)); + package_id_eq (bid.package, id) && + bid.configuration == bld_query::_ref (config) && + + // Note that the query already constrains the tenant via the build + // package id. + // + build_query (cstrings () /* configs */, + bld_params, + nullopt /* tenant */)); prep_bld_query bld_prep_query ( build_db_->prepare_query ( @@ -620,7 +640,8 @@ handle (request& rq, response& rs) // form parameters. // using query = query; - query q (package_query (params)); + query q (package_query (params, + tenant)); for (const auto& p: build_db_->query (q)) { @@ -675,13 +696,15 @@ handle (request& rq, response& rs) using pkg_query = query; using prep_pkg_query = prepared_query; - pkg_query pq (package_query (params)); + pkg_query pq (package_query (params, tenant)); // Specify the portion. Note that we will still be querying packages in // chunks, not to hold locks for too long. // size_t offset (0); + // @@ TENANT: use tenant for sorting when add support for global view. + // pq += "ORDER BY" + pkg_query::build_package::id.name + order_by_version_desc (pkg_query::build_package::id.version, false) + @@ -706,7 +729,13 @@ handle (request& rq, response& rs) bld_query bq ( package_id_eq (bld_query::build::id.package, id) && - build_query (*build_conf_names_, bld_params)); + + // Note that the query already constrains the tenant via the build + // package id. + // + build_query (*build_conf_names_, + bld_params, + nullopt /* tenant */)); prep_bld_query bld_prep_query ( conn->prepare_query ("mod-builds-build-query", bq)); @@ -805,8 +834,8 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist build") << TBODY - << TR_NAME (id.name, string (), root) - << TR_VERSION (id.name, p.version, root) + << TR_NAME (id.name, string (), root, tenant) + << TR_VERSION (id.name, p.version, root, tenant) << TR_VALUE ("toolchain", string (ct.toolchain_name) + '-' + ct.toolchain_version.string ()) @@ -829,7 +858,7 @@ handle (request& rq, response& rs) s << ~DIV; } - string u (root.string () + "?builds"); + string u (tenant_dir (root, tenant).string () + "?builds"); if (!params.name ().empty ()) { diff --git a/mod/mod-ci.cxx b/mod/mod-ci.cxx index 79472d0..e16a1a1 100644 --- a/mod/mod-ci.cxx +++ b/mod/mod-ci.cxx @@ -182,7 +182,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("ci.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content") << *form_ << ~DIV << ~BODY << ~HTML; diff --git a/mod/mod-package-details.cxx b/mod/mod-package-details.cxx index 3db9e1f..6ec0b0f 100644 --- a/mod/mod-package-details.cxx +++ b/mod/mod-package-details.cxx @@ -50,7 +50,9 @@ init (scanner& s) template static inline query -search_params (const brep::package_name& n, const brep::string& q) +search_params (const brep::string& q, + const brep::string& t, + const brep::package_name& n) { using query = query; @@ -59,6 +61,8 @@ search_params (const brep::package_name& n, const brep::string& q) ? query ("NULL") : "plainto_tsquery (" + query::_val (q) + ")") + "," + + query::_val (t) + + "," + query::_val (n) + ")"; } @@ -100,12 +104,13 @@ handle (request& rq, response& rs) try { + using query = query; + package_name n (*rq.path ().rbegin ()); latest_package lp; if (!package_db_->query_one ( - query ("(" + query::_val (n) + ")"), - lp)) + "(" + query::_val (tenant) + "," + query::_val (n) + ")", lp)) throw invalid_request (404, "Package '" + n.string () + "' not found"); pkg = package_db_->load (lp.id); @@ -115,7 +120,7 @@ handle (request& rq, response& rs) throw invalid_request (400, "invalid package name format"); } - const package_name& name (pkg->id.name); + const package_name& name (pkg->name); const string ename (mime_url_encode (name.string (), false)); auto url = [&ename] (bool f = false, @@ -156,7 +161,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); if (full) @@ -187,7 +192,7 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist", ID="package") << TBODY << TR_LICENSE (licenses) - << TR_PROJECT (pkg->project, root); + << TR_PROJECT (pkg->project, root, tenant); if (pkg->url) s << TR_URL (*pkg->url); @@ -201,14 +206,14 @@ handle (request& rq, response& rs) if (pkg->email) s << TR_EMAIL (*pkg->email); - s << TR_TAGS (pkg->tags, root) + s << TR_TAGS (pkg->tags, root, tenant) << ~TBODY << ~TABLE; } auto pkg_count ( package_db_->query_value ( - search_params (name, squery))); + search_params (squery, tenant, name))); s << FORM_SEARCH (squery) << DIV_COUNTER (pkg_count, "Version", "Versions"); @@ -218,7 +223,7 @@ handle (request& rq, response& rs) s << DIV; for (const auto& pr: package_db_->query ( - search_params (name, squery) + + search_params (squery, tenant, name) + "ORDER BY rank DESC, version_epoch DESC, " "version_canonical_upstream DESC, version_canonical_release DESC, " "version_revision DESC" + @@ -229,7 +234,7 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist version") << TBODY - << TR_VERSION (name, p->version, root) + << TR_VERSION (name, p->version, root, tenant) // @@ Shouldn't we skip low priority row ? Don't think so, why? // @@ -256,8 +261,11 @@ handle (request& rq, response& rs) // // Hm, I am not so sure about this. Consider: stable/testing/unstable. // - s << TR_REPOSITORY (p->internal_repository.object_id (), root) - << TR_DEPENDS (p->dependencies, root) + s << TR_REPOSITORY ( + p->internal_repository.object_id ().canonical_name, + root, + tenant) + << TR_DEPENDS (p->dependencies, root, tenant) << TR_REQUIRES (p->requirements) << ~TBODY << ~TABLE; diff --git a/mod/mod-package-search.cxx b/mod/mod-package-search.cxx index a86e5a7..347abf1 100644 --- a/mod/mod-package-search.cxx +++ b/mod/mod-package-search.cxx @@ -67,13 +67,15 @@ init (scanner& s) template static inline query -search_param (const brep::string& q) +search_param (const brep::string& q, const brep::string& t) { using query = query; return "(" + (q.empty () ? query ("NULL") : "plainto_tsquery (" + query::_val (q) + ")") + + "," + + query::_val (t) + ")"; } @@ -133,7 +135,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); session sn; @@ -141,17 +143,19 @@ handle (request& rq, response& rs) auto pkg_count ( package_db_->query_value ( - search_param (squery))); + search_param (squery, tenant))); s << FORM_SEARCH (squery) << DIV_COUNTER (pkg_count, "Package", "Packages"); // Enclose the subsequent tables to be able to use nth-child CSS selector. // + // @@ TENANT: use tenant for sorting when add support for global view. + // s << DIV; for (const auto& pr: package_db_->query ( - search_param (squery) + + search_param (squery, tenant) + "ORDER BY rank DESC, name" + "OFFSET" + to_string (page * res_page) + "LIMIT" + to_string (res_page))) @@ -160,11 +164,11 @@ handle (request& rq, response& rs) s << TABLE(CLASS="proplist package") << TBODY - << TR_NAME (p->id.name, squery_param, root) + << TR_NAME (p->name, squery_param, root, tenant) << TR_SUMMARY (p->summary) << TR_LICENSE (p->license_alternatives) - << TR_TAGS (p->project, p->tags, root) - << TR_DEPENDS (p->dependencies, root) + << TR_TAGS (p->project, p->tags, root, tenant) + << TR_DEPENDS (p->dependencies, root, tenant) << TR_REQUIRES (p->requirements) << ~TBODY << ~TABLE; @@ -174,7 +178,7 @@ handle (request& rq, response& rs) t.commit (); s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), - root.string () + squery_param) + tenant_dir (root, tenant).string () + squery_param) << ~DIV << ~BODY << ~HTML; diff --git a/mod/mod-package-version-details.cxx b/mod/mod-package-version-details.cxx index d2d96d2..ee7457a 100644 --- a/mod/mod-package-version-details.cxx +++ b/mod/mod-package-version-details.cxx @@ -131,7 +131,7 @@ handle (request& rq, response& rs) try { - pkg = package_db_->load (package_id (pn, ver)); + pkg = package_db_->load (package_id (tenant, pn, ver)); // If the requested package turned up to be an "external" one just // respond that no "internal" package is present. @@ -147,7 +147,7 @@ handle (request& rq, response& rs) throw invalid_request ( 404, "Package '" + pn.string () + ' ' + sver + "' not found"); - const string& name (pkg->id.name.string ()); + const string& name (pkg->name.string ()); const string title (name + " " + sver); xml::serializer s (rs.content (), title); @@ -158,7 +158,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("package-version-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); if (full) @@ -166,7 +166,8 @@ handle (request& rq, response& rs) s << DIV(ID="heading") << H1 - << A(HREF=root / path (mime_url_encode (name, false))) + << A(HREF=tenant_dir (root, tenant) / + path (mime_url_encode (name, false))) << name << ~A << "/" @@ -195,7 +196,7 @@ handle (request& rq, response& rs) << TR_PRIORITY (pkg->priority) << TR_LICENSES (pkg->license_alternatives) - << TR_REPOSITORY (rl.canonical_name (), root) + << TR_REPOSITORY (rl.canonical_name (), root, tenant) << TR_LOCATION (rl); if (rl.type () == repository_type::pkg) @@ -216,7 +217,7 @@ handle (request& rq, response& rs) << TABLE(CLASS="proplist", ID="package") << TBODY - << TR_PROJECT (pkg->project, root); + << TR_PROJECT (pkg->project, root, tenant); const auto& u (pkg->url); @@ -246,7 +247,7 @@ handle (request& rq, response& rs) if (be && ((pe && be != pe) || (!pe && be != em))) s << TR_EMAIL (*be, "build-email"); - s << TR_TAGS (pkg->tags, root) + s << TR_TAGS (pkg->tags, root, tenant) << ~TBODY << ~TABLE; @@ -306,7 +307,7 @@ handle (request& rq, response& rs) } else if (p->internal ()) { - dir_path u (root / dir_path (ename)); + dir_path u (tenant_dir (root, tenant) / dir_path (ename)); s << A(HREF=u) << dname << ~A; if (dcon) diff --git a/mod/mod-repository-details.cxx b/mod/mod-repository-details.cxx index 36d5508..6d3b8c1 100644 --- a/mod/mod-repository-details.cxx +++ b/mod/mod-repository-details.cxx @@ -82,7 +82,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("repository-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content"); transaction t (package_db_->begin ()); @@ -91,13 +91,14 @@ handle (request& rq, response& rs) for (const auto& r: package_db_->query ( - query::internal + "ORDER BY" + query::priority)) + (query::internal && query::id.tenant == tenant) + + "ORDER BY" + query::priority)) { //@@ Feels like a lot of trouble (e.g., id_attribute()) for very // dubious value. A link to the package search page just for // this repository would probably be more useful. // - string id (html_id (r.name)); + string id (html_id (r.canonical_name)); s << H1(ID=id) << A(HREF="#" + web::mime_url_encode (id, false)) << r.display_name @@ -144,9 +145,9 @@ handle (request& rq, response& rs) // s << P << "REPOSITORY CERTIFICATE" << ~P << P - << "CN=" << cert.name.c_str () + np + 1 << *BR - << "O=" << cert.organization << *BR - << email (cert.email) + << "CN=" << cert.name.c_str () + np + 1 << *BR + << "O=" << cert.organization << *BR + << email (cert.email) << ~P << P(CLASS="certfp") << cert.fingerprint << ~P << PRE(CLASS="certpem") << cert.pem << ~PRE; diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx index 3b0ab1f..b8777fd 100644 --- a/mod/mod-repository-root.cxx +++ b/mod/mod-repository-root.cxx @@ -275,13 +275,27 @@ namespace brep if (!rpath.sub (root)) return false; - const path& lpath (rpath.leaf (root)); + path lpath (rpath.leaf (root)); + + if (!lpath.empty ()) + { + path::iterator i (lpath.begin ()); + const string& s (*i); + + if (s[0] == '@' && s.size () > 1) + { + tenant = string (s, 1); + lpath = path (++i, lpath.end ()); + } + } // Delegate the request handling to the selected sub-handler. Intercept // exception handling to add sub-handler attribution. // - auto handle = [&rq, &rs, this] (const char* nm, bool fn = false) -> bool + auto handle = [&rq, &rs, this] (const char* nm, bool fn = false) { + handler_->tenant = move (tenant); + try { // Delegate the handling straight away if the sub-handler is not a diff --git a/mod/mod-submit.cxx b/mod/mod-submit.cxx index 470bd45..1b93756 100644 --- a/mod/mod-submit.cxx +++ b/mod/mod-submit.cxx @@ -201,7 +201,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("submit.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, options_->logo (), options_->menu ()) + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) << DIV(ID="content") << *form_ << ~DIV << ~BODY << ~HTML; diff --git a/mod/module.hxx b/mod/module.hxx index 127cdab..25dce43 100644 --- a/mod/module.hxx +++ b/mod/module.hxx @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -70,6 +71,13 @@ namespace brep // class handler: public web::handler { + public: + // If not empty, denotes the repository tenant the request is for. + // Extracted by the handler implementation from the request (URL path, + // parameters, etc). + // + string tenant; + // Diagnostics. // protected: @@ -89,7 +97,7 @@ namespace brep // Set to true when the handler is successfully initialized. // - bool initialized_ {false}; + bool initialized_ = false; // Implementation details. // diff --git a/mod/page.cxx b/mod/page.cxx index 46b5e71..46f4879 100644 --- a/mod/page.cxx +++ b/mod/page.cxx @@ -18,6 +18,7 @@ #include #include +#include #include // build_log_url() using namespace std; @@ -61,11 +62,14 @@ namespace brep s << DIV(ID="header-menu") << DIV(ID="header-menu-body"); + dir_path root (tenant_dir (root_, tenant_)); + for (const auto& m: menu_) { - const string& l (m.link[0] == '/' || m.link.find (':') != string::npos - ? m.link - : root_.string () + m.link); + const string& l ( + m.link[0] == '/' || m.link.find (':') != string::npos + ? m.link + : root.string () + m.link); s << A(HREF=l) << m.label << ~A; } @@ -191,7 +195,8 @@ namespace brep // Propagate search criteria to the package details page. // - << root_ / path (mime_url_encode (name_.string (), false)) + << tenant_dir (root_, tenant_) / + path (mime_url_encode (name_.string (), false)) << query_param_ << ~HREF @@ -221,8 +226,9 @@ namespace brep } else { - assert (root_ != nullptr); - s << A(HREF=*root_ / + assert (root_ != nullptr && tenant_ != nullptr); + + s << A(HREF=tenant_dir (*root_, *tenant_) / dir_path (mime_url_encode (package_->string (), false)) / path (version_)) << version_; @@ -249,7 +255,8 @@ namespace brep << SPAN(CLASS="value") << A << HREF - << root_ << "?q=" << mime_url_encode (project_.string ()) + << tenant_dir (root_, tenant_) << "?q=" + << mime_url_encode (project_.string ()) << ~HREF << project_ << ~A @@ -347,7 +354,10 @@ namespace brep auto print = [&s, this] (const string& t) { - s << A << HREF << root_ << "?q=" << mime_url_encode (t) << ~HREF + s << A + << HREF + << tenant_dir (root_, tenant_) << "?q=" << mime_url_encode (t) + << ~HREF << t << ~A; }; @@ -439,7 +449,7 @@ namespace brep if (r->interface_url) s << A(HREF=*r->interface_url + en) << n << ~A; else if (p->internal ()) - s << A(HREF=root_ / path (en)) << n << ~A; + s << A(HREF=tenant_dir (root_, tenant_) / path (en)) << n << ~A; else // Display the dependency as plain text if no repository URL // available. @@ -586,7 +596,8 @@ namespace brep << SPAN(CLASS="value") << A << HREF - << root_ << "?about#" << mime_url_encode (html_id (name_), false) + << tenant_dir (root_, tenant_) << "?about#" + << mime_url_encode (html_id (name_), false) << ~HREF << name_ << ~A diff --git a/mod/page.hxx b/mod/page.hxx index 759984e..d3e10db 100644 --- a/mod/page.hxx +++ b/mod/page.hxx @@ -44,18 +44,20 @@ namespace brep class DIV_HEADER { public: - DIV_HEADER (const dir_path& root, - const web::xhtml::fragment& logo, - const vector& menu): - root_ (root), logo_ (logo), menu_ (menu) {} + DIV_HEADER (const web::xhtml::fragment& logo, + const vector& menu, + const dir_path& root, + const string& tenant): + logo_ (logo), menu_ (menu), root_ (root), tenant_ (tenant) {} void operator() (xml::serializer&) const; private: - const dir_path& root_; const web::xhtml::fragment& logo_; const vector& menu_; + const dir_path& root_; + const string& tenant_; }; // Generates package search form element. @@ -163,8 +165,11 @@ namespace brep class TR_NAME { public: - TR_NAME (const package_name& n, const string& q, const dir_path& r) - : name_ (n), query_param_ (q), root_ (r) {} + TR_NAME (const package_name& n, + const string& q, + const dir_path& r, + const string& t) + : name_ (n), query_param_ (q), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; @@ -173,6 +178,7 @@ namespace brep const package_name& name_; const string& query_param_; const dir_path& root_; + const string& tenant_; }; // Generates package version element. @@ -182,11 +188,15 @@ namespace brep public: // Display the version as a link to the package version details page. // - TR_VERSION (const package_name& p, const version& v, const dir_path& r) + TR_VERSION (const package_name& p, + const version& v, + const dir_path& r, + const string& t) : package_ (&p), version_ (v.string ()), stub_ (v.compare (wildcard_version, true) == 0), - root_ (&r) + root_ (&r), + tenant_ (&t) { } @@ -196,7 +206,8 @@ namespace brep : package_ (nullptr), version_ (v.string ()), stub_ (v.compare (wildcard_version, true) == 0), - root_ (nullptr) + root_ (nullptr), + tenant_ (nullptr) { } @@ -208,6 +219,7 @@ namespace brep string version_; bool stub_; const dir_path* root_; + const string* tenant_; }; // Generates package project name element. @@ -218,8 +230,8 @@ namespace brep class TR_PROJECT { public: - TR_PROJECT (const package_name& p, const dir_path& r) - : project_ (p), root_ (r) {} + TR_PROJECT (const package_name& p, const dir_path& r, const string& t) + : project_ (p), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; @@ -227,6 +239,7 @@ namespace brep private: const package_name& project_; const dir_path& root_; + const string& tenant_; }; // Generates package summary element. @@ -279,14 +292,17 @@ namespace brep public: // Display the tag link list. // - TR_TAGS (const strings& ts, const dir_path& r) - : project_ (nullptr), tags_ (ts), root_ (r) {} + TR_TAGS (const strings& ts, const dir_path& r, const string& t) + : project_ (nullptr), tags_ (ts), root_ (r), tenant_ (t) {} // As above but prepend the list with a tag link produced from the project // name, if it differs from other tags. // - TR_TAGS (const package_name& pr, const strings& ts, const dir_path& r) - : project_ (&pr), tags_ (ts), root_ (r) {} + TR_TAGS (const package_name& pr, + const strings& ts, + const dir_path& r, + const string& t) + : project_ (&pr), tags_ (ts), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; @@ -295,6 +311,7 @@ namespace brep const package_name* project_; const strings& tags_; const dir_path& root_; + const string& tenant_; }; // Generates package dependencies element. @@ -302,8 +319,8 @@ namespace brep class TR_DEPENDS { public: - TR_DEPENDS (const dependencies& d, const dir_path& r) - : dependencies_ (d), root_ (r) {} + TR_DEPENDS (const dependencies& d, const dir_path& r, const string& t) + : dependencies_ (d), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; @@ -311,6 +328,7 @@ namespace brep private: const dependencies& dependencies_; const dir_path& root_; + const string& tenant_; }; // Generates package requirements element. @@ -377,8 +395,8 @@ namespace brep class TR_REPOSITORY { public: - TR_REPOSITORY (const string& n, const dir_path& r) - : name_ (n), root_ (r) {} + TR_REPOSITORY (const string& n, const dir_path& r, const string& t) + : name_ (n), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; @@ -386,6 +404,7 @@ namespace brep private: const string& name_; const dir_path& root_; + const string& tenant_; }; // Generates repository location element. diff --git a/mod/utility.hxx b/mod/utility.hxx new file mode 100644 index 0000000..2ce1e07 --- /dev/null +++ b/mod/utility.hxx @@ -0,0 +1,25 @@ +// file : mod/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_UTILITY_HXX +#define MOD_UTILITY_HXX + +#include +#include + +namespace brep +{ + // Append the @ leaf component to the directory if the tenant is + // not empty. Otherwise, return the directory unchanged. + // + inline dir_path + tenant_dir (const dir_path& dir, const string& tenant) + { + return !tenant.empty () + ? path_cast (dir / ('@' + tenant)) + : dir; + } +} + +#endif // MOD_UTILITY_HXX diff --git a/tests/ci/ci-load.testscript b/tests/ci/ci-load.testscript index de23b79..c574a32 100644 --- a/tests/ci/ci-load.testscript +++ b/tests/ci/ci-load.testscript @@ -55,12 +55,14 @@ cat <<"EOI" >=$loader; #!/usr/bin/env bash - if [ "\$#" != 5 -o \ + if [ "\$#" != 7 -o \ "\$1" != "--db-host=localhost" -o \ "\$2" != "--db-port=8432" -o \ "\$3" != "--force" -o \ "\$4" != "--shallow" -o \ - "\$5" != "$data_dir/loadtab" -o \ + "\$5" != "--tenant" -o \ + -z "\$6" -o \ + "\$7" != "$data_dir/loadtab" -o \ ! -f "$data_dir/cache/repositories.manifest" -o \ ! -f "$data_dir/cache/packages.manifest" ]; then exit 1 @@ -74,7 +76,7 @@ $clone_root_data; - $* $loader --db-host=localhost --db-port=8432 $data_dir >>"EOO" + $* $loader --db-host=localhost --db-port=8432 $data_dir >>"EOO"; : 1 status: 200 message: CI request is queued @@ -85,10 +87,11 @@ $* --result-url "http://example.com/" \ $loader --db-host=localhost --db-port=8432 \ - $data_dir >>"EOO" + $data_dir >>~"%EOO%" : 1 status: 200 - message: CI request is queued: http://example.com + %message: CI request is queued: http://example.com/@.+% + %. reference: $request_id EOO } diff --git a/tests/load/driver.cxx b/tests/load/driver.cxx index b387b9b..a36653c 100644 --- a/tests/load/driver.cxx +++ b/tests/load/driver.cxx @@ -35,7 +35,7 @@ check_location (shared_ptr& p) { if (p->internal ()) return p->location && *p->location == - path (p->id.name.string () + "-" + p->version.string () + ".tar.gz"); + path (p->name.string () + "-" + p->version.string () + ".tar.gz"); else return !p->location; } @@ -63,12 +63,14 @@ namespace bpkg static void test_pkg_repos (const cstrings& loader_args, const dir_path& loadtab_dir, - odb::pgsql::database&); + odb::pgsql::database&, + const string& tenant); static void test_git_repos (const cstrings& loader_args, const dir_path& loadtab_dir, - odb::pgsql::database&); + odb::pgsql::database&, + const string& tenant); int main (int argc, char* argv[]) @@ -98,8 +100,9 @@ main (int argc, char* argv[]) return 1; } - // Parse the database options. + // Parse the tenant and database options. // + string tenant; string user; string password; string name ("brep_package"); @@ -109,7 +112,9 @@ main (int argc, char* argv[]) for (++i; i < argc - 1; ++i) { string n (argv[i]); - if (n == "--db-user" || n == "-u") + if (n == "--tenant") + tenant = argv[++i]; + else if (n == "--db-user" || n == "-u") user = argv[++i]; else if (n == "--db-password") password = argv[++i]; @@ -157,12 +162,12 @@ main (int argc, char* argv[]) { case repository_type::pkg: { - test_pkg_repos (loader_args, loadtab_dir, db); + test_pkg_repos (loader_args, loadtab_dir, db, tenant); break; } case repository_type::git: { - test_git_repos (loader_args, loadtab_dir, db); + test_git_repos (loader_args, loadtab_dir, db, tenant); break; } default: @@ -192,7 +197,8 @@ dep (const char* n, optional c) static void test_git_repos (const cstrings& loader_args, const dir_path& loadtab_dir, - odb::pgsql::database& db) + odb::pgsql::database& db, + const string& tenant) { path loadtab (loadtab_dir / "git-loadtab"); @@ -212,13 +218,17 @@ test_git_repos (const cstrings& loader_args, session s; transaction t (db.begin ()); - assert (db.query ().size () == 1); - assert (db.query ().size () == 1); + assert (db.query ( + query::id.tenant == tenant).size () == 1); + + assert (db.query ( + query::id.tenant == tenant).size () == 1); // Verify 'foo' repository. // shared_ptr r ( - db.load ("git:example.com/foo#master")); + db.load (repository_id (tenant, + "git:example.com/foo#master"))); assert (r->location.string () == "https://git.example.com/foo.git#master"); assert (r->summary && *r->summary == "foo project repository"); @@ -229,7 +239,7 @@ test_git_repos (const cstrings& loader_args, // shared_ptr p ( db.load ( - package_id (package_name ("libfoo"), version ("1.0")))); + package_id (tenant, package_name ("libfoo"), version ("1.0")))); assert (p->fragment && *p->fragment == "0f50af28d1cfb0c22f5b88e2bf674ab732e058d9"); @@ -249,7 +259,8 @@ test_git_repos (const cstrings& loader_args, static void test_pkg_repos (const cstrings& loader_args, const dir_path& loadtab_dir, - odb::pgsql::database& db) + odb::pgsql::database& db, + const string& tenant) { path p (loadtab_dir / dir_path ("1/stable") / packages); timestamp srt (file_mtime (p)); @@ -273,23 +284,30 @@ test_pkg_repos (const cstrings& loader_args, session s; transaction t (db.begin ()); - assert (db.query ().size () == 7); - assert (db.query ().size () == 18); + assert (db.query ( + query::id.tenant == tenant).size () == 7); + + assert (db.query ( + query::id.tenant == tenant).size () == 18); shared_ptr sr ( - db.load ("pkg:dev.cppget.org/stable")); + db.load (repository_id (tenant, + "pkg:dev.cppget.org/stable"))); shared_ptr mr ( - db.load ("pkg:dev.cppget.org/math")); + db.load (repository_id (tenant, + "pkg:dev.cppget.org/math"))); shared_ptr cr ( - db.load ("pkg:dev.cppget.org/misc")); + db.load (repository_id (tenant, "pkg:dev.cppget.org/misc"))); shared_ptr tr ( - db.load ("pkg:dev.cppget.org/testing")); + db.load (repository_id (tenant, + "pkg:dev.cppget.org/testing"))); shared_ptr gr ( - db.load ("pkg:dev.cppget.org/staging")); + db.load (repository_id (tenant, + "pkg:dev.cppget.org/staging"))); // Verify 'stable' repository. // @@ -326,7 +344,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpvxy ( db.load ( - package_id (package_name ("libfoo"), version ("+0-X.Y")))); + package_id (tenant, package_name ("libfoo"), version ("+0-X.Y")))); assert (fpvxy->project == package_name ("libfoo")); assert (fpvxy->summary == "The Foo Library"); @@ -359,7 +377,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv1 ( db.load ( - package_id (package_name ("libfoo"), version ("1.0")))); + package_id (tenant, package_name ("libfoo"), version ("1.0")))); assert (fpv1->summary == "The Foo Library"); assert (fpv1->tags.empty ()); @@ -393,7 +411,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv2 ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.2")))); + package_id (tenant, package_name ("libfoo"), version ("1.2.2")))); assert (fpv2->summary == "The Foo library"); assert (fpv2->tags == strings ({"c++", "foo"})); @@ -435,7 +453,9 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv2a ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.2-alpha.1")))); + package_id (tenant, + package_name ("libfoo"), + version ("1.2.2-alpha.1")))); assert (fpv2a->summary == "The Foo library"); assert (fpv2a->tags == strings ({"c++", "foo"})); @@ -495,7 +515,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv3 ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.3+4")))); + package_id (tenant, package_name ("libfoo"), version ("1.2.3+4")))); assert (fpv3->summary == "The Foo library"); assert (fpv3->tags == strings ({"c++", "foo"})); @@ -531,7 +551,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv4 ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.4")))); + package_id (tenant, package_name ("libfoo"), version ("1.2.4")))); assert (fpv4->summary == "The Foo Library"); assert (fpv4->tags == strings ({"c++", "foo"})); @@ -597,7 +617,9 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr xpv ( db.load ( - package_id (package_name ("libstudxml"), version ("1.0.0+1")))); + package_id (tenant, + package_name ("libstudxml"), + version ("1.0.0+1")))); assert (xpv->summary == "Modern C++ XML API"); assert (xpv->tags == strings ({"c++", "xml", "parser", "serializer", @@ -646,7 +668,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv5 ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.4+1")))); + package_id (tenant, package_name ("libfoo"), version ("1.2.4+1")))); assert (fpv5->summary == "The Foo Math Library"); assert (fpv5->tags == strings ({"c++", "foo", "math"})); @@ -761,7 +783,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr epv ( db.load ( - package_id (package_name ("libexp"), version ("+2-1.2+1")))); + package_id (tenant, package_name ("libexp"), version ("+2-1.2+1")))); assert (epv->project == "mathLab"); assert (epv->summary == "The exponent"); @@ -812,7 +834,9 @@ test_pkg_repos (const cstrings& loader_args, // libpq-0 // shared_ptr qpv ( - db.load (package_id (package_name ("libpq"), version ("0")))); + db.load (package_id (tenant, + package_name ("libpq"), + version ("0")))); assert (qpv->summary == "PostgreSQL C API client library"); @@ -849,7 +873,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr bpv ( db.load ( - package_id (package_name ("libbar"), version ("2.4.0+3")))); + package_id (tenant, package_name ("libbar"), version ("2.4.0+3")))); assert (check_external (*bpv)); assert (bpv->other_repositories.size () == 1); @@ -862,7 +886,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv0 ( db.load ( - package_id (package_name ("libfoo"), version ("0.1")))); + package_id (tenant, package_name ("libfoo"), version ("0.1")))); assert (check_external (*fpv0)); assert (fpv0->other_repositories.size () == 1); @@ -873,7 +897,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr fpv6 ( db.load ( - package_id (package_name ("libfoo"), version ("1.2.4+2")))); + package_id (tenant, package_name ("libfoo"), version ("1.2.4+2")))); assert (check_external (*fpv6)); assert (fpv6->other_repositories.size () == 1); @@ -913,7 +937,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr mpv0 ( db.load ( - package_id (package_name ("libmisc"), version ("2.4.0")))); + package_id (tenant, package_name ("libmisc"), version ("2.4.0")))); assert (check_external (*mpv0)); assert (mpv0->other_repositories.size () == 1); @@ -924,7 +948,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr mpv1 ( db.load ( - package_id (package_name ("libmisc"), version ("2.3.0+1")))); + package_id (tenant, package_name ("libmisc"), version ("2.3.0+1")))); assert (check_external (*mpv1)); assert (mpv1->other_repositories.size () == 1); @@ -963,7 +987,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr tpv ( db.load ( - package_id (package_name ("libexpat"), version ("5.1")))); + package_id (tenant, package_name ("libexpat"), version ("5.1")))); assert (check_external (*tpv)); assert (tpv->other_repositories.size () == 1); @@ -976,7 +1000,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr gpv ( db.load ( - package_id (package_name ("libgenx"), version ("1.0")))); + package_id (tenant, package_name ("libgenx"), version ("1.0")))); assert (check_external (*gpv)); assert (gpv->other_repositories.size () == 1); @@ -989,7 +1013,7 @@ test_pkg_repos (const cstrings& loader_args, // shared_ptr mpv2 ( db.load ( - package_id (package_name ("libmisc"), version ("1.0")))); + package_id (tenant, package_name ("libmisc"), version ("1.0")))); assert (check_external (*mpv2)); assert (mpv2->other_repositories.size () == 1); @@ -1019,7 +1043,7 @@ test_pkg_repos (const cstrings& loader_args, shared_ptr bpv ( db.load ( - package_id (package_name ("libbar"), version ("2.4.0+3")))); + package_id (tenant, package_name ("libbar"), version ("2.4.0+3")))); assert (bpv->summary == "test"); @@ -1040,7 +1064,7 @@ test_pkg_repos (const cstrings& loader_args, shared_ptr bpv ( db.find ( - package_id (package_name ("libbar"), version ("2.4.0+3")))); + package_id (tenant, package_name ("libbar"), version ("2.4.0+3")))); // External package summary is not saved. // -- cgit v1.1