diff --git a/completions/bash/multipass b/completions/bash/multipass index 4b0493872f6..3e00201b044 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -200,7 +200,7 @@ _multipass_complete() opts="${opts} --working-directory --no-map-working-directory" ;; "info") - _add_nonrepeating_args "--format" + _add_nonrepeating_args "--format --snapshots" ;; "list"|"ls"|"networks"|"aliases") _add_nonrepeating_args "--format" diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 7d618b9ad3b..22ab54133ba 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -62,19 +62,23 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "Names of instances or snapshots to display information about", "[.snapshot] [[.snapshot] ...]"); - QCommandLineOption all_option(all_option_name, "Display info for all instances"); + QCommandLineOption all_option(all_option_name, "Display info for all instances."); all_option.setFlags(QCommandLineOption::HiddenFromHelp); QCommandLineOption noRuntimeInfoOption( "no-runtime-information", - "Retrieve from the daemon only the information obtained without running commands on the instance"); + "Retrieve from the daemon only the information obtained without running commands on the instance."); noRuntimeInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); - QCommandLineOption formatOption( + QCommandLineOption snapshots_option{"snapshots", + "Display detailed information about the snapshots of specified instances. This " + "option has no effect on snapshot arguments. Omit instance/snapshot arguments " + "to obtain detailed information on all the snapshots of all instances."}; + QCommandLineOption format_option( format_option_name, - "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", + "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml.", format_option_name, "table"); - parser->addOptions({all_option, noRuntimeInfoOption, formatOption}); + parser->addOptions({all_option, noRuntimeInfoOption, snapshots_option, format_option}); auto status = parser->commandParse(this); if (status != ParseCode::Ok) @@ -103,7 +107,10 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); - if (instance_found && snapshot_found && parser->value(format_option_name) == "csv") + const auto& snapshots_only = parser->isSet(snapshots_option); + request.set_snapshots(snapshots_only); + + if (instance_found && snapshot_found && parser->value(format_option_name) == "csv" && !snapshots_only) { cerr << "Mixed snapshot and instance arguments are not supported with CSV format\n"; return ParseCode::CommandLineError; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6f916a85762..5995b19fff3 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1727,25 +1727,44 @@ try // clang-format on InstanceSnapshotsMap instance_snapshots_map; bool have_mounts = false; bool deleted = false; + bool snapshots_only = request->snapshots(); + + auto populate_info = [&](VirtualMachine& vm, const std::shared_ptr& snapshot) { + auto* details = response.add_details(); + if (snapshot) + populate_snapshot_info(vm, snapshot, details, have_mounts); // TODO@snapshots remove have_mounts + else + populate_instance_info(vm, details, request->no_runtime_information(), deleted, have_mounts); + }; + + auto process_snapshot_pick = [populate_info, snapshots_only](VirtualMachine& vm, + const SnapshotPick& snapshot_pick) { + for (const auto& snapshot_name : snapshot_pick.pick) + { + const auto snapshot = vm.get_snapshot(snapshot_name); // verify validity even if unused + if (!snapshot_pick.all_or_none || !snapshots_only) + populate_info(vm, snapshot); + } + }; auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; const auto& it = instance_snapshots_map.find(name); - const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + const auto& snapshot_pick = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; try { - if (all_or_none) - populate_instance_info(vm, - response.add_details(), - request->no_runtime_information(), - deleted, - have_mounts); - - for (const auto& snapshot : pick) - populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.add_details(), have_mounts); + process_snapshot_pick(vm, snapshot_pick); + if (snapshot_pick.all_or_none) + { + if (snapshots_only) + for (const auto& snapshot : vm.view_snapshots()) + populate_info(vm, snapshot); + else + populate_info(vm, nullptr); + } } catch (const NoSuchSnapshot& e) { diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index f21328314b5..7c1aa503dc7 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -169,6 +169,7 @@ message InfoRequest { repeated InstanceSnapshotPair instances_snapshots = 1; int32 verbosity_level = 3; bool no_runtime_information = 4; + bool snapshots = 5; } message IdMap {