Skip to content

Commit

Permalink
Merge pull request #3203 from canonical/move-snapshot-overview-to-list
Browse files Browse the repository at this point in the history
[snapshots] Move snapshot overview to list

a=sharder996 r=ricab
  • Loading branch information
ricab committed Oct 16, 2023
2 parents fa4323e + d504c1b commit 25869b8
Show file tree
Hide file tree
Showing 15 changed files with 666 additions and 726 deletions.
107 changes: 34 additions & 73 deletions include/multipass/cli/format_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,8 @@ std::string status_string_for(const InstanceStatus& status);
std::string image_string_for(const multipass::FindReply_AliasInfo& alias);
Formatter* formatter_for(const std::string& format);

template <typename Instances>
Instances sorted(const Instances& instances);

template <typename Snapshots>
Snapshots sort_snapshots(const Snapshots& snapshots);

template <typename Details>
Details sort_instances_and_snapshots(const Details& details);
template <typename Container>
Container sorted(const Container& items);

void filter_aliases(google::protobuf::RepeatedPtrField<multipass::FindReply_AliasInfo>& aliases);

Expand All @@ -65,82 +59,49 @@ static constexpr auto column_width = [](const auto begin, const auto end, const
} // namespace format
} // namespace multipass

template <typename Instances>
Instances multipass::format::sorted(const Instances& instances)
{
if (instances.empty())
return instances;

auto ret = instances;
const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString();
std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) {
if (a.name() == petenv_name)
return true;
else if (b.name() == petenv_name)
return false;
else
return a.name() < b.name();
});

return ret;
}

// TODO@snapshots DRY
template <typename Snapshots>
Snapshots multipass::format::sort_snapshots(const Snapshots& snapshots)
template <typename Container>
Container multipass::format::sorted(const Container& items)
{
using google::protobuf::util::TimeUtil;
if (snapshots.empty())
return snapshots;
if (items.empty())
return items;

auto ret = snapshots;
auto ret = items;
const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString();
std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) {
if (a.instance_name() == petenv_name && b.instance_name() != petenv_name)
return true;
else if (a.instance_name() != petenv_name && b.instance_name() == petenv_name)
return false;

if (a.instance_name() < b.instance_name())
return true;
else if (a.instance_name() > b.instance_name())
return false;

return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp());
});

return ret;
}

template <typename Details>
Details multipass::format::sort_instances_and_snapshots(const Details& details)
{
using google::protobuf::util::TimeUtil;
if (details.empty())
return details;
using T = std::decay_t<decltype(a)>;
using google::protobuf::util::TimeUtil;

auto ret = details;
const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString();
std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) {
if (a.has_instance_info() && b.has_snapshot_info())
return true;
else if (a.has_snapshot_info() && b.has_instance_info())
return false;
// Put instances first when sorting info reply
if constexpr (std::is_same_v<T, multipass::DetailedInfoItem>)
{
if (a.has_instance_info() && b.has_snapshot_info())
return true;
else if (a.has_snapshot_info() && b.has_instance_info())
return false;
}

// Put petenv related entries first
if (a.name() == petenv_name && b.name() != petenv_name)
return true;
else if (a.name() != petenv_name && b.name() == petenv_name)
else if (b.name() == petenv_name && a.name() != petenv_name)
return false;

if (a.has_instance_info())
return a.name() < b.name();
else
{
if (a.name() == b.name())
return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp());

// Sort by timestamp when names are the same for snapshots
if constexpr (std::is_same_v<T, multipass::DetailedInfoItem>)
{
if (a.has_snapshot_info() && a.name() == b.name())
return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp());
}
else if constexpr (std::is_same_v<T, multipass::ListVMSnapshot>)
{
if (a.name() == b.name())
return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp());
}

// Lastly, sort by name
return a.name() < b.name();
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/client/cli/cmd/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser)
QStringList split_exec_dir = clean_exec_dir.split('/');

auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) {
for (const auto& mount : reply.detailed_report().details(0).mount_info().mount_paths())
for (const auto& mount : reply.details(0).mount_info().mount_paths())
{
auto source_dir = QDir(QString::fromStdString(mount.source_path()));
auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath());
Expand Down
4 changes: 1 addition & 3 deletions src/client/cli/cmd/info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser)
format_option_name,
"Output info in the requested format.\nValid formats are: table (default), json, csv and yaml",
format_option_name, "table");
QCommandLineOption snapshotOverviewOption("snapshot-overview", "Display info on snapshots");

parser->addOptions({all_option, noRuntimeInfoOption, formatOption, snapshotOverviewOption});
parser->addOptions({all_option, noRuntimeInfoOption, formatOption});

auto status = parser->commandParse(this);
if (status != ParseCode::Ok)
Expand All @@ -96,7 +95,6 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser)
}

request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption));
request.set_snapshot_overview(parser->isSet(snapshotOverviewOption));

