diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index 42030d93b..1ab8a0450 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2537,4 +2537,49 @@ On first run, creates the file with all settings commented out at their defaults
Requested load but no input provided.
+
+ Manage volumes.
+
+
+ Manage the lifecycle of WSL volumes, including creating, inspecting, listing, and deleting them.
+ {Locked="WSL"}Product names should not be translated
+
+
+ Create a volume.
+
+
+ Creates a named volume that can be attached to containers.
+
+
+ Remove one or more volumes.
+
+
+ Removes one or more volumes. A volume cannot be removed if it is in use by a container.
+
+
+ Display detailed information on one or more volumes.
+
+
+ Display detailed information on one or more volumes.
+
+
+ List volumes.
+
+
+ Lists all volumes in the session.
+
+
+ Volume name
+
+
+ Specify volume driver name (default {})
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Set driver specific options
+
+
+ Outputs the volume names only
+
+
diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h
index 776674abc..dc79e4a6e 100644
--- a/src/windows/wslc/arguments/ArgumentDefinitions.h
+++ b/src/windows/wslc/arguments/ArgumentDefinitions.h
@@ -45,6 +45,7 @@ _(Detach, "detach", L"d", Kind::Flag, L
/*_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription())*/ \
/*_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription())*/ \
/*_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription())*/ \
+_(Driver, "driver", L"d", Kind::Value, Localization::WSLCCLI_DriverArgDescription("vhd")) \
_(Entrypoint, "entrypoint", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EntrypointArgDescription()) \
_(Env, "env", L"e", Kind::Value, Localization::WSLCCLI_EnvArgDescription()) \
_(EnvFile, "env-file", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EnvFileArgDescription()) \
@@ -62,6 +63,7 @@ _(Name, "name", NO_ALIAS, Kind::Value, L
/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \
_(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoPruneArgDescription()) \
_(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoTruncArgDescription()) \
+_(Options, "opt", L"o", Kind::Value, Localization::WSLCCLI_OptionsArgDescription()) \
_(Output, "output", L"o", Kind::Value, Localization::WSLCCLI_OutputArgDescription()) \
_(Path, "path", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_PathArgDescription()) \
/*_(Progress, "progress", NO_ALIAS, Kind::Value, Localization::WSLCCLI_ProgressArgDescription())*/ \
@@ -85,5 +87,6 @@ _(Verbose, "verbose", NO_ALIAS, Kind::Flag, L
_(Version, "version", L"v", Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \
/*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \
_(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \
+_(VolumeName, "volume-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_VolumeNameArgDescription()) \
_(WorkDir, "workdir", L"w", Kind::Value, Localization::WSLCCLI_WorkingDirArgDescription()) \
// clang-format on
diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp
index 091bc1cdb..8c02582d3 100644
--- a/src/windows/wslc/commands/RootCommand.cpp
+++ b/src/windows/wslc/commands/RootCommand.cpp
@@ -19,6 +19,7 @@ Module Name:
#include "SessionCommand.h"
#include "SettingsCommand.h"
#include "VersionCommand.h"
+#include "VolumeCommand.h"
using namespace wsl::windows::wslc::execution;
using namespace wsl::shared;
@@ -31,6 +32,7 @@ std::vector> RootCommand::GetCommands() const
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
diff --git a/src/windows/wslc/commands/VolumeCommand.cpp b/src/windows/wslc/commands/VolumeCommand.cpp
new file mode 100644
index 000000000..9c171a349
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeCommand.cpp
@@ -0,0 +1,51 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+#include "CLIExecutionContext.h"
+#include "VolumeCommand.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Volume Root Command
+std::vector> VolumeCommand::GetCommands() const
+{
+ std::vector> commands;
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ return commands;
+}
+
+std::vector VolumeCommand::GetArguments() const
+{
+ return {};
+}
+
+std::wstring VolumeCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_VolumeCommandDesc();
+}
+
+std::wstring VolumeCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_VolumeCommandLongDesc();
+}
+
+void VolumeCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ OutputHelp();
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/VolumeCommand.h b/src/windows/wslc/commands/VolumeCommand.h
new file mode 100644
index 000000000..19a2b0634
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeCommand.h
@@ -0,0 +1,95 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeCommand.h
+
+Abstract:
+
+ Declaration of command classes and interfaces.
+
+--*/
+#pragma once
+#include "Command.h"
+
+namespace wsl::windows::wslc {
+// Root Volume Command
+struct VolumeCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"volume";
+ VolumeCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+ std::vector> GetCommands() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Create Command
+struct VolumeCreateCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"create";
+ VolumeCreateCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Remove Command
+struct VolumeRemoveCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"remove";
+ VolumeRemoveCommand(const std::wstring& parent) : Command(CommandName, {L"delete", L"rm"}, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Inspect Command
+struct VolumeInspectCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"inspect";
+ VolumeInspectCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// List Command
+struct VolumeListCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"list";
+ VolumeListCommand(const std::wstring& parent) : Command(CommandName, {L"ls"}, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/VolumeCreateCommand.cpp b/src/windows/wslc/commands/VolumeCreateCommand.cpp
new file mode 100644
index 000000000..fcbe26325
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeCreateCommand.cpp
@@ -0,0 +1,52 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeCreateCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "VolumeCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "VolumeTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Volume Create Command
+std::vector VolumeCreateCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::VolumeName, true),
+ Argument::Create(ArgType::Driver),
+ Argument::Create(ArgType::Options, false, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring VolumeCreateCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_VolumeCreateDesc();
+}
+
+std::wstring VolumeCreateCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_VolumeCreateLongDesc();
+}
+
+void VolumeCreateCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << CreateVolume;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/VolumeInspectCommand.cpp b/src/windows/wslc/commands/VolumeInspectCommand.cpp
new file mode 100644
index 000000000..0c0d196b0
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeInspectCommand.cpp
@@ -0,0 +1,50 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeInspectCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "VolumeCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "VolumeTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Volume Inspect Command
+std::vector VolumeInspectCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::VolumeName, true, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring VolumeInspectCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_VolumeInspectDesc();
+}
+
+std::wstring VolumeInspectCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_VolumeInspectLongDesc();
+}
+
+void VolumeInspectCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << InspectVolumes;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/VolumeListCommand.cpp b/src/windows/wslc/commands/VolumeListCommand.cpp
new file mode 100644
index 000000000..e8f336b50
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeListCommand.cpp
@@ -0,0 +1,65 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeListCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "VolumeCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "VolumeTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+
+namespace wsl::windows::wslc {
+// Volume List Command
+std::vector VolumeListCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::Format),
+ Argument::Create(ArgType::Quiet, false, std::nullopt, Localization::WSLCCLI_VolumeListQuietArgDesc()),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring VolumeListCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_VolumeListDesc();
+}
+
+std::wstring VolumeListCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_VolumeListLongDesc();
+}
+
+void VolumeListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
+{
+ if (execArgs.Contains(ArgType::Format))
+ {
+ auto format = execArgs.Get();
+ if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
+ {
+ throw CommandException(Localization::WSLCCLI_InvalidFormatError());
+ }
+ }
+}
+
+void VolumeListCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << GetVolumes //
+ << ListVolumes;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/VolumeRemoveCommand.cpp b/src/windows/wslc/commands/VolumeRemoveCommand.cpp
new file mode 100644
index 000000000..59bb57136
--- /dev/null
+++ b/src/windows/wslc/commands/VolumeRemoveCommand.cpp
@@ -0,0 +1,50 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeRemoveCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "VolumeCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "VolumeTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Volume Delete Command
+std::vector VolumeRemoveCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::VolumeName, true, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring VolumeRemoveCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_VolumeRemoveDesc();
+}
+
+std::wstring VolumeRemoveCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_VolumeRemoveLongDesc();
+}
+
+void VolumeRemoveCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << DeleteVolumes;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/core/ExecutionContextData.h b/src/windows/wslc/core/ExecutionContextData.h
index ca550670c..f48c44951 100644
--- a/src/windows/wslc/core/ExecutionContextData.h
+++ b/src/windows/wslc/core/ExecutionContextData.h
@@ -16,6 +16,7 @@ Module Name:
#include "ContainerModel.h"
#include "ImageModel.h"
#include "SessionModel.h"
+#include "VolumeModel.h"
#include
@@ -36,6 +37,7 @@ enum class Data : size_t
Containers,
ContainerOptions,
Images,
+ Volumes,
Max
};
@@ -50,6 +52,7 @@ namespace details {
DEFINE_DATA_MAPPING(Containers, std::vector);
DEFINE_DATA_MAPPING(ContainerOptions, wsl::windows::wslc::models::ContainerOptions);
DEFINE_DATA_MAPPING(Images, std::vector);
+ DEFINE_DATA_MAPPING(Volumes, std::vector);
} // namespace details
struct DataMap : wsl::windows::wslc::EnumBasedVariantMap
diff --git a/src/windows/wslc/services/VolumeModel.h b/src/windows/wslc/services/VolumeModel.h
new file mode 100644
index 000000000..308a91561
--- /dev/null
+++ b/src/windows/wslc/services/VolumeModel.h
@@ -0,0 +1,30 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeModel.h
+
+Abstract:
+
+ This file contains the VolumeModel definitions
+
+--*/
+
+#pragma once
+
+#include "JsonUtils.h"
+#include
+
+namespace wsl::windows::wslc::models {
+
+struct VolumeInformation
+{
+ std::string Name;
+ std::string Type;
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE(VolumeInformation, Name, Type);
+};
+
+} // namespace wsl::windows::wslc::models
diff --git a/src/windows/wslc/services/VolumeService.cpp b/src/windows/wslc/services/VolumeService.cpp
new file mode 100644
index 000000000..305625592
--- /dev/null
+++ b/src/windows/wslc/services/VolumeService.cpp
@@ -0,0 +1,61 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeService.cpp
+
+Abstract:
+
+ This file contains the VolumeService implementation
+
+--*/
+#include "VolumeService.h"
+#include
+
+using namespace wsl::shared;
+using namespace wsl::windows::common::wslutil;
+
+namespace wsl::windows::wslc::services {
+
+void VolumeService::Create(models::Session& session, const std::string& name, const std::string& type, const std::string& opt)
+{
+ WSLCVolumeOptions options{};
+ options.Name = name.c_str();
+ options.Type = type.c_str();
+ options.Options = opt.c_str();
+ THROW_IF_FAILED(session.Get()->CreateVolume(&options));
+}
+
+void VolumeService::Delete(models::Session& session, const std::string& name)
+{
+ THROW_IF_FAILED(session.Get()->DeleteVolume(name.c_str()));
+}
+
+std::vector VolumeService::List(models::Session& session)
+{
+ wil::unique_cotaskmem_array_ptr rawVolumes;
+ ULONG count = 0;
+ THROW_IF_FAILED(session.Get()->ListVolumes(&rawVolumes, &count));
+
+ std::vector volumes;
+ volumes.reserve(count);
+ for (auto ptr = rawVolumes.get(), end = rawVolumes.get() + count; ptr != end; ++ptr)
+ {
+ models::VolumeInformation info;
+ info.Name = ptr->Name;
+ info.Type = ptr->Type;
+ volumes.push_back(std::move(info));
+ }
+
+ return volumes;
+}
+
+wsl::windows::common::wslc_schema::InspectVolume VolumeService::Inspect(models::Session& session, const std::string& name)
+{
+ wil::unique_cotaskmem_ansistring output;
+ THROW_IF_FAILED(session.Get()->InspectVolume(name.c_str(), &output));
+ return FromJson(output.get());
+}
+} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/services/VolumeService.h b/src/windows/wslc/services/VolumeService.h
new file mode 100644
index 000000000..b9b688c77
--- /dev/null
+++ b/src/windows/wslc/services/VolumeService.h
@@ -0,0 +1,28 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeService.h
+
+Abstract:
+
+ This file contains the VolumeService definition
+
+--*/
+#pragma once
+
+#include "SessionModel.h"
+#include "VolumeModel.h"
+#include
+
+namespace wsl::windows::wslc::services {
+struct VolumeService
+{
+ static void Create(models::Session& session, const std::string& name, const std::string& type, const std::string& opt);
+ static void Delete(models::Session& session, const std::string& name);
+ static std::vector List(models::Session& session);
+ static wsl::windows::common::wslc_schema::InspectVolume Inspect(models::Session& session, const std::string& name);
+};
+} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp
new file mode 100644
index 000000000..490fff15b
--- /dev/null
+++ b/src/windows/wslc/tasks/VolumeTasks.cpp
@@ -0,0 +1,153 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeTasks.cpp
+
+Abstract:
+
+ Implementation of volume command related execution logic.
+
+--*/
+#include "Argument.h"
+#include "ArgumentValidation.h"
+#include "CLIExecutionContext.h"
+#include "VolumeModel.h"
+#include "VolumeService.h"
+#include "VolumeTasks.h"
+#include "TableOutput.h"
+#include
+
+using namespace wsl::shared;
+using namespace wsl::windows::common::string;
+using namespace wsl::windows::common::wslutil;
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::models;
+using namespace wsl::windows::wslc::services;
+
+namespace wsl::windows::wslc::task {
+
+static std::string OptionsToJson(const std::vector& options)
+{
+ std::map result{};
+ for (const auto& option : options)
+ {
+ auto pos = option.find('=');
+ if (pos == std::wstring::npos)
+ {
+ result[WideToMultiByte(option)] = {};
+ }
+ else
+ {
+ auto key = WideToMultiByte(option.substr(0, pos));
+ auto value = WideToMultiByte(option.substr(pos + 1));
+ result[key] = value;
+ }
+ }
+
+ return ToJson(result);
+}
+
+void CreateVolume(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ WI_ASSERT(context.Args.Contains(ArgType::VolumeName));
+
+ auto name = WideToMultiByte(context.Args.Get());
+
+ // Driver option (default "vhd")
+ std::string type = "vhd";
+ if (context.Args.Contains(ArgType::Driver))
+ {
+ type = WideToMultiByte(context.Args.Get());
+ }
+
+ auto optionsJson = OptionsToJson(context.Args.GetAll());
+ VolumeService::Create(context.Data.Get(), name, type, optionsJson);
+ PrintMessage(MultiByteToWide(name));
+}
+
+void DeleteVolumes(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ auto volumeNames = context.Args.GetAll();
+ for (const auto& name : volumeNames)
+ {
+ VolumeService::Delete(session, WideToMultiByte(name));
+ }
+}
+
+void GetVolumes(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ context.Data.Add(VolumeService::List(session));
+}
+
+void InspectVolumes(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ auto volumeNames = context.Args.GetAll();
+ std::vector result;
+ for (const auto& name : volumeNames)
+ {
+ auto inspectData = VolumeService::Inspect(session, WideToMultiByte(name));
+ result.push_back(inspectData);
+ }
+
+ auto json = ToJson(result, c_jsonPrettyPrintIndent);
+ PrintMessage(MultiByteToWide(json));
+}
+
+void ListVolumes(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Volumes));
+ auto& volumes = context.Data.Get();
+
+ if (context.Args.Contains(ArgType::Quiet))
+ {
+ for (const auto& volume : volumes)
+ {
+ PrintMessage(MultiByteToWide(volume.Name));
+ }
+
+ return;
+ }
+
+ FormatType format = FormatType::Table;
+ if (context.Args.Contains(ArgType::Format))
+ {
+ format = validation::GetFormatTypeFromString(context.Args.Get());
+ }
+
+ switch (format)
+ {
+ case FormatType::Json:
+ {
+ auto json = ToJson(volumes, c_jsonPrettyPrintIndent);
+ PrintMessage(MultiByteToWide(json));
+ break;
+ }
+ case FormatType::Table:
+ {
+ auto table = wsl::windows::wslc::TableOutput<2>({L"DRIVER", L"VOLUME NAME"});
+ for (const auto& volume : volumes)
+ {
+ table.OutputLine({
+ MultiByteToWide(volume.Type),
+ MultiByteToWide(volume.Name),
+ });
+ }
+
+ table.Complete();
+ break;
+ }
+ default:
+ THROW_HR(E_UNEXPECTED);
+ }
+}
+} // namespace wsl::windows::wslc::task
diff --git a/src/windows/wslc/tasks/VolumeTasks.h b/src/windows/wslc/tasks/VolumeTasks.h
new file mode 100644
index 000000000..d6fe39479
--- /dev/null
+++ b/src/windows/wslc/tasks/VolumeTasks.h
@@ -0,0 +1,24 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ VolumeTasks.h
+
+Abstract:
+
+ Declaration of volume command execution tasks.
+
+--*/
+#pragma once
+#include "CLIExecutionContext.h"
+
+namespace wsl::windows::wslc::task {
+
+void CreateVolume(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void DeleteVolumes(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void GetVolumes(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void InspectVolumes(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void ListVolumes(wsl::windows::wslc::execution::CLIExecutionContext& context);
+} // namespace wsl::windows::wslc::task
diff --git a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
index 83d2d5a32..a5eeb6dd3 100644
--- a/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
+++ b/test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
@@ -97,6 +97,12 @@ class WSLCCLIExecutionUnitTests
dataMap.Add(std::move(images));
handled = true;
}
+ else if (dataType == Data::Volumes)
+ {
+ std::vector volumes;
+ dataMap.Add(std::move(volumes));
+ handled = true;
+ }
if (!handled)
{
diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
index 043a02656..4c0352966 100644
--- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
@@ -411,6 +411,7 @@ class WSLCE2EGlobalTests
{L"image", Localization::WSLCCLI_ImageCommandDesc()},
{L"session", Localization::WSLCCLI_SessionCommandDesc()},
{L"settings", Localization::WSLCCLI_SettingsCommandDesc()},
+ {L"volume", Localization::WSLCCLI_VolumeCommandDesc()},
{L"attach", Localization::WSLCCLI_ContainerAttachDesc()},
{L"build", Localization::WSLCCLI_ImageBuildDesc()},
{L"create", Localization::WSLCCLI_ContainerCreateDesc()},
diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
index f0e0e2420..4f376ad75 100644
--- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
@@ -14,6 +14,7 @@ Module Name:
#include "precomp.h"
#include "SessionModel.h"
#include "ImageModel.h"
+#include "VolumeModel.h"
#include "windows/Common.h"
#include "WSLCExecutor.h"
#include "WSLCE2EHelpers.h"
@@ -212,6 +213,36 @@ void VerifyImageIsListed(const TestImage& image)
VERIFY_FAIL(std::format(L"Image '{}' not found in image list output", image.NameAndTag()).c_str());
}
+void VerifyVolumeIsListed(const std::wstring& volumeName)
+{
+ auto result = RunWslc(L"volume list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto volumes = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& vol : volumes)
+ {
+ if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
+ {
+ return;
+ }
+ }
+
+ VERIFY_FAIL(std::format(L"Volume '{}' not found in volume list output", volumeName).c_str());
+}
+
+void VerifyVolumeIsNotListed(const std::wstring& volumeName)
+{
+ auto result = RunWslc(L"volume list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto volumes = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& vol : volumes)
+ {
+ if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
+ {
+ VERIFY_FAIL(std::format(L"Volume '{}' found in volume list output", volumeName).c_str());
+ }
+ }
+}
+
std::string GetHashId(const std::string& id, bool fullId)
{
return wsl::windows::common::string::TruncateId(id, !fullId);
@@ -235,6 +266,15 @@ wslc_schema::InspectImage InspectImage(const std::wstring& imageName)
return inspectData[0];
}
+wslc_schema::InspectVolume InspectVolume(const std::wstring& volumeName)
+{
+ auto result = RunWslc(std::format(L"volume inspect {}", volumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ return inspectData[0];
+}
+
void EnsureContainerDoesNotExist(const std::wstring& containerName)
{
auto listResult = RunWslc(L"container list --no-trunc --all");
@@ -360,6 +400,22 @@ void EnsureSessionIsTerminated(const std::wstring& sessionName)
}
}
+void EnsureVolumeDoesNotExist(const std::wstring& volumeName)
+{
+ auto result = RunWslc(L"volume list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto volumes = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& vol : volumes)
+ {
+ if (vol.Name == wsl::shared::string::WideToMultiByte(volumeName))
+ {
+ auto deleteResult = RunWslc(std::format(L"volume rm {}", volumeName));
+ deleteResult.Verify({.Stderr = L"", .ExitCode = 0});
+ break;
+ }
+ }
+}
+
void WriteTestFile(const std::filesystem::path& filePath, const std::vector& lines)
{
std::ofstream file(filePath, std::ios::out | std::ios::trunc | std::ios::binary);
diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.h b/test/windows/wslc/e2e/WSLCE2EHelpers.h
index a09bdefec..06f97177a 100644
--- a/test/windows/wslc/e2e/WSLCE2EHelpers.h
+++ b/test/windows/wslc/e2e/WSLCE2EHelpers.h
@@ -120,10 +120,13 @@ void VerifyContainerIsListed(const std::wstring& containerName, const std::wstri
void VerifyImageIsUsed(const TestImage& image);
void VerifyImageIsNotUsed(const TestImage& image);
void VerifyImageIsListed(const TestImage& image);
+void VerifyVolumeIsListed(const std::wstring& volumeName);
+void VerifyVolumeIsNotListed(const std::wstring& volumeName);
std::string GetHashId(const std::string& id, bool fullId = false);
wsl::windows::common::wslc_schema::InspectContainer InspectContainer(const std::wstring& containerName);
wsl::windows::common::wslc_schema::InspectImage InspectImage(const std::wstring& imageName);
+wsl::windows::common::wslc_schema::InspectVolume InspectVolume(const std::wstring& volumeName);
std::vector ListAllContainers();
void EnsureContainerDoesNotExist(const std::wstring& containerName);
@@ -131,6 +134,7 @@ void EnsureImageIsLoaded(const TestImage& image, const std::wstring& sessionName
void EnsureImageIsDeleted(const TestImage& image);
void EnsureImageContainersAreDeleted(const TestImage& image);
void EnsureSessionIsTerminated(const std::wstring& sessionName = L"");
+void EnsureVolumeDoesNotExist(const std::wstring& volumeName);
void WriteTestFile(const std::filesystem::path& filePath, const std::vector& envVariableLines);
std::wstring GetPythonHttpServerScript(uint16_t port);
diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp
new file mode 100644
index 000000000..ee0e4b42f
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp
@@ -0,0 +1,128 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EVolumeCreateTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "VolumeModel.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+using namespace wsl::windows::wslc::models;
+
+class WSLCE2EVolumeCreateTests
+{
+ WSLC_TEST_CLASS(WSLCE2EVolumeCreateTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Create_HelpCommand)
+ {
+ auto result = RunWslc(L"volume create --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Create_MissingVolumeName)
+ {
+ auto result = RunWslc(L"volume create");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'volume-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Create_DefaultDriver_Success)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestVolumeName, result.GetStdoutOneLine());
+
+ VerifyVolumeIsListed(TestVolumeName);
+ auto inspect = InspectVolume(TestVolumeName);
+ VERIFY_ARE_EQUAL("vhd", inspect.Type);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Create_ExplicitDriver_Success)
+ {
+ auto result = RunWslc(std::format(L"volume create --driver vhd --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestVolumeName, result.GetStdoutOneLine());
+
+ VerifyVolumeIsListed(TestVolumeName);
+ auto inspect = InspectVolume(TestVolumeName);
+ VERIFY_ARE_EQUAL("vhd", inspect.Type);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Create_InvalidDriver)
+ {
+ auto result =
+ RunWslc(std::format(L"volume create --driver invalid_driver --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stdout = L"", .Stderr = L"Unsupported volume type: 'invalid_driver'\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
+ VerifyVolumeIsNotListed(TestVolumeName);
+ }
+
+private:
+ const std::wstring TestVolumeName = L"wslc-e2e-volume-create";
+ const int DefaultVolumeSizeBytes = 3 * 1024 * 1024;
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_VolumeCreateLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc volume create [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" volume-name Volume name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" -d,--driver Specify volume driver name (default vhd)\r\n" //
+ << L" -o,--opt Set driver specific options\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -h,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp
new file mode 100644
index 000000000..9b31e4320
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp
@@ -0,0 +1,148 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EVolumeInspectTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+
+class WSLCE2EVolumeInspectTests
+{
+ WSLC_TEST_CLASS(WSLCE2EVolumeInspectTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName1);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName1);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Inspect_HelpCommand)
+ {
+ auto result = RunWslc(L"volume inspect --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Inspect_MissingVolumeName)
+ {
+ auto result = RunWslc(L"volume inspect");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'volume-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Inspect_Success)
+ {
+ auto result = RunWslc(std::format(L"volume create --driver vhd --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestVolumeName1, result.GetStdoutOneLine());
+
+ result = RunWslc(std::format(L"volume inspect {}", TestVolumeName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ auto inspect = inspectData[0];
+
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName1), inspect.Name);
+ VERIFY_ARE_EQUAL("vhd", inspect.Type);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_InspectMultiple_Success)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestVolumeName1, result.GetStdoutOneLine());
+ result = RunWslc(std::format(L"volume create --driver vhd --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestVolumeName2, result.GetStdoutOneLine());
+
+ result = RunWslc(std::format(L"volume inspect {} {}", TestVolumeName1, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(2u, inspectData.size());
+
+ auto inspect1 = inspectData[0];
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName1), inspect1.Name);
+ VERIFY_ARE_EQUAL("vhd", inspect1.Type);
+
+ auto inspect2 = inspectData[1];
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestVolumeName2), inspect2.Name);
+ VERIFY_ARE_EQUAL("vhd", inspect2.Type);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Inspect_NotFound)
+ {
+ auto result = RunWslc(std::format(L"volume inspect {}", TestVolumeName1));
+ result.Verify(
+ {.Stdout = L"",
+ .Stderr = std::format(L"Volume not found: '{}'\r\nError code: WSLC_E_VOLUME_NOT_FOUND\r\n", TestVolumeName1),
+ .ExitCode = 1});
+ }
+
+private:
+ const std::wstring TestVolumeName1 = L"wslc-e2e-volume-inspect-1";
+ const std::wstring TestVolumeName2 = L"wslc-e2e-volume-inspect-2";
+ const int DefaultVolumeSizeBytes = 3 * 1024 * 1024;
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_VolumeInspectLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc volume inspect [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" volume-name Volume name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -h,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp
new file mode 100644
index 000000000..3f7efde88
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp
@@ -0,0 +1,137 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EVolumeListTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "VolumeModel.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+using namespace wsl::windows::wslc::models;
+
+class WSLCE2EVolumeListTests
+{
+ WSLC_TEST_CLASS(WSLCE2EVolumeListTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_List_HelpCommand)
+ {
+ auto result = RunWslc(L"volume list --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_List_InvalidFormatOption)
+ {
+ auto result = RunWslc(L"volume list --format invalid");
+ result.Verify({.Stderr = L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table.\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_List_QuietOption_OutputsNamesOnly)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(L"volume list --quiet");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto lines = result.GetStdoutLines();
+ VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestVolumeName));
+ VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestVolumeName2));
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_List_JsonFormat)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(L"volume list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto volumes = FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(2U, volumes.size());
+
+ std::vector names;
+ names.reserve(volumes.size());
+ for (const auto& volume : volumes)
+ {
+ names.push_back(volume.Name);
+ }
+
+ VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestVolumeName)));
+ VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestVolumeName2)));
+ }
+
+private:
+ const std::wstring TestVolumeName = L"wslc-e2e-volume-list";
+ const std::wstring TestVolumeName2 = L"wslc-e2e-volume-list-2";
+ const int DefaultVolumeSizeBytes = 3 * 1024 * 1024;
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommandAliases() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_VolumeListLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc volume list []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommandAliases() const
+ {
+ return L"The following command aliases are available: ls\r\n\r\n";
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --format " << Localization::WSLCCLI_FormatArgDescription() << L"\r\n"
+ << L" -q,--quiet Outputs the volume names only\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -h,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp
new file mode 100644
index 000000000..abb44a8b6
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp
@@ -0,0 +1,142 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EVolumeRemoveTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2EVolumeRemoveTests
+{
+ WSLC_TEST_CLASS(WSLCE2EVolumeRemoveTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureVolumeDoesNotExist(TestVolumeName);
+ EnsureVolumeDoesNotExist(TestVolumeName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Remove_HelpCommand)
+ {
+ auto result = RunWslc(L"volume remove --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Remove_MissingVolumeName)
+ {
+ auto result = RunWslc(L"volume remove");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'volume-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Remove_Valid)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyVolumeIsListed(TestVolumeName);
+
+ result = RunWslc(std::format(L"volume remove {}", TestVolumeName));
+ result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
+
+ VerifyVolumeIsNotListed(TestVolumeName);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Remove_Multiple_Valid)
+ {
+ auto result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"volume create --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyVolumeIsListed(TestVolumeName);
+ VerifyVolumeIsListed(TestVolumeName2);
+
+ result = RunWslc(std::format(L"volume remove {} {}", TestVolumeName, TestVolumeName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyVolumeIsNotListed(TestVolumeName);
+ VerifyVolumeIsNotListed(TestVolumeName2);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_Remove_NotFound)
+ {
+ auto result = RunWslc(std::format(L"volume remove {}", TestVolumeName));
+ result.Verify(
+ {.Stdout = L"",
+ .Stderr = std::format(L"Volume not found: '{}'\r\nError code: WSLC_E_VOLUME_NOT_FOUND\r\n", TestVolumeName),
+ .ExitCode = 1});
+ }
+
+private:
+ const std::wstring TestVolumeName = L"wslc-e2e-volume-remove";
+ const std::wstring TestVolumeName2 = L"wslc-e2e-volume-remove-2";
+ const int DefaultVolumeSizeBytes = 3 * 1024 * 1024;
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommandAliases() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_VolumeRemoveLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc volume remove [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommandAliases() const
+ {
+ return L"The following command aliases are available: delete rm\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" volume-name Volume name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -h,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp
new file mode 100644
index 000000000..211a7a18a
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp
@@ -0,0 +1,101 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EVolumeTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+#include "Argument.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2EVolumeTests
+{
+ WSLC_TEST_CLASS(WSLCE2EVolumeTests)
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_HelpCommand)
+ {
+ auto result = RunWslc(L"volume --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_NoSubcommand_ShowsHelp)
+ {
+ auto result = RunWslc(L"volume");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Volume_InvalidCommand_DisplaysErrorMessage)
+ {
+ auto result = RunWslc(L"volume INVALID_CMD");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Unrecognized command: 'INVALID_CMD'\r\n", .ExitCode = 1});
+ }
+
+private:
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_VolumeCommandLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc volume [] []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::vector> entries = {
+ {L"create", Localization::WSLCCLI_VolumeCreateDesc()},
+ {L"remove", Localization::WSLCCLI_VolumeRemoveDesc()},
+ {L"inspect", Localization::WSLCCLI_VolumeInspectDesc()},
+ {L"list", Localization::WSLCCLI_VolumeListDesc()},
+ };
+
+ size_t maxLen = 0;
+ for (const auto& [name, _] : entries)
+ {
+ maxLen = (std::max)(maxLen, name.size());
+ }
+
+ std::wstringstream commands;
+ commands << Localization::WSLCCLI_AvailableSubcommands() << L"\r\n";
+ for (const auto& [name, desc] : entries)
+ {
+ commands << L" " << name << std::wstring(maxLen - name.size() + 2, L' ') << desc << L"\r\n";
+ }
+ commands << L"\r\n" << Localization::WSLCCLI_HelpForDetails() << L" [" << WSLC_CLI_HELP_ARG_STRING << L"]\r\n\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n"
+ << L" -h,--help Shows help about the selected command\r\n"
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
\ No newline at end of file