Skip to content

Commit

Permalink
uenv image delete (#38)
Browse files Browse the repository at this point in the history
Support deleting uenv images from a remote registry.

Uses the REST API of JFrog via the curl C API.
  • Loading branch information
bcumming authored Dec 18, 2024
1 parent b9af6c2 commit 8bf26de
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 13 deletions.
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ if uenv_cli
uenv_src = [
'src/cli/add_remove.cpp',
'src/cli/copy.cpp',
'src/cli/delete.cpp',
'src/cli/find.cpp',
'src/cli/help.cpp',
'src/cli/image.cpp',
Expand Down
7 changes: 4 additions & 3 deletions src/cli/add_remove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ int image_add(const image_add_args& args, const global_settings& settings) {
label.error().message());
return 1;
}
if (!label->partially_qualified()) {
term::error("the label {} does not provide at least name/version:tag",
args.uenv_description);
if (!label->fully_qualified()) {
term::error(
"the label {} must provide at name/version:tag@system%uarch",
args.uenv_description);
return 1;
}

Expand Down
151 changes: 151 additions & 0 deletions src/cli/delete.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// vim: ts=4 sts=4 sw=4 et

#include <string>

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

#include <site/site.h>
#include <uenv/oras.h>
#include <uenv/parse.h>
#include <uenv/print.h>
#include <uenv/repository.h>
#include <util/curl.h>
#include <util/expected.h>
#include <util/fs.h>
#include <util/signal.h>

#include "delete.h"
#include "help.h"
#include "terminal.h"

namespace uenv {

std::string image_delete_footer();

void image_delete_args::add_cli(CLI::App& cli,
[[maybe_unused]] global_settings& settings) {
auto* delete_cli =
cli.add_subcommand("delete", "delete a uenv from a remote registry");
delete_cli
->add_option("uenv", uenv_description,
"either name/version:tag, sha256 or id")
->required();
delete_cli
->add_option(
"--token", token,
"a path that contains a TOKEN file for accessing restricted uenv")
->required();
delete_cli->add_option("--username", username,
"user name for accessing restricted uenv.");
delete_cli->callback(
[&settings]() { settings.mode = uenv::cli_mode::image_delete; });

delete_cli->footer(image_delete_footer);
}

int image_delete([[maybe_unused]] const image_delete_args& args,
[[maybe_unused]] const global_settings& settings) {
uenv::oras::credentials credentials;
if (auto c = site::get_credentials(args.username, args.token)) {
if (!*c) {
term::error("full credentials must be provided", c.error());
}
credentials = (*c).value();
} else {
term::error("{}", c.error());
return 1;
}
spdlog::debug("registry credentials: {}", credentials);

uenv_label label{};
std::string nspace{};
if (const auto parse = parse_uenv_nslabel(args.uenv_description)) {
label = parse->label;
if (!label.name || !parse->nspace) {
term::error("the uenv {} must provide at least a namespace and "
"name, e.g. 'build::f7076704830c8de7'",
args.uenv_description);
return 1;
}
nspace = parse->nspace.value();
} else {
term::error("invalid uenv: {}", parse.error().message());
return 1;
}
spdlog::debug("requested to delete {}::{}", nspace, label);

auto registry = site::registry_listing(nspace);
if (!registry) {
term::error("unable to get a listing of the uenv", registry.error());
return 1;
}

// search db for matching records
const auto matches = registry->query(label);
if (!matches) {
term::error("invalid search term: {}", registry.error());
return 1;
}
// check that there is one record with a unique sha
if (matches->empty()) {
using enum help::block::admonition;
term::error("no uenv found that matches '{}'\n\n{}",
args.uenv_description,
help::block(info, "try searching for the uenv to copy "
"first using 'uenv image find'"));
return 1;
} else if (!matches->unique_sha()) {
std::string errmsg =
fmt::format("more than one sha found that matches '{}':\n",
args.uenv_description);
errmsg += format_record_set(*matches);
term::error("{}", errmsg);
return 1;
}

const auto rego_url = site::registry_url();
spdlog::debug("registry url: {}", rego_url);
for (auto& record : *matches) {
// TODO: create a second pure JFrog API URL (now with added artifictory)
auto url = fmt::format(
"https://jfrog.svc.cscs.ch/artifactory/uenv/{}/{}/{}/{}/{}/{}",
nspace, record.system, record.uarch, record.name, record.version,
record.tag);

if (auto result =
util::curl::del(url, credentials.username, credentials.token);
!result) {
term::error("unable to delete uenv: {}", result.error().message);
return 1;
}

term::msg("delete {}", url);
}

return 0;
}

std::string image_delete_footer() {
using enum help::block::admonition;
std::vector<help::item> items{
// clang-format off
help::block{none, "Delete a uenv from a remote registry." },
help::linebreak{},
help::linebreak{},
help::block{xmpl, "deploy a uenv from build to deploy namespace"},
help::block{code, "uenv image delete build::prgenv-gnu/24.11:1551223269@todi%gh200"},
help::block{code, "uenv image delete build::7890d67458ce7deb"},
help::block{code, "uenv image delete deploy::prgenv-gnu/24.11:rc1@todi"},
help::linebreak{},
help::block{note, "the requested uenv must resolve to a unique sha."},
// clang-format on
};

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

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

#include <optional>
#include <string>

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

#include <uenv/env.h>

#include "uenv.h"

namespace uenv {

struct image_delete_args {
std::string uenv_description;
std::optional<std::string> token;
std::optional<std::string> username;
void add_cli(CLI::App&, global_settings& settings);
};

int image_delete(const image_delete_args& args,
const global_settings& settings);

} // namespace uenv

template <> class fmt::formatter<uenv::image_delete_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(uenv::image_delete_args const& opts,
FmtContext& ctx) const {
return fmt::format_to(ctx.out(), "(image delete {} .token={})",
opts.uenv_description, opts.token);
}
};
4 changes: 4 additions & 0 deletions src/cli/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "add_remove.h"
#include "copy.h"
#include "delete.h"
#include "help.h"
#include "image.h"
#include "inspect.h"
Expand All @@ -34,6 +35,9 @@ void image_args::add_cli(CLI::App& cli,
// add the `uenv image copy` command
copy_args.add_cli(*image_cli, settings);

// add the `uenv image delete` command
delete_args.add_cli(*image_cli, settings);

// add the `uenv image add` command
add_args.add_cli(*image_cli, settings);

Expand Down
2 changes: 2 additions & 0 deletions src/cli/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "add_remove.h"
#include "copy.h"
#include "delete.h"
#include "find.h"
#include "inspect.h"
#include "ls.h"
Expand All @@ -19,6 +20,7 @@ void image_help();
struct image_args {
image_add_args add_args;
image_copy_args copy_args;
image_delete_args delete_args;
image_find_args find_args;
image_inspect_args inspect_args;
image_ls_args ls_args;
Expand Down
3 changes: 3 additions & 0 deletions src/cli/uenv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "add_remove.h"
#include "build.h"
#include "delete.h"
#include "help.h"
#include "image.h"
#include "repo.h"
Expand Down Expand Up @@ -130,6 +131,8 @@ int main(int argc, char** argv) {
return uenv::image_add(image.add_args, settings);
case settings.image_copy:
return uenv::image_copy(image.copy_args, settings);
case settings.image_delete:
return uenv::image_delete(image.delete_args, settings);
case settings.image_inspect:
return uenv::image_inspect(image.inspect_args, settings);
case settings.image_rm:
Expand Down
3 changes: 3 additions & 0 deletions src/cli/uenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class cli_mode : std::uint32_t {
unset,
image_add,
image_copy,
image_delete,
image_find,
image_inspect,
image_ls,
Expand Down Expand Up @@ -68,6 +69,8 @@ template <> class fmt::formatter<uenv::cli_mode> {
return format_to(ctx.out(), "image-add");
case image_copy:
return format_to(ctx.out(), "image-copy");
case image_delete:
return format_to(ctx.out(), "image-delete");
case image_rm:
return format_to(ctx.out(), "image-rm");
case image_find:
Expand Down
3 changes: 1 addition & 2 deletions src/uenv/oras.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ util::expected<void, int> pull_tag(const std::string& registry,
});
while (!proc->finished()) {
std::this_thread::sleep_for(100ms);
// handle a signacl, usually SIGTERM or SIGINT
// handle a signal, usually SIGTERM or SIGINT
if (util::signal_raised()) {
spdlog::warn("signal raised - interrupting download");
throw util::signal_exception(util::last_signal_raised());
Expand Down Expand Up @@ -237,7 +237,6 @@ copy(const std::string& registry, const std::string& src_nspace,
args.push_back(fmt::format("--to-password={}", token->token));
args.push_back(fmt::format("--to-username={}", token->username));
}
// fmt::println("oras {}", fmt::join(args, " "));
auto result = run_oras(args);

if (result.rcode) {
Expand Down
Loading

0 comments on commit 8bf26de

Please # to comment.