if (instance_found && snapshot_found && parser->value(format_option_name) == "csv")
{
Expand Down
15 changes: 11 additions & 4 deletions src/client/cli/cmd/list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,24 @@ std::vector<std::string> cmd::List::aliases() const

QString cmd::List::short_help() const
{
return QStringLiteral("List all available instances");
return QStringLiteral("List all available instances or snapshots");
}

QString cmd::List::description() const
{
return QStringLiteral("List all instances which have been created.");
return QStringLiteral("List all instances or snapshots which have been created.");
}

mp::ParseCode cmd::List::parse_args(mp::ArgParser* parser)
{
QCommandLineOption snapshotsOption("snapshots", "List all available snapshots");
QCommandLineOption formatOption(
"format", "Output list in the requested format.\nValid formats are: table (default), json, csv and yaml",
"format", "table");

QCommandLineOption noIpv4Option("no-ipv4", "Do not query the instances for the IPv4's they are using");
noIpv4Option.setFlags(QCommandLineOption::HiddenFromHelp);

parser->addOptions({formatOption, noIpv4Option});
parser->addOptions({snapshotsOption, formatOption, noIpv4Option});

auto status = parser->commandParse(this);

Expand All @@ -91,6 +91,13 @@ mp::ParseCode cmd::List::parse_args(mp::ArgParser* parser)
return ParseCode::CommandLineError;
}

if (parser->isSet(snapshotsOption) && parser->isSet(noIpv4Option))
{
cerr << "IP addresses are not applicable in conjunction with listing snapshots\n";
return ParseCode::CommandLineError;
}

request.set_snapshots(parser->isSet(snapshotsOption));
request.set_request_ipv4(!parser->isSet(noIpv4Option));

status = handle_format_option(parser, &chosen_formatter, cerr);
Expand Down
64 changes: 32 additions & 32 deletions src/client/cli/formatter/csv_formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ std::string format_images(const google::protobuf::RepeatedPtrField<mp::FindReply
std::string format_mounts(const mp::MountInfo& mount_info)
{
fmt::memory_buffer buf;
auto mount_paths = mount_info.mount_paths();
const auto& mount_paths = mount_info.mount_paths();

if (!mount_paths.size())
return {};
Expand All @@ -69,7 +69,7 @@ std::string generate_snapshot_details(const mp::InfoReply reply)
fmt::format_to(std::back_inserter(buf),
"Snapshot,Instance,CPU(s),Disk space,Memory size,Mounts,Created,Parent,Children,Comment\n");

for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details()))
for (const auto& info : mp::format::sorted(reply.details()))
{
const auto& fundamentals = info.snapshot_info().fundamentals();

Expand All @@ -95,7 +95,7 @@ std::string generate_instance_details(const mp::InfoReply reply)
"Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory "
"total,Mounts,AllIPv4,CPU(s),Snapshots\n");

for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details()))
for (const auto& info : mp::format::sorted(reply.details()))
{
assert(info.has_instance_info() &&
"outputting instance and snapshot details together is not supported in csv format");
Expand All @@ -118,31 +118,35 @@ std::string generate_instance_details(const mp::InfoReply reply)
return fmt::to_string(buf);
}

std::string generate_instance_info_report(const mp::InfoReply& reply)
std::string generate_instances_list(const mp::InstancesList& instance_list)
{
std::string output;
fmt::memory_buffer buf;

if (reply.detailed_report().details_size() > 0)
fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n");

for (const auto& instance : mp::format::sorted(instance_list.instances()))
{
if (reply.detailed_report().details()[0].has_instance_info())
output = generate_instance_details(reply);
else
output = generate_snapshot_details(reply);
fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(),
mp::format::status_string_for(instance.instance_status()),
instance.ipv4_size() ? instance.ipv4(0) : "", instance.ipv6_size() ? instance.ipv6(0) : "",
instance.current_release().empty() ? "Not Available"
: fmt::format("Ubuntu {}", instance.current_release()),
fmt::join(instance.ipv4(), ","));
}

return output;
return fmt::to_string(buf);
}

std::string generate_snapshot_overview_report(const mp::InfoReply& reply)
std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list)
{
fmt::memory_buffer buf;

fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n");

for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview()))
for (const auto& item : mp::format::sorted(snapshot_list.snapshots()))
{
const auto& snapshot = item.fundamentals();
fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.instance_name(), snapshot.snapshot_name(),
fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.name(), snapshot.snapshot_name(),
snapshot.parent(), snapshot.comment());
}

Expand All @@ -154,36 +158,32 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const
{
std::string output;

if (reply.has_detailed_report())
{
output = generate_instance_info_report(reply);
}
else
if (reply.details_size() > 0)
{
assert(reply.has_snapshot_overview() && "either one of the reports should be populated");
output = generate_snapshot_overview_report(reply);
if (reply.details()[0].has_instance_info())
output = generate_instance_details(reply);
else
output = generate_snapshot_details(reply);
}

return output;
}

std::string mp::CSVFormatter::format(const ListReply& reply) const
{
fmt::memory_buffer buf;

fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n");
std::string output;

for (const auto& instance : format::sorted(reply.instances()))
if (reply.has_instance_list())
{
fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(),
mp::format::status_string_for(instance.instance_status()),
instance.ipv4_size() ? instance.ipv4(0) : "", instance.ipv6_size() ? instance.ipv6(0) : "",
instance.current_release().empty() ? "Not Available"
: fmt::format("Ubuntu {}", instance.current_release()),
fmt::join(instance.ipv4(), ","));
output = generate_instances_list(reply.instance_list());
}
else
{
assert(reply.has_snapshot_list() && "either one of instances or snapshots should be populated");
output = generate_snapshots_list(reply.snapshot_list());
}

return fmt::to_string(buf);
return output;
}

std::string mp::CSVFormatter::format(const NetworksReply& reply) const
Expand Down
Loading

0 comments on commit 25869b8

Please sign in to comment.