Skip to content

Commit

Permalink
uenv status (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcumming authored Dec 2, 2024
1 parent 8a082c7 commit 638be2c
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 1 deletion.
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ if uenv_cli
'src/cli/repo.cpp',
'src/cli/run.cpp',
'src/cli/start.cpp',
'src/cli/status.cpp',
'src/cli/uenv.cpp',
]
uenv_dep = [sqlite3_dep]
Expand Down
117 changes: 117 additions & 0 deletions src/cli/status.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// vim: ts=4 sts=4 sw=4 et

#include <algorithm>
#include <optional>
#include <string>
#include <vector>

#include <fmt/core.h>
#include <fmt/ranges.h>
#include <fmt/std.h>
#include <spdlog/spdlog.h>

#include <site/site.h>
#include <uenv/parse.h>
#include <uenv/repository.h>
#include <util/expected.h>
#include <util/strings.h>

#include "help.h"
#include "status.h"
#include "terminal.h"

namespace uenv {

std::string status_footer();

void status_args::add_cli(CLI::App& cli,
[[maybe_unused]] global_settings& settings) {
auto* status_cli = cli.add_subcommand(
"status", "print information about the currently loaded uenv");
status_cli->callback(
[&settings]() { settings.mode = uenv::cli_mode::status; });

status_cli->footer(status_footer);
}

int status([[maybe_unused]] const status_args& args,
[[maybe_unused]] const global_settings& settings) {
spdlog::info("uenv status");

if (!(std::getenv("UENV_MOUNT_LIST") && std::getenv("UENV_VIEW"))) {
term::msg("there is no uenv loaded");
return 0;
}

std::string mount_desc = std::getenv("UENV_MOUNT_LIST");
std::string view_literal = std::getenv("UENV_VIEW");

// the UENV_VIEW environment variable is a comma-separated list of the form
// mount:uenv-name:view-name
// concretise_uenv requires a comma-separated list of the form
// uenv-name:view-name
std::string view_string = "";
for (auto view : util::split(view_literal, ',', true)) {
auto terms = util::split(view, ':', true);
if (terms.size() != 3) {
// just skip incorrectly formatted view description
spdlog::warn(
"incorrectly formatted view description in UENV_VIEW: '{}'",
view);
continue;
}
view_string += fmt::format("{}:{},", terms[1], terms[2]);
}
std::optional<std::string> view_desc;
if (!view_string.empty()) {
view_desc = view_string;
}
spdlog::debug("derived view description from UENV_VIEW {}", view_desc);

const auto env = concretise_env(mount_desc, view_desc, settings.repo);

if (!env) {
term::error("could not interpret environment: {}", env.error());
return 1;
}

for (auto& [name, E] : env->uenvs) {
term::msg("{}:{}", color::cyan(name), color::white(E.mount_path));
if (E.description) {
term::msg(" {}", *E.description);
}
if (!E.views.empty()) {
term::msg(" {}:", color::white("views"));
for (auto& [name, view] : E.views) {
const bool loaded =
std::ranges::find_if(
env->views, [name, uenv = E.name](auto& p) {
return p.name == name && p.uenv == uenv;
}) != env->views.end();
std::string status = loaded ? color::yellow(" (loaded)") : "";
term::msg(" {}{}: {}", color::cyan(name), status,
view.description);
}
}
}

return 0;
}

std::string status_footer() {
using enum help::block::admonition;
std::vector<help::item> items{
// clang-format off
help::block{none, "Disply information about the current uenv environment." },
help::linebreak{},
help::block{xmpl, "get status:"},
help::block{code, "uenv status"},
help::linebreak{},
help::block{note, "if no uenv is loaded, the message 'there is no no uenv loaded' will be printed"},
// clang-format on
};

return fmt::format("{}", fmt::join(items, "\n"));
}

} // namespace uenv
33 changes: 33 additions & 0 deletions src/cli/status.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
// vim: ts=4 sts=4 sw=4 et

#include <CLI/CLI.hpp>
#include <fmt/core.h>

#include <uenv/env.h>

#include "uenv.h"

namespace uenv {

struct status_args {
void add_cli(CLI::App&, global_settings& settings);
};

int status(const status_args& args, const global_settings& settings);

} // namespace uenv

