diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2018-09-05 21:23:41 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2018-09-08 17:44:57 +0300 |
commit | 70c1cdfd8f34472761fe5ec97f0713990c1b4f5b (patch) | |
tree | f2e631c10563bcc0cde07e4359c11b800a188d86 /mod | |
parent | 3be834183ae36c321e4b560dce9a63cee846e63d (diff) |
Add multi-tenancy support
Diffstat (limited to 'mod')
-rw-r--r-- | mod/build-config.cxx | 9 | ||||
-rw-r--r-- | mod/mod-build-force.cxx | 3 | ||||
-rw-r--r-- | mod/mod-build-log.cxx | 18 | ||||
-rw-r--r-- | mod/mod-build-result.cxx | 23 | ||||
-rw-r--r-- | mod/mod-build-task.cxx | 69 | ||||
-rw-r--r-- | mod/mod-builds.cxx | 119 | ||||
-rw-r--r-- | mod/mod-ci.cxx | 2 | ||||
-rw-r--r-- | mod/mod-package-details.cxx | 32 | ||||
-rw-r--r-- | mod/mod-package-search.cxx | 20 | ||||
-rw-r--r-- | mod/mod-package-version-details.cxx | 17 | ||||
-rw-r--r-- | mod/mod-repository-details.cxx | 13 | ||||
-rw-r--r-- | mod/mod-repository-root.cxx | 18 | ||||
-rw-r--r-- | mod/mod-submit.cxx | 2 | ||||
-rw-r--r-- | mod/module.hxx | 10 | ||||
-rw-r--r-- | mod/page.cxx | 31 | ||||
-rw-r--r-- | mod/page.hxx | 59 | ||||
-rw-r--r-- | mod/utility.hxx | 25 |
17 files changed, 312 insertions, 158 deletions
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 <web/mime-url-encoding.hxx> +#include <mod/utility.hxx> + 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<string>& target_pattern, + match (const string& config_pattern, + const optional<string>& 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<std::chrono::nanoseconds> ( 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<build_repository> 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<buildable_package>; using prep_pkg_query = prepared_query<buildable_package>; @@ -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<build> b (build_db_->find<build> (bid)); optional<string> 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<build> (move (bid.package.name), + b = make_shared<build> (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<build>& x, - const shared_ptr<build>& y) -> bool + auto cmp = [] (const shared_ptr<build>& x, const shared_ptr<build>& 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<build_package> p ( build_db_->find<build_package> (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 <typename T> static inline query<T> -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<string>& tenant) { using namespace brep; using query = query<T>; @@ -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 <typename T, typename P = typename query<T>::build_package> static inline query<T> -package_query (const brep::params::builds& params) +package_query (const brep::params::builds& params, const string& tenant) { using namespace brep; using query = query<T>; - 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<T>; 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<pair<string, string>> build_results ({ - {"unbuilt", "<unbuilt>"}, - {"*", "*"}, - {"pending", "pending"}, + {"unbuilt", "<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<toolchain> ( + (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" <input/> 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" <input/> 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<package_build_count> ( - build_query<package_build_count> (*build_conf_names_, params)); + build_query<package_build_count> (*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<package_build> ( - build_query<package_build> (*build_conf_names_, params) + - "ORDER BY" + query<build>::timestamp + "DESC" + + build_query<package_build> (*build_conf_names_, params, tenant) + + "ORDER BY" + query<package_build>::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_toolchain> 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<buildable_package_count> ( - package_query<buildable_package_count> (params))); + size_t nmax ( + config_toolchains.size () * + build_db_->query_value<buildable_package_count> ( + package_query<buildable_package_count> (params, tenant))); size_t ncur = build_db_->query_value<package_build_count> ( - build_query<package_build_count> (*build_conf_names_, bld_params)); + build_query<package_build_count> (*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<package_build_count> ( - bld_query::build::id.package, id) && - bld_query::build::id.configuration == bld_query::_ref (config) && - build_query<package_build_count> (cstrings (), bld_params)); + package_id_eq<package_build_count> (bid.package, id) && + bid.configuration == bld_query::_ref (config) && + + // Note that the query already constrains the tenant via the build + // package id. + // + build_query<package_build_count> (cstrings () /* configs */, + bld_params, + nullopt /* tenant */)); prep_bld_query bld_prep_query ( build_db_->prepare_query<package_build_count> ( @@ -620,7 +640,8 @@ handle (request& rq, response& rs) // form parameters. // using query = query<build_constrained_package>; - query q (package_query<build_constrained_package, query> (params)); + query q (package_query<build_constrained_package, query> (params, + tenant)); for (const auto& p: build_db_->query<build_constrained_package> (q)) { @@ -675,13 +696,15 @@ handle (request& rq, response& rs) using pkg_query = query<buildable_package>; using prep_pkg_query = prepared_query<buildable_package>; - pkg_query pq (package_query<buildable_package> (params)); + pkg_query pq (package_query<buildable_package> (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<package_build> (bld_query::build::id.package, id) && - build_query<package_build> (*build_conf_names_, bld_params)); + + // Note that the query already constrains the tenant via the build + // package id. + // + build_query<package_build> (*build_conf_names_, + bld_params, + nullopt /* tenant */)); prep_bld_query bld_prep_query ( conn->prepare_query<package_build> ("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 <typename T> static inline query<T> -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<T>; @@ -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<latest_package>; + package_name n (*rq.path ().rbegin ()); latest_package lp; if (!package_db_->query_one<latest_package> ( - query<latest_package> ("(" + query<latest_package>::_val (n) + ")"), - lp)) + "(" + query::_val (tenant) + "," + query::_val (n) + ")", lp)) throw invalid_request (404, "Package '" + n.string () + "' not found"); pkg = package_db_->load<package> (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<package_count> ( - search_params<package_count> (name, squery))); + search_params<package_count> (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<package_search_rank> ( - search_params<package_search_rank> (name, squery) + + search_params<package_search_rank> (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 <typename T> static inline query<T> -search_param (const brep::string& q) +search_param (const brep::string& q, const brep::string& t) { using query = query<T>; 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<latest_package_count> ( - search_param<latest_package_count> (squery))); + search_param<latest_package_count> (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<latest_package_search_rank> ( - search_param<latest_package_search_rank> (squery) + + search_param<latest_package_search_rank> (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> (package_id (pn, ver)); + pkg = package_db_->load<package> (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<repository> ( - 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 <libbrep/types.hxx> #include <libbrep/utility.hxx> +#include <mod/utility.hxx> #include <mod/options.hxx> #include <mod/diagnostics.hxx> @@ -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 <libbrep/package.hxx> #include <libbrep/package-odb.hxx> +#include <mod/utility.hxx> #include <mod/build-config.hxx> // 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<page_menu>& menu): - root_ (root), logo_ (logo), menu_ (menu) {} + DIV_HEADER (const web::xhtml::fragment& logo, + const vector<page_menu>& 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<page_menu>& 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 <libbrep/types.hxx> +#include <libbrep/utility.hxx> + +namespace brep +{ + // Append the @<tenant> 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_path> (dir / ('@' + tenant)) + : dir; + } +} + +#endif // MOD_UTILITY_HXX |