Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion completions/bash/multipass
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ _multipass_complete()
_add_nonrepeating_args "--all --cancel --time"
;;
"find")
_add_nonrepeating_args "--show-unsupported --force-update --format"
_add_nonrepeating_args "--show-unsupported --force-update --format --only-cached"
;;
"unalias")
_multipass_aliases
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/command-line-interface/find.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The list of available images is updated periodically. The option `--force-update

The option `--show-unsupported` includes old Ubuntu images, which were available at some point but are not supported anymore. This means that some features of Multipass might now work on these images and no user support is given. However, they are still available for testing.

The option `--only-cached` limits output to images that have already been downloaded and are stored locally. This is useful when you want to launch an instance without any network access, or when you want to know which images are ready for immediate use.

The command also supports searching through available images. For example, `multipass find mantic` returns:

```{code-block} text
Expand All @@ -59,6 +61,7 @@ Options:
--format <format> Output list in the requested format.
Valid formats are: table (default), json, csv and yaml
--force-update Force the image information to update from the network
--only-cached Show only locally cached images

Arguments:
string An optional value to search for in [<remote:>]<string>
Expand Down
1 change: 1 addition & 0 deletions include/multipass/vm_image_vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class VMImageVault : private DisabledCopyMove
virtual VMImageHost* image_host_for(const std::string& remote_name) const = 0;
virtual std::vector<std::pair<std::string, VMImageInfo>> all_info_for(
const Query& query) const = 0;
virtual std::vector<std::pair<std::string, VMImage>> cached_images() const = 0;

protected:
VMImageVault() = default;
Expand Down
11 changes: 10 additions & 1 deletion src/client/cli/cmd/find.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ mp::ParseCode cmd::Find::parse_args(mp::ArgParser* parser)
const QCommandLineOption force_manifest_network_download(
"force-update",
"Force the image information to update from the network");
parser->addOptions({unsupportedOption, formatOption, force_manifest_network_download});
QCommandLineOption onlyCachedOption("only-cached", "Show only locally cached images");
parser->addOptions(
{unsupportedOption, formatOption, force_manifest_network_download, onlyCachedOption});

auto status = parser->commandParse(this);

Expand All @@ -91,6 +93,12 @@ mp::ParseCode cmd::Find::parse_args(mp::ArgParser* parser)
return status;
}

if (parser->isSet(onlyCachedOption) && parser->positionalArguments().count() > 0)
{
cerr << "Cannot use --only-cached with a search string\n";
return ParseCode::CommandLineError;
}

if (parser->positionalArguments().count() > 1)
{
cerr << "Wrong number of arguments\n";
Expand Down Expand Up @@ -119,6 +127,7 @@ mp::ParseCode cmd::Find::parse_args(mp::ArgParser* parser)

request.set_allow_unsupported(parser->isSet(unsupportedOption));
request.set_force_manifest_network_download(parser->isSet(force_manifest_network_download));
request.set_only_cached(parser->isSet(onlyCachedOption));

status = handle_format_option(parser, &chosen_formatter, cerr);

Expand Down
26 changes: 26 additions & 0 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,32 @@ try
server};
FindReply response;

if (request->only_cached())
{
auto cached = config->vault->cached_images();
for (const auto& [id, image] : cached)
{
auto entry = response.add_images_info();
entry->set_os(image.os);
entry->set_release(image.original_release);
entry->set_version(image.release_date);

if (!image.aliases.empty())
{
for (const auto& alias : image.aliases)
entry->add_aliases(alias);
}
else
{
entry->add_aliases(id.substr(0, 12));
}
}

server->Write(response);
status_promise->set_value(grpc::Status::OK);
return;
}

if (!request->search_string().empty())
{
if (!request->remote_name().empty())
Expand Down
12 changes: 12 additions & 0 deletions src/daemon/default_vm_image_vault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,15 @@ void mp::DefaultVMImageVault::amend_db()
}
}
}

