Skip to content

Commit

Permalink
improve support for namespaces (#29)
Browse files Browse the repository at this point in the history
remove the `-nspace` flag that replaced `--build`:

* now the namespace is part of the uenv label, e.g. `build::prgenv-gnu`
* add a helpful error message if the old `--build` flag is used
* use the namespace correctly when searching for uenv in the registry - now the `service` namespace used by the build service can be accessed
  • Loading branch information
bcumming authored Dec 7, 2024
1 parent 45e5b6c commit ebb43e0
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 34 deletions.
34 changes: 25 additions & 9 deletions src/cli/find.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ void image_find_args::add_cli(CLI::App& cli,
find_cli->add_option("uenv", uenv_description, "search term");
find_cli->add_flag("--no-header", no_header,
"print only the matching records, with no header.");
find_cli->add_option("-n,--namespace", nspace,
"the namespace in which to search (default 'deploy')");
find_cli->add_flag("--build", build,
"invalid: replaced with 'build::' prefix on uenv label");
find_cli->callback(
[&settings]() { settings.mode = uenv::cli_mode::image_find; });

Expand All @@ -40,21 +40,32 @@ void image_find_args::add_cli(CLI::App& cli,

int image_find([[maybe_unused]] const image_find_args& args,
[[maybe_unused]] const global_settings& settings) {
if (args.build) {
std::string descr = args.uenv_description.value_or("");
term::error(
"the --build flag has been removed.\nSpecify the build namespace "
"as part of the uenv description, e.g.\n{}",
color::yellow(fmt::format("uenv image find build::{}", descr)));
return 1;
}

// find the search term that was provided by the user
uenv_label label{};
std::string nspace{site::default_namespace()};
if (args.uenv_description) {
if (const auto parse = parse_uenv_label(*args.uenv_description)) {
label = *parse;
if (const auto parse = parse_uenv_nslabel(*args.uenv_description)) {
label = parse->label;
if (parse->nspace) {
nspace = *parse->nspace;
}
} else {
term::error("invalid search term: {}", parse.error().message());
return 1;
}
}
label.system = site::get_system_name(label.system);
spdlog::info("image_find: {}::{}", nspace, label);

spdlog::info("image_find: {}::{}", args.nspace, label);

auto store = site::registry_listing(args.nspace);
auto store = site::registry_listing(nspace);
if (!store) {
term::error("unable to get a listing of the uenv", store.error());
return 1;
Expand Down Expand Up @@ -111,8 +122,13 @@ std::string image_find_footer() {
help::block{note, "more than one uenv might be listed if there are two uenv that refer",
"to the same underlying uenv sha256."},
help::linebreak{},
help::block{xmpl, "search for uenv by id (id is the first 16 characters of the sha256):"},
help::block{xmpl, "search for uenv by id (id is the first 16 characters of the sha256)"},
help::block{code, "uenv image find 510094ddb3484e30"},
help::linebreak{},
help::block{xmpl, "search for uenv in the service namespace"},
help::block{code, "uenv image find service:: # all uenv"},
help::block{code, "uenv image find service::prgenv-gnu # match a name"},
help::block{code, "uenv image find service::%gh200 # built for gh200"},
// clang-format on
};

Expand Down
2 changes: 1 addition & 1 deletion src/cli/find.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace uenv {
struct image_find_args {
std::optional<std::string> uenv_description;
bool no_header = false;
std::string nspace = "deploy";
bool build = false;
void add_cli(CLI::App&, global_settings& settings);
};

Expand Down
34 changes: 22 additions & 12 deletions src/cli/pull.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ void image_pull_args::add_cli(CLI::App& cli,
pull_cli->add_flag("--only-meta", only_meta, "only download meta data");
pull_cli->add_flag("--force", force,
"download and overwrite existing images");
pull_cli->add_option("-n,--namespace", nspace,
"the namespace in which to search (default 'deploy')");
pull_cli->add_flag("--build", build,
"invalid: replaced with 'build::' prefix on uenv label");
pull_cli->callback(
[&settings]() { settings.mode = uenv::cli_mode::image_pull; });

Expand All @@ -49,15 +49,25 @@ void image_pull_args::add_cli(CLI::App& cli,

int image_pull([[maybe_unused]] const image_pull_args& args,
[[maybe_unused]] const global_settings& settings) {

namespace fs = std::filesystem;

spdlog::info("image pull: {}", args);
if (args.build) {
term::error(
"the --build flag has been removed.\nSpecify the build namespace "
"as part of the uenv description, e.g.\n{}",
color::yellow(fmt::format("uenv image pull build::{}",
args.uenv_description)));
return 1;
}

// pull the search term that was provided by the user
uenv_label label{};
if (const auto parse = parse_uenv_label(args.uenv_description)) {
label = *parse;
std::string nspace{site::default_namespace()};
if (const auto parse = parse_uenv_nslabel(args.uenv_description)) {
label = parse->label;
if (parse->nspace) {
nspace = *parse->nspace;
}
} else {
term::error("invalid search term: {}", parse.error().message());
return 1;
Expand All @@ -71,9 +81,9 @@ int image_pull([[maybe_unused]] const image_pull_args& args,
return 1;
}

spdlog::info("image_pull: {}::{}", args.nspace, label);
spdlog::info("image_pull: {}::{}", nspace, label);

auto registry = site::registry_listing(args.nspace);
auto registry = site::registry_listing(nspace);
if (!registry) {
term::error("unable to get a listing of the uenv", registry.error());
return 1;
Expand Down Expand Up @@ -149,7 +159,7 @@ int image_pull([[maybe_unused]] const image_pull_args& args,
auto rego_url = site::registry_url();
spdlog::debug("registry url: {}", rego_url);

auto digests = oras::discover(rego_url, args.nspace, record);
auto digests = oras::discover(rego_url, nspace, record);
if (!digests || digests->empty()) {
term::error(
"unable to pull image - rerun with -vvv flag and send "
Expand All @@ -160,8 +170,8 @@ int image_pull([[maybe_unused]] const image_pull_args& args,

const auto digest = *(digests->begin());

if (auto okay = oras::pull_digest(rego_url, args.nspace, record,
digest, paths.store);
if (auto okay = oras::pull_digest(rego_url, nspace, record, digest,
paths.store);
!okay) {
term::error(
"unable to pull image - rerun with -vvv flag and send "
Expand All @@ -170,7 +180,7 @@ int image_pull([[maybe_unused]] const image_pull_args& args,
}

auto tag_result =
oras::pull_tag(rego_url, args.nspace, record, paths.store);
oras::pull_tag(rego_url, nspace, record, paths.store);
if (!tag_result) {
return 1;
}
Expand Down
6 changes: 3 additions & 3 deletions src/cli/pull.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct image_pull_args {
std::string uenv_description;
bool only_meta = false;
bool force = false;
std::string nspace = "deploy";
bool build = false;
void add_cli(CLI::App&, global_settings& settings);
};

Expand All @@ -35,7 +35,7 @@ template <> class fmt::formatter<uenv::image_pull_args> {
constexpr auto format(uenv::image_pull_args const& opts,
FmtContext& ctx) const {
return fmt::format_to(
ctx.out(), "(image pull {} .only_meta={} .force={} .namespace={})",
opts.uenv_description, opts.only_meta, opts.force, opts.nspace);
ctx.out(), "(image pull {} .only_meta={} .force={})",
opts.uenv_description, opts.only_meta, opts.force);
}
};
19 changes: 14 additions & 5 deletions src/site/site.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,29 @@ std::optional<std::string> get_system_name(std::optional<std::string> value) {
return std::nullopt;
}

std::string default_namespace() {
return "deploy";
}

util::expected<uenv::repository, std::string>
registry_listing(const std::string& nspace) {
using json = nlohmann::json;
spdlog::debug("registry_listing: https://uenv-list.svc.cscs.ch/list/{}",
nspace);

// perform curl call against middleware end point
// https://github.com/eth-cscs/uenv/blob/master/lib/jfrog.py
auto raw_records = util::curl::get("https://uenv-list.svc.cscs.ch/list");
// example of full url end point call:
// https://uenv-list.svc.cscs.ch/list?namespace=deploy&cluster=todi&arch=gh200&app=prgenv-gnu&version=24.7
// we only filter on namespace, and use the database to do more querying
// later
const auto url =
fmt::format("https://uenv-list.svc.cscs.ch/list?namespace={}", nspace);
spdlog::debug("registry_listing: {}", url);
auto raw_records = util::curl::get(url);

if (!raw_records) {
int ec = raw_records.error().code;
spdlog::error("curl error {}: {}", ec, raw_records.error().message);
return util::unexpected{"unable to list entries"};
return util::unexpected{"unable to reach uenv-list.svc.cscs.ch to get "
"list of available uenv"};
}

std::vector<uenv::uenv_record> records;
Expand Down
3 changes: 3 additions & 0 deletions src/site/site.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace site {
// on CSCS systems this is derived from the CLUSTER_NAME environment variable
std::optional<std::string> get_system_name(std::optional<std::string>);

// default namespace for image deployment
std::string default_namespace();

util::expected<uenv::repository, std::string>
registry_listing(const std::string& nspace);

Expand Down
35 changes: 35 additions & 0 deletions src/uenv/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,41 @@ parse_uenv_label(const std::string& in) {
return result;
}

util::expected<uenv_nslabel, parse_error>
parse_uenv_nslabel(const std::string& in) {
auto L = lexer(in);
std::optional<std::string> nspace;

// check whether in begins with 'name::'
if (is_name_start_tok(L.peek().kind)) {
// consume the name tokens
auto i = 1u;
while (is_name_tok(L.peek(i).kind)) {
++i;
}
// check for following colons
if (L.peek(i).kind == tok::colon && L.peek(i + 1).kind == tok::colon) {
// parse the namespace name
PARSE(L, name, nspace);
// gobble the ::
L.next();
L.next();
}
}

// now parse the label
auto label = parse_uenv_label(L);
if (!label) {
return util::unexpected(label.error());
}

if (const auto t = L.peek(); t.kind != tok::end) {
return util::unexpected(parse_error{
L.string(), fmt::format("unexpected symbol '{}'", t.spelling), t});
}
return uenv_nslabel{.nspace = nspace, .label = *label};
}

// parse an individual uenv description
util::expected<uenv_description, parse_error> parse_uenv_description(lexer& L) {
const auto k = L.current_kind();
Expand Down
4 changes: 4 additions & 0 deletions src/uenv/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ util::expected<std::string, parse_error> parse_path(const std::string& in);

util::expected<uenv_label, parse_error> parse_uenv_label(const std::string& in);

// pares a namespacesd uenv label
util::expected<uenv_nslabel, parse_error>
parse_uenv_nslabel(const std::string& in);

util::expected<uenv_registry_entry, parse_error>
parse_registry_entry(const std::string& in);

Expand Down
2 changes: 0 additions & 2 deletions src/uenv/repository.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,11 +755,9 @@ repository_impl::query(const uenv_label& label) const {
results.push_back(record_from_query(*s).value());
}

spdlog::info("will we checking for id and sha");
// now check for id and sha search terms
if (label.only_name()) {
query_terms.erase(query_terms.begin());
spdlog::info("checking for id and sha");
// search for an if name could also be an id
if (is_sha(*label.name, 16)) {
query_terms.push_back(
Expand Down
8 changes: 8 additions & 0 deletions src/uenv/uenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ struct uenv_label {
bool partially_qualified() const {
return name && version && tag;
}
bool empty() const {
return !fully_qualified();
}
};

struct uenv_nslabel {
std::optional<std::string> nspace;
uenv_label label;
};

struct uenv_date {
Expand Down
62 changes: 60 additions & 2 deletions test/unit/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,74 @@ TEST_CASE("parse uenv label", "[parse]") {
REQUIRE(result->uarch == "a100");
REQUIRE(!result->system);
}
for (auto defectiv_label : {
for (auto defective_label : {
"prgenv-gnu/:v1",
"prgenv-gnu/wombat:",
"prgenv-gnu/:v1",
"prgenv-gnu/24:v1@",
"prgenv-gnu/24:@",
"prgenv-gnu/24:v1@gh200%",
".wombat",
}) {
auto L = uenv::lexer(defectiv_label);
auto L = uenv::lexer(defective_label);
REQUIRE(!uenv::parse_uenv_label(L));
}
}

TEST_CASE("parse namespace uenv label", "[parse]") {
{
auto r = uenv::parse_uenv_nslabel("");
REQUIRE(r);
REQUIRE(!r->nspace);
REQUIRE(r->label.empty());
}
{
auto r = uenv::parse_uenv_nslabel("deploy::");
REQUIRE(r);
REQUIRE(r->nspace == "deploy");
REQUIRE(r->label.empty());
}
{
auto r = uenv::parse_uenv_nslabel("prgenv-gnu/24.7:v1");
REQUIRE(r);
REQUIRE(!r->nspace);
REQUIRE(r->label.name == "prgenv-gnu");
REQUIRE(r->label.version == "24.7");
REQUIRE(r->label.tag == "v1");
REQUIRE(!r->label.uarch);
REQUIRE(!r->label.system);
}
{
auto r = uenv::parse_uenv_nslabel("deploy:::v1");
REQUIRE(r);
REQUIRE(r->nspace == "deploy");
REQUIRE(!r->label.name);
REQUIRE(!r->label.version);
REQUIRE(r->label.tag == "v1");
REQUIRE(!r->label.uarch);
REQUIRE(!r->label.system);
}
{
auto r = uenv::parse_uenv_nslabel("wombat::@*");
REQUIRE(r);
REQUIRE(r->nspace == "wombat");
REQUIRE(!r->label.name);
REQUIRE(!r->label.version);
REQUIRE(!r->label.tag);
REQUIRE(!r->label.uarch);
REQUIRE(r->label.system == "*");
}
for (auto defective_label : {
"build::prgenv-gnu/:v1",
"build::prgenv-gnu/wombat:",
"build::.wombat",
"-build::.wombat",
"_build::.wombat",
}) {
REQUIRE(!uenv::parse_uenv_nslabel(defective_label));
}
}

TEST_CASE("parse view list", "[parse]") {
{
auto in = "spack,modules";
Expand Down

0 comments on commit ebb43e0

Please sign in to comment.