template <> class fmt::formatter<uenv::status_args> {
public:
// parse format specification and store it:
constexpr auto parse(format_parse_context& ctx) {
return ctx.end();
}
// format a value using stored specification:
template <typename FmtContext>
constexpr auto format([[maybe_unused]] uenv::status_args const& opts,
FmtContext& ctx) const {
return fmt::format_to(ctx.out(), "");
}
};
5 changes: 5 additions & 0 deletions src/cli/uenv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "repo.h"
#include "run.h"
#include "start.h"
#include "status.h"
#include "terminal.h"
#include "uenv.h"

Expand Down Expand Up @@ -49,11 +50,13 @@ int main(int argc, char** argv) {
uenv::run_args run;
uenv::image_args image;
uenv::repo_args repo;
uenv::status_args stat;

start.add_cli(cli, settings);
run.add_cli(cli, settings);
image.add_cli(cli, settings);
repo.add_cli(cli, settings);
stat.add_cli(cli, settings);

CLI11_PARSE(cli, argc, argv);

Expand Down Expand Up @@ -136,6 +139,8 @@ int main(int argc, char** argv) {
return uenv::repo_create(repo.create_args, settings);
case settings.repo_status:
return uenv::repo_status(repo.status_args, settings);
case settings.status:
return uenv::status(stat, settings);
case settings.unset:
term::msg("uenv version {}", UENV_VERSION);
term::msg("call '{} --help' for help", argv[0]);
Expand Down
3 changes: 3 additions & 0 deletions src/cli/uenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum class cli_mode : std::uint32_t {
repo_status,
run,
start,
status,
};

struct global_settings {
Expand Down Expand Up @@ -75,6 +76,8 @@ template <> class fmt::formatter<uenv::cli_mode> {
return format_to(ctx.out(), "repo-create");
case repo_status:
return format_to(ctx.out(), "repo-status");
case status:
return format_to(ctx.out(), "status");
}
return format_to(ctx.out(), "unknown");
}
Expand Down
7 changes: 6 additions & 1 deletion src/slurm/mount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ util::expected<void, std::string> mount_entry::validate() const {
fmt::format("the squashfs file '{}' is not a file", sqfs_path));
}

// do we have read access to the squashfs file
// do we have read access to the squashfs file?
//
// NOTE: this check is performed on the login node under the user's account.
// The mount step is performed with elevated privelages on the compute node.
// Skipping this check could possibly allow users to mount images to which
// they don't have access.
const fs::perms sqfs_perm = fs::status(sqfs).permissions();
auto satisfies = [&sqfs_perm](fs::perms c) {
return fs::perms::none != (sqfs_perm & c);
Expand Down
4 changes: 4 additions & 0 deletions src/slurm/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ validate_uenv_mount_list(std::string mount_var) {
}

for (auto& entry : *mount_list) {
// NOTE: the validate step is important because it checks both
// that the paths exist AND that the user has permission
// to read the squashfs image. Weakening this check would possibly
// let users mount images to which they don't have access.
if (auto valid = entry.validate(); !valid) {
return util::unexpected(valid.error());
}
Expand Down
15 changes: 15 additions & 0 deletions src/uenv/env.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include <algorithm>
#include <filesystem>
#include <optional>
#include <ranges>
#include <set>
#include <string>
#include <vector>

#include <fmt/core.h>
#include <fmt/ranges.h>
#include <spdlog/spdlog.h>

#include <site/site.h>
Expand Down Expand Up @@ -336,6 +338,19 @@ std::unordered_map<std::string, std::string> getenv(const env& environment) {
}
}

// set UENV_VIEW env variable, used inside the environment by uenv
auto view_description = [&environment](const auto& v) {
return fmt::format("{}:{}:{}", environment.uenvs.at(v.uenv).mount_path,
v.uenv, v.name);
};

env_vars["UENV_VIEW"] =
fmt::format("{}", fmt::join(environment.views |
std::views::transform(view_description),
","));

// NOTE: UENV_MOUNT_LIST is set by squashfs-mount, not by the uenv CLI.

return env_vars;
}

Expand Down

0 comments on commit 638be2c

Please sign in to comment.