std::vector<std::pair<std::string, mp::VMImage>> mp::DefaultVMImageVault::cached_images() const
{
std::vector<std::pair<std::string, VMImage>> images;

for (const auto& record : prepared_image_records)
{
images.emplace_back(record.first, record.second.image);
}

return images;
}
1 change: 1 addition & 0 deletions src/daemon/default_vm_image_vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class DefaultVMImageVault final : public BaseVMImageVault
MemorySize minimum_image_size_for(const std::string& id) override;
void clone(const std::string& source_instance_name,
const std::string& destination_instance_name) override;
std::vector<std::pair<std::string, VMImage>> cached_images() const override;

private:
VMImage image_instance_from(const VMImage& prepared_image, const Path& dest_dir);
Expand Down
1 change: 1 addition & 0 deletions src/rpc/multipass.proto
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ message FindRequest {
int32 verbosity_level = 3;
bool allow_unsupported = 4;
bool force_manifest_network_download = 5;
bool only_cached = 6;
}

message FindReply {
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/mock_vm_image_vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class MockVMImageVault : public VMImageVault
all_info_for,
(const Query&),
(const, override));
MOCK_METHOD((std::vector<std::pair<std::string, VMImage>>),
cached_images,
(),
(const, override));

private:
TempFile dummy_image;
Expand Down
11 changes: 8 additions & 3 deletions tests/unit/stub_vm_image_vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ struct StubVMImageVault final : public multipass::VMImageVault
return prepare({dummy_image.name(), {}, {}, {}, {}, {}, {}});
};

void remove(const std::string&) override{};
void remove(const std::string&) override {};
bool has_record_for(const std::string&) override
{
return false;
}

void prune_expired_images() override{};
void prune_expired_images() override {};
void update_images(const FetchType& fetch_type,
const PrepareAction& prepare,
const ProgressMonitor& monitor) override{};
const ProgressMonitor& monitor) override {};

MemorySize minimum_image_size_for(const std::string& image) override
{
Expand Down Expand Up @@ -83,6 +83,11 @@ struct StubVMImageVault final : public multipass::VMImageVault
{
}

std::vector<std::pair<std::string, VMImage>> cached_images() const override
{
return {};
}

TempFile dummy_image;
};
} // namespace test
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/test_daemon_find.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,44 @@ TEST_F(DaemonFind, findForceUpdateRemoteSearchNameCheckUpdateManifestsCalls)

send_command({"find", "release:22.04", "--force-update"});
}

TEST_F(DaemonFind, onlyCachedReturnsCachedImages)
{
auto mock_image_vault = std::make_unique<NiceMock<mpt::MockVMImageVault>>();

mp::VMImage cached_image;
cached_image.id = "abc123def456";
cached_image.original_release = "22.04 LTS";
cached_image.release_date = "20240101";
cached_image.os = "Ubuntu";
cached_image.aliases = {"jammy"};

EXPECT_CALL(*mock_image_vault, cached_images())
.WillOnce(Return(
std::vector<std::pair<std::string, mp::VMImage>>{{"abc123def456full", cached_image}}));

config_builder.vault = std::move(mock_image_vault);
mp::Daemon daemon{config_builder.build()};

std::stringstream stream;
send_command({"find", "--only-cached"}, stream);

EXPECT_THAT(stream.str(),
AllOf(HasSubstr("jammy"), HasSubstr("22.04 LTS"), HasSubstr("Ubuntu")));
}

TEST_F(DaemonFind, onlyCachedEmptyReturnsNoImages)
{
auto mock_image_vault = std::make_unique<NiceMock<mpt::MockVMImageVault>>();

EXPECT_CALL(*mock_image_vault, cached_images())
.WillOnce(Return(std::vector<std::pair<std::string, mp::VMImage>>{}));

config_builder.vault = std::move(mock_image_vault);
mp::Daemon daemon{config_builder.build()};

std::stringstream stream;
send_command({"find", "--only-cached"}, stream);

EXPECT_THAT(stream.str(), HasSubstr("No images found."));
}