From b851ae906f411dc3057f94b64002a02a49b655d1 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:24:55 -0700 Subject: [PATCH 01/13] Init volume command --- localization/strings/en-US/Resources.resw | 37 +++++ .../wslc/arguments/ArgumentDefinitions.h | 2 + .../wslc/arguments/ArgumentValidation.cpp | 27 ++++ .../wslc/arguments/ArgumentValidation.h | 1 + src/windows/wslc/commands/RootCommand.cpp | 2 + src/windows/wslc/commands/VolumeCommand.cpp | 51 +++++++ src/windows/wslc/commands/VolumeCommand.h | 95 +++++++++++++ .../wslc/commands/VolumeCreateCommand.cpp | 54 ++++++++ .../wslc/commands/VolumeDeleteCommand.cpp | 53 +++++++ .../wslc/commands/VolumeInspectCommand.cpp | 53 +++++++ .../wslc/commands/VolumeListCommand.cpp | 68 +++++++++ src/windows/wslc/core/ExecutionContextData.h | 3 + src/windows/wslc/services/VolumeModel.h | 30 ++++ src/windows/wslc/services/VolumeService.cpp | 63 +++++++++ src/windows/wslc/services/VolumeService.h | 28 ++++ src/windows/wslc/tasks/VolumeTasks.cpp | 130 ++++++++++++++++++ src/windows/wslc/tasks/VolumeTasks.h | 28 ++++ .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 67 +++++++++ .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 67 +++++++++ 19 files changed, 859 insertions(+) create mode 100644 src/windows/wslc/commands/VolumeCommand.cpp create mode 100644 src/windows/wslc/commands/VolumeCommand.h create mode 100644 src/windows/wslc/commands/VolumeCreateCommand.cpp create mode 100644 src/windows/wslc/commands/VolumeDeleteCommand.cpp create mode 100644 src/windows/wslc/commands/VolumeInspectCommand.cpp create mode 100644 src/windows/wslc/commands/VolumeListCommand.cpp create mode 100644 src/windows/wslc/services/VolumeModel.h create mode 100644 src/windows/wslc/services/VolumeService.cpp create mode 100644 src/windows/wslc/services/VolumeService.h create mode 100644 src/windows/wslc/tasks/VolumeTasks.cpp create mode 100644 src/windows/wslc/tasks/VolumeTasks.h diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 462c7b36a..9d2a313a1 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2505,4 +2505,41 @@ 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 + diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index 3748a95cd..ab9522bdc 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -83,5 +83,7 @@ _(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()) \ +_(VolumeDriver, "driver", L"d", Kind::Value, Localization::WSLCCLI_VolumeDriverArgDescription()) \ +_(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/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index ea47a5f55..35022fbb8 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -47,6 +47,10 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateVolumeMount(execArgs.GetAll()); break; + case ArgType::VolumeDriver: + validation::ValidateVolumeDriver(execArgs.GetAll(), m_name); + break; + case ArgType::WorkDir: { const auto& value = execArgs.Get(); @@ -97,6 +101,29 @@ void ValidateVolumeMount(const std::vector& values) } } +void ValidateVolumeDriver(const std::vector& values, const std::wstring& argName) +{ + static const std::vector supportedDrivers = {L"vhd"}; + for (const auto& value : values) + { + bool found = false; + for (const auto& driver : supportedDrivers) + { + if (IsEqual(value, driver)) + { + found = true; + break; + } + } + + if (!found) + { + throw ArgumentException(std::format( + L"Invalid {} value: {} is not a recognized volume driver. Supported drivers are: vhd.", argName, value)); + } + } +} + // Convert string to WSLCSignal enum - accepts either signal name (e.g., "SIGKILL") or number (e.g., "9") WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName) { diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 23208699c..673d63edd 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -61,5 +61,6 @@ void ValidateFormatTypeFromString(const std::vector& values, const FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {}); void ValidateVolumeMount(const std::vector& values); +void ValidateVolumeDriver(const std::vector& values, const std::wstring& argName); } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp index 732c19404..4d665ee3e 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..4e38ea574 --- /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..82346c760 --- /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; +}; + +// Delete Command +struct VolumeDeleteCommand final : public Command +{ + constexpr static std::wstring_view CommandName = L"remove"; + VolumeDeleteCommand(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..4f9b7f29d --- /dev/null +++ b/src/windows/wslc/commands/VolumeCreateCommand.cpp @@ -0,0 +1,54 @@ +/*++ + +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::VolumeDriver), + Argument::Create(ArgType::Session), + }; +} + +std::wstring VolumeCreateCommand::ShortDescription() const +{ + return Localization::WSLCCLI_VolumeCreateDesc(); +} + +std::wstring VolumeCreateCommand::LongDescription() const +{ + return Localization::WSLCCLI_VolumeCreateLongDesc(); +} + +// clang-format off +void VolumeCreateCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + context + << CreateSession + << CreateVolume; +} +// clang-format on +} // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/VolumeDeleteCommand.cpp b/src/windows/wslc/commands/VolumeDeleteCommand.cpp new file mode 100644 index 000000000..395fbd0fb --- /dev/null +++ b/src/windows/wslc/commands/VolumeDeleteCommand.cpp @@ -0,0 +1,53 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VolumeDeleteCommand.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 VolumeDeleteCommand::GetArguments() const +{ + return { + Argument::Create(ArgType::VolumeName, true, NO_LIMIT), + Argument::Create(ArgType::Session), + }; +} + +std::wstring VolumeDeleteCommand::ShortDescription() const +{ + return Localization::WSLCCLI_VolumeDeleteDesc(); +} + +std::wstring VolumeDeleteCommand::LongDescription() const +{ + return Localization::WSLCCLI_VolumeDeleteLongDesc(); +} + +// clang-format off +void VolumeDeleteCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + context + << CreateSession + << DeleteVolumes; +} +// clang-format on +} // 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..c84c2d4bb --- /dev/null +++ b/src/windows/wslc/commands/VolumeInspectCommand.cpp @@ -0,0 +1,53 @@ +/*++ + +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(); +} + +// clang-format off +void VolumeInspectCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + context + << CreateSession + << InspectVolumes; +} +// clang-format on +} // 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..ccbfed72c --- /dev/null +++ b/src/windows/wslc/commands/VolumeListCommand.cpp @@ -0,0 +1,68 @@ +/*++ + +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), + 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()); + } + } +} + +// clang-format off +void VolumeListCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + context + << CreateSession + << GetVolumes + << ListVolumes; +} +// clang-format on +} // 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..609eb31cf --- /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 +#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..0f4ba13d4 --- /dev/null +++ b/src/windows/wslc/services/VolumeService.cpp @@ -0,0 +1,63 @@ +/*++ + +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) +{ + WSLCVolumeOptions options{}; + options.Name = name.c_str(); + options.Type = type.c_str(); + options.Options = nullptr; + + 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..41ed222eb --- /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); + 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..8d39823d6 --- /dev/null +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -0,0 +1,130 @@ +/*++ + +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 { + +void CreateVolume(CLIExecutionContext& context) +{ + WI_ASSERT(context.Data.Contains(Data::Session)); + WI_ASSERT(context.Args.Contains(ArgType::VolumeName)); + + auto name = WideToMultiByte(context.Args.Get()); + std::string type = "vhd"; + if (context.Args.Contains(ArgType::VolumeDriver)) + { + type = WideToMultiByte(context.Args.Get()); + } + + VolumeService::Create(context.Data.Get(), name, type); + 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"VOLUME NAME", L"DRIVER"}); + for (const auto& volume : volumes) + { + table.OutputLine({ + MultiByteToWide(volume.Name), + MultiByteToWide(volume.Type), + }); + } + + 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..b1c49acfd --- /dev/null +++ b/src/windows/wslc/tasks/VolumeTasks.h @@ -0,0 +1,28 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + VolumeTasks.h + +Abstract: + + Declaration of volume command execution tasks. + +--*/ +#pragma once +#include "CLIExecutionContext.h" +#include "Task.h" + +using wsl::windows::wslc::execution::CLIExecutionContext; + +namespace wsl::windows::wslc::task { + +void CreateVolume(CLIExecutionContext& context); +void DeleteVolumes(CLIExecutionContext& context); +void GetVolumes(CLIExecutionContext& context); +void InspectVolumes(CLIExecutionContext& context); +void ListVolumes(CLIExecutionContext& context); + +} // namespace wsl::windows::wslc::task diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index cda1f24b1..f863c600d 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -1081,6 +1081,73 @@ class WSLCE2EContainerCreateTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NamedVolume_WriteFromContainerReadFromContainer) + { + + const std::wstring volumeName = L"wslc-test-vol-create"; + + // Cleanup any pre-existing volume + RunWslc(std::format(L"volume rm {}", volumeName)); + + // Create the named volume + auto result = RunWslc(std::format(L"volume create {}", volumeName)); + result.Verify({.Stderr = L"", .ExitCode = S_OK}); + + // Write data to the volume using a container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} sh -c \"echo -n 'named_volume_data' > /data/testfile\"", + volumeName, + AlpineImage.NameAndTag())); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + + // Read data back from the volume using another container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} cat /data/testfile", + volumeName, + AlpineImage.NameAndTag())); + result.Verify({.Stdout = L"named_volume_data", .Stderr = L"", .ExitCode = S_OK}); + + // Cleanup + RunWslc(std::format(L"volume rm {}", volumeName)); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NamedVolume_PersistsBetweenContainers) + { + + const std::wstring volumeName = L"wslc-test-vol-persist"; + + // Cleanup any pre-existing volume + RunWslc(std::format(L"volume rm {}", volumeName)); + + // Create the named volume + auto result = RunWslc(std::format(L"volume create {}", volumeName)); + result.Verify({.Stderr = L"", .ExitCode = S_OK}); + + // Write data using first container + result = RunWslc(std::format( + L"container create --name {} --volume {}:/data {} sh -c \"echo -n 'persist_test' > /data/testfile\"", + WslcContainerName, + volumeName, + AlpineImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = S_OK}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + + // Remove first container + RunWslc(std::format(L"container rm {}", WslcContainerName)); + + // Read data using second container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} cat /data/testfile", + volumeName, + AlpineImage.NameAndTag())); + result.Verify({.Stdout = L"persist_test", .Stderr = L"", .ExitCode = S_OK}); + + // Cleanup + RunWslc(std::format(L"volume rm {}", volumeName)); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 85f166890..7ef632f85 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -170,6 +170,73 @@ class WSLCE2EContainerRunTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NamedVolume_WriteAndRead) + { + + const std::wstring volumeName = L"wslc-test-vol-run"; + + // Cleanup any pre-existing volume + RunWslc(std::format(L"volume rm {}", volumeName)); + + // Create the named volume + auto result = RunWslc(std::format(L"volume create {}", volumeName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Write data to the named volume + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} sh -c \"echo -n 'run_vol_test' > /data/testfile\"", + volumeName, + DebianImage.NameAndTag())); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); + + // Read data back from the named volume in a new container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} cat /data/testfile", + volumeName, + DebianImage.NameAndTag())); + result.Verify({.Stdout = L"run_vol_test", .Stderr = L"", .ExitCode = 0}); + + // Cleanup + RunWslc(std::format(L"volume rm {}", volumeName)); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NamedVolume_MultipleContainers) + { + + const std::wstring volumeName = L"wslc-test-vol-run-multi"; + + // Cleanup any pre-existing volume + RunWslc(std::format(L"volume rm {}", volumeName)); + + // Create the named volume + auto result = RunWslc(std::format(L"volume create {}", volumeName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Write data with first container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} sh -c \"echo -n 'first' > /data/testfile\"", + volumeName, + DebianImage.NameAndTag())); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); + + // Append data with second container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} sh -c \"echo -n '_second' >> /data/testfile\"", + volumeName, + DebianImage.NameAndTag())); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); + + // Read combined data with third container + result = RunWslc(std::format( + L"container run --rm --volume {}:/data {} cat /data/testfile", + volumeName, + DebianImage.NameAndTag())); + result.Verify({.Stdout = L"first_second", .Stderr = L"", .ExitCode = 0}); + + // Cleanup + RunWslc(std::format(L"volume rm {}", volumeName)); + } + private: const std::wstring WslcContainerName = L"wslc-test-container"; const TestImage& DebianImage = DebianTestImage(); From 89c64d58ffba5fa7cbe1beb6989958dff94ac188 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:19:09 -0700 Subject: [PATCH 02/13] WIP --- src/windows/wslc/arguments/ArgumentDefinitions.h | 3 ++- src/windows/wslc/arguments/ArgumentValidation.cpp | 4 ++-- src/windows/wslc/commands/VolumeCreateCommand.cpp | 5 +++-- src/windows/wslc/services/VolumeService.cpp | 5 ++--- src/windows/wslc/services/VolumeService.h | 2 +- src/windows/wslc/tasks/VolumeTasks.cpp | 13 +++++++++---- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index ab9522bdc..5102ee7ef 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, L"Specify volume driver name (default 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, L"Set driver specific options") \ _(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())*/ \ @@ -83,7 +85,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()) \ -_(VolumeDriver, "driver", L"d", Kind::Value, Localization::WSLCCLI_VolumeDriverArgDescription()) \ _(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/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 35022fbb8..2e5eb5378 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -47,8 +47,8 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateVolumeMount(execArgs.GetAll()); break; - case ArgType::VolumeDriver: - validation::ValidateVolumeDriver(execArgs.GetAll(), m_name); + case ArgType::Driver: + // validation::ValidateVolumeDriver(execArgs.GetAll(), m_name); break; case ArgType::WorkDir: diff --git a/src/windows/wslc/commands/VolumeCreateCommand.cpp b/src/windows/wslc/commands/VolumeCreateCommand.cpp index 4f9b7f29d..fd029541e 100644 --- a/src/windows/wslc/commands/VolumeCreateCommand.cpp +++ b/src/windows/wslc/commands/VolumeCreateCommand.cpp @@ -27,8 +27,9 @@ namespace wsl::windows::wslc { std::vector VolumeCreateCommand::GetArguments() const { return { - Argument::Create(ArgType::VolumeName, true), - Argument::Create(ArgType::VolumeDriver), + Argument::Create(ArgType::VolumeName), + Argument::Create(ArgType::Driver), + Argument::Create(ArgType::Options), Argument::Create(ArgType::Session), }; } diff --git a/src/windows/wslc/services/VolumeService.cpp b/src/windows/wslc/services/VolumeService.cpp index 0f4ba13d4..16c3aad94 100644 --- a/src/windows/wslc/services/VolumeService.cpp +++ b/src/windows/wslc/services/VolumeService.cpp @@ -19,13 +19,12 @@ 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) +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 = nullptr; - + options.Options = opt.c_str(); THROW_IF_FAILED(session.Get()->CreateVolume(&options)); } diff --git a/src/windows/wslc/services/VolumeService.h b/src/windows/wslc/services/VolumeService.h index 41ed222eb..b9b688c77 100644 --- a/src/windows/wslc/services/VolumeService.h +++ b/src/windows/wslc/services/VolumeService.h @@ -20,7 +20,7 @@ Module Name: namespace wsl::windows::wslc::services { struct VolumeService { - static void Create(models::Session& session, const std::string& name, const std::string& type); + 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); diff --git a/src/windows/wslc/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp index 8d39823d6..370ed9a50 100644 --- a/src/windows/wslc/tasks/VolumeTasks.cpp +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -32,16 +32,21 @@ namespace wsl::windows::wslc::task { void CreateVolume(CLIExecutionContext& context) { WI_ASSERT(context.Data.Contains(Data::Session)); - WI_ASSERT(context.Args.Contains(ArgType::VolumeName)); auto name = WideToMultiByte(context.Args.Get()); std::string type = "vhd"; - if (context.Args.Contains(ArgType::VolumeDriver)) + if (context.Args.Contains(ArgType::Driver)) { - type = WideToMultiByte(context.Args.Get()); + type = WideToMultiByte(context.Args.Get()); } - VolumeService::Create(context.Data.Get(), name, type); + std::string options{}; + if(context.Args.Contains(ArgType::Options)) + { + options = WideToMultiByte(context.Args.Get()); + } + + VolumeService::Create(context.Data.Get(), name, type, options); PrintMessage(MultiByteToWide(name)); } From 2fa9ce80bd4c20548674759d8eaf7106585c5d27 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:53:49 -0700 Subject: [PATCH 03/13] Create --- .../wslc/arguments/ArgumentValidation.cpp | 27 --------------- .../wslc/commands/VolumeCreateCommand.cpp | 8 ++--- src/windows/wslc/tasks/VolumeTasks.cpp | 33 +++++++++++++++---- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 2e5eb5378..ea47a5f55 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -47,10 +47,6 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateVolumeMount(execArgs.GetAll()); break; - case ArgType::Driver: - // validation::ValidateVolumeDriver(execArgs.GetAll(), m_name); - break; - case ArgType::WorkDir: { const auto& value = execArgs.Get(); @@ -101,29 +97,6 @@ void ValidateVolumeMount(const std::vector& values) } } -void ValidateVolumeDriver(const std::vector& values, const std::wstring& argName) -{ - static const std::vector supportedDrivers = {L"vhd"}; - for (const auto& value : values) - { - bool found = false; - for (const auto& driver : supportedDrivers) - { - if (IsEqual(value, driver)) - { - found = true; - break; - } - } - - if (!found) - { - throw ArgumentException(std::format( - L"Invalid {} value: {} is not a recognized volume driver. Supported drivers are: vhd.", argName, value)); - } - } -} - // Convert string to WSLCSignal enum - accepts either signal name (e.g., "SIGKILL") or number (e.g., "9") WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName) { diff --git a/src/windows/wslc/commands/VolumeCreateCommand.cpp b/src/windows/wslc/commands/VolumeCreateCommand.cpp index fd029541e..ea49c3142 100644 --- a/src/windows/wslc/commands/VolumeCreateCommand.cpp +++ b/src/windows/wslc/commands/VolumeCreateCommand.cpp @@ -27,9 +27,9 @@ namespace wsl::windows::wslc { std::vector VolumeCreateCommand::GetArguments() const { return { - Argument::Create(ArgType::VolumeName), + Argument::Create(ArgType::VolumeName, true), Argument::Create(ArgType::Driver), - Argument::Create(ArgType::Options), + Argument::Create(ArgType::Options, false, NO_LIMIT), Argument::Create(ArgType::Session), }; } @@ -44,12 +44,10 @@ std::wstring VolumeCreateCommand::LongDescription() const return Localization::WSLCCLI_VolumeCreateLongDesc(); } -// clang-format off void VolumeCreateCommand::ExecuteInternal(CLIExecutionContext& context) const { context - << CreateSession + << CreateSession // << CreateVolume; } -// clang-format on } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp index 370ed9a50..2710c8501 100644 --- a/src/windows/wslc/tasks/VolumeTasks.cpp +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -29,24 +29,43 @@ 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()); } - std::string options{}; - if(context.Args.Contains(ArgType::Options)) - { - options = WideToMultiByte(context.Args.Get()); - } - - VolumeService::Create(context.Data.Get(), name, type, options); + auto optionsJson = OptionsToJson(context.Args.GetAll()); + VolumeService::Create(context.Data.Get(), name, type, optionsJson); PrintMessage(MultiByteToWide(name)); } From 5029cf8d39aa67da7ac1525addad48c565aad961 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:56:13 -0700 Subject: [PATCH 04/13] CLang format --- src/windows/wslc/commands/VolumeCreateCommand.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/windows/wslc/commands/VolumeCreateCommand.cpp b/src/windows/wslc/commands/VolumeCreateCommand.cpp index ea49c3142..fcbe26325 100644 --- a/src/windows/wslc/commands/VolumeCreateCommand.cpp +++ b/src/windows/wslc/commands/VolumeCreateCommand.cpp @@ -46,8 +46,7 @@ std::wstring VolumeCreateCommand::LongDescription() const void VolumeCreateCommand::ExecuteInternal(CLIExecutionContext& context) const { - context - << CreateSession // - << CreateVolume; + context << CreateSession // + << CreateVolume; } } // namespace wsl::windows::wslc From 506a52c1812fe1befaa7c2d021bf0cec665c2b0e Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:59:30 -0700 Subject: [PATCH 05/13] Volume command --- src/windows/wslc/commands/VolumeInspectCommand.cpp | 7 ++----- src/windows/wslc/commands/VolumeListCommand.cpp | 9 +++------ src/windows/wslc/services/VolumeService.cpp | 1 - src/windows/wslc/tasks/VolumeTasks.cpp | 5 ++--- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/windows/wslc/commands/VolumeInspectCommand.cpp b/src/windows/wslc/commands/VolumeInspectCommand.cpp index c84c2d4bb..0c0d196b0 100644 --- a/src/windows/wslc/commands/VolumeInspectCommand.cpp +++ b/src/windows/wslc/commands/VolumeInspectCommand.cpp @@ -42,12 +42,9 @@ std::wstring VolumeInspectCommand::LongDescription() const return Localization::WSLCCLI_VolumeInspectLongDesc(); } -// clang-format off void VolumeInspectCommand::ExecuteInternal(CLIExecutionContext& context) const { - context - << CreateSession - << InspectVolumes; + context << CreateSession // + << InspectVolumes; } -// clang-format on } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/VolumeListCommand.cpp b/src/windows/wslc/commands/VolumeListCommand.cpp index ccbfed72c..460610fc1 100644 --- a/src/windows/wslc/commands/VolumeListCommand.cpp +++ b/src/windows/wslc/commands/VolumeListCommand.cpp @@ -56,13 +56,10 @@ void VolumeListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const } } -// clang-format off void VolumeListCommand::ExecuteInternal(CLIExecutionContext& context) const { - context - << CreateSession - << GetVolumes - << ListVolumes; + context << CreateSession // + << GetVolumes // + << ListVolumes; } -// clang-format on } // namespace wsl::windows::wslc diff --git a/src/windows/wslc/services/VolumeService.cpp b/src/windows/wslc/services/VolumeService.cpp index 16c3aad94..305625592 100644 --- a/src/windows/wslc/services/VolumeService.cpp +++ b/src/windows/wslc/services/VolumeService.cpp @@ -58,5 +58,4 @@ wsl::windows::common::wslc_schema::InspectVolume VolumeService::Inspect(models:: 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/tasks/VolumeTasks.cpp b/src/windows/wslc/tasks/VolumeTasks.cpp index 2710c8501..490fff15b 100644 --- a/src/windows/wslc/tasks/VolumeTasks.cpp +++ b/src/windows/wslc/tasks/VolumeTasks.cpp @@ -134,12 +134,12 @@ void ListVolumes(CLIExecutionContext& context) } case FormatType::Table: { - auto table = wsl::windows::wslc::TableOutput<2>({L"VOLUME NAME", L"DRIVER"}); + auto table = wsl::windows::wslc::TableOutput<2>({L"DRIVER", L"VOLUME NAME"}); for (const auto& volume : volumes) { table.OutputLine({ - MultiByteToWide(volume.Name), MultiByteToWide(volume.Type), + MultiByteToWide(volume.Name), }); } @@ -150,5 +150,4 @@ void ListVolumes(CLIExecutionContext& context) THROW_HR(E_UNEXPECTED); } } - } // namespace wsl::windows::wslc::task From 02ba8898d8fa41683e0e133c51103ec742db3261 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:46:10 -0700 Subject: [PATCH 06/13] Added E2E tests --- .../wslc/commands/VolumeListCommand.cpp | 4 +- .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 10 +- .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 22 +-- test/windows/wslc/e2e/WSLCE2EHelpers.cpp | 56 +++++++ test/windows/wslc/e2e/WSLCE2EHelpers.h | 4 + .../wslc/e2e/WSLCE2EVolumeCreateTests.cpp | 127 +++++++++++++++ .../wslc/e2e/WSLCE2EVolumeInspectTests.cpp | 144 ++++++++++++++++++ .../wslc/e2e/WSLCE2EVolumeListTests.cpp | 137 +++++++++++++++++ .../wslc/e2e/WSLCE2EVolumeRemoveTests.cpp | 139 +++++++++++++++++ test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp | 101 ++++++++++++ 10 files changed, 717 insertions(+), 27 deletions(-) create mode 100644 test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp create mode 100644 test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp create mode 100644 test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp create mode 100644 test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp create mode 100644 test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp diff --git a/src/windows/wslc/commands/VolumeListCommand.cpp b/src/windows/wslc/commands/VolumeListCommand.cpp index 460610fc1..8dafd47c9 100644 --- a/src/windows/wslc/commands/VolumeListCommand.cpp +++ b/src/windows/wslc/commands/VolumeListCommand.cpp @@ -29,7 +29,7 @@ std::vector VolumeListCommand::GetArguments() const { return { Argument::Create(ArgType::Format), - Argument::Create(ArgType::Quiet), + Argument::Create(ArgType::Quiet, false, std::nullopt, L"Outputs the volume names only"), Argument::Create(ArgType::Session), }; } @@ -59,7 +59,7 @@ void VolumeListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const void VolumeListCommand::ExecuteInternal(CLIExecutionContext& context) const { context << CreateSession // - << GetVolumes // + << GetVolumes // << ListVolumes; } } // namespace wsl::windows::wslc diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 271c0b843..70acf7b0a 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -552,10 +552,7 @@ class WSLCE2EContainerCreateTests result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); // Read data back from the volume using another container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} cat /data/testfile", - volumeName, - AlpineImage.NameAndTag())); + result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); result.Verify({.Stdout = L"named_volume_data", .Stderr = L"", .ExitCode = S_OK}); // Cleanup @@ -589,10 +586,7 @@ class WSLCE2EContainerCreateTests RunWslc(std::format(L"container rm {}", WslcContainerName)); // Read data using second container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} cat /data/testfile", - volumeName, - AlpineImage.NameAndTag())); + result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); result.Verify({.Stdout = L"persist_test", .Stderr = L"", .ExitCode = S_OK}); // Cleanup diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 277e6eb23..c9481dcbe 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -549,16 +549,11 @@ class WSLCE2EContainerRunTests // Write data to the named volume result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n 'run_vol_test' > /data/testfile\"", - volumeName, - DebianImage.NameAndTag())); + L"container run --rm --volume {}:/data {} sh -c \"echo -n 'run_vol_test' > /data/testfile\"", volumeName, DebianImage.NameAndTag())); result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Read data back from the named volume in a new container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} cat /data/testfile", - volumeName, - DebianImage.NameAndTag())); + result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, DebianImage.NameAndTag())); result.Verify({.Stdout = L"run_vol_test", .Stderr = L"", .ExitCode = 0}); // Cleanup @@ -579,23 +574,16 @@ class WSLCE2EContainerRunTests // Write data with first container result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n 'first' > /data/testfile\"", - volumeName, - DebianImage.NameAndTag())); + L"container run --rm --volume {}:/data {} sh -c \"echo -n 'first' > /data/testfile\"", volumeName, DebianImage.NameAndTag())); result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Append data with second container result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n '_second' >> /data/testfile\"", - volumeName, - DebianImage.NameAndTag())); + L"container run --rm --volume {}:/data {} sh -c \"echo -n '_second' >> /data/testfile\"", volumeName, DebianImage.NameAndTag())); result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Read combined data with third container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} cat /data/testfile", - volumeName, - DebianImage.NameAndTag())); + result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, DebianImage.NameAndTag())); result.Verify({.Stdout = L"first_second", .Stderr = L"", .ExitCode = 0}); // Cleanup 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..4aef23da2 --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp @@ -0,0 +1,127 @@ +/*++ + +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..6ae4040e4 --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp @@ -0,0 +1,144 @@ +/*++ + +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..1f77c926d --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp @@ -0,0 +1,139 @@ +/*++ + +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_VolumeDeleteLongDesc() + 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..a0b20be68 --- /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_VolumeDeleteDesc()}, + {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 From f1f47fca3e1578005d42e07ad419ba22b4933e92 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:46:34 -0700 Subject: [PATCH 07/13] Clang format --- .../wslc/e2e/WSLCE2EVolumeCreateTests.cpp | 9 +++++---- .../wslc/e2e/WSLCE2EVolumeInspectTests.cpp | 16 ++++++++++------ .../wslc/e2e/WSLCE2EVolumeRemoveTests.cpp | 11 +++++++---- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp index 4aef23da2..ee0e4b42f 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeCreateTests.cpp @@ -73,7 +73,8 @@ class WSLCE2EVolumeCreateTests WSLC_TEST_METHOD(WSLCE2E_Volume_Create_InvalidDriver) { - auto result = RunWslc(std::format(L"volume create --driver invalid_driver --opt SizeBytes={} {}", DefaultVolumeSizeBytes, TestVolumeName)); + 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); } @@ -106,8 +107,8 @@ class WSLCE2EVolumeCreateTests std::wstring GetAvailableCommands() const { std::wstringstream commands; - commands << L"The following arguments are available:\r\n" // - << L" volume-name Volume name\r\n" // + commands << L"The following arguments are available:\r\n" // + << L" volume-name Volume name\r\n" // << L"\r\n"; return commands.str(); } @@ -115,7 +116,7 @@ class WSLCE2EVolumeCreateTests std::wstring GetAvailableOptions() const { std::wstringstream options; - options << L"The following options are available:\r\n" // + 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" // diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp index 6ae4040e4..9b31e4320 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeInspectTests.cpp @@ -58,7 +58,8 @@ class WSLCE2EVolumeInspectTests result = RunWslc(std::format(L"volume inspect {}", TestVolumeName1)); result.Verify({.Stderr = L"", .ExitCode = 0}); - auto inspectData = wsl::shared::FromJson>(result.Stdout.value().c_str()); + auto inspectData = + wsl::shared::FromJson>(result.Stdout.value().c_str()); VERIFY_ARE_EQUAL(1u, inspectData.size()); auto inspect = inspectData[0]; @@ -77,7 +78,8 @@ class WSLCE2EVolumeInspectTests 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()); + auto inspectData = + wsl::shared::FromJson>(result.Stdout.value().c_str()); VERIFY_ARE_EQUAL(2u, inspectData.size()); auto inspect1 = inspectData[0]; @@ -93,7 +95,9 @@ class WSLCE2EVolumeInspectTests { 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}); + {.Stdout = L"", + .Stderr = std::format(L"Volume not found: '{}'\r\nError code: WSLC_E_VOLUME_NOT_FOUND\r\n", TestVolumeName1), + .ExitCode = 1}); } private: @@ -125,8 +129,8 @@ class WSLCE2EVolumeInspectTests std::wstring GetAvailableCommands() const { std::wstringstream commands; - commands << L"The following arguments are available:\r\n" // - << L" volume-name Volume name\r\n" // + commands << L"The following arguments are available:\r\n" // + << L" volume-name Volume name\r\n" // << L"\r\n"; return commands.str(); } @@ -134,7 +138,7 @@ class WSLCE2EVolumeInspectTests std::wstring GetAvailableOptions() const { std::wstringstream options; - options << L"The following options are available:\r\n" // + 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"; diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp index 1f77c926d..6871ff4fb 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp @@ -82,7 +82,10 @@ class WSLCE2EVolumeRemoveTests 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}); + 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: @@ -120,8 +123,8 @@ class WSLCE2EVolumeRemoveTests std::wstring GetAvailableCommands() const { std::wstringstream commands; - commands << L"The following arguments are available:\r\n" // - << L" volume-name Volume name\r\n" // + commands << L"The following arguments are available:\r\n" // + << L" volume-name Volume name\r\n" // << L"\r\n"; return commands.str(); } @@ -129,7 +132,7 @@ class WSLCE2EVolumeRemoveTests std::wstring GetAvailableOptions() const { std::wstringstream options; - options << L"The following options are available:\r\n" // + 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"; From 7917b2ec4fd2d9374d8974cd47e291efabb29918 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:10:48 -0700 Subject: [PATCH 08/13] Resolve copilot comment --- localization/strings/en-US/Resources.resw | 8 ++++++-- src/windows/wslc/arguments/ArgumentDefinitions.h | 4 ++-- src/windows/wslc/services/VolumeModel.h | 1 - src/windows/wslc/tasks/VolumeTasks.h | 14 +++++--------- .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 14 +++++++------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index ac3d3ced1..46a561b5a 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2571,7 +2571,11 @@ On first run, creates the file with all settings commented out at their defaults Volume name - - Specify volume driver name + + Specify volume driver name (default {}) + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Set driver specific options diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index b2b7708d5..dc79e4a6e 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -45,7 +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, L"Specify volume driver name (default vhd)") \ +_(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()) \ @@ -63,7 +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, L"Set driver specific options") \ +_(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())*/ \ diff --git a/src/windows/wslc/services/VolumeModel.h b/src/windows/wslc/services/VolumeModel.h index 609eb31cf..5995850d8 100644 --- a/src/windows/wslc/services/VolumeModel.h +++ b/src/windows/wslc/services/VolumeModel.h @@ -14,7 +14,6 @@ Module Name: #pragma once -#include #include namespace wsl::windows::wslc::models { diff --git a/src/windows/wslc/tasks/VolumeTasks.h b/src/windows/wslc/tasks/VolumeTasks.h index b1c49acfd..d6fe39479 100644 --- a/src/windows/wslc/tasks/VolumeTasks.h +++ b/src/windows/wslc/tasks/VolumeTasks.h @@ -13,16 +13,12 @@ Module Name: --*/ #pragma once #include "CLIExecutionContext.h" -#include "Task.h" - -using wsl::windows::wslc::execution::CLIExecutionContext; namespace wsl::windows::wslc::task { -void CreateVolume(CLIExecutionContext& context); -void DeleteVolumes(CLIExecutionContext& context); -void GetVolumes(CLIExecutionContext& context); -void InspectVolumes(CLIExecutionContext& context); -void ListVolumes(CLIExecutionContext& context); - +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/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 70acf7b0a..e1c06db27 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -542,18 +542,18 @@ class WSLCE2EContainerCreateTests // Create the named volume auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Write data to the volume using a container result = RunWslc(std::format( L"container run --rm --volume {}:/data {} sh -c \"echo -n 'named_volume_data' > /data/testfile\"", volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Read data back from the volume using another container result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"named_volume_data", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"named_volume_data", .Stderr = L"", .ExitCode = 0}); // Cleanup RunWslc(std::format(L"volume rm {}", volumeName)); @@ -569,7 +569,7 @@ class WSLCE2EContainerCreateTests // Create the named volume auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Write data using first container result = RunWslc(std::format( @@ -577,17 +577,17 @@ class WSLCE2EContainerCreateTests WslcContainerName, volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Remove first container RunWslc(std::format(L"container rm {}", WslcContainerName)); // Read data using second container result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"persist_test", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"persist_test", .Stderr = L"", .ExitCode = 0}); // Cleanup RunWslc(std::format(L"volume rm {}", volumeName)); From 87834577776085e85d50567c0eac827eaf891d2d Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:20:30 -0700 Subject: [PATCH 09/13] Revert test in run/create --- .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 61 ------------------- .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 55 ----------------- 2 files changed, 116 deletions(-) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index e1c06db27..9497f9c91 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -532,67 +532,6 @@ class WSLCE2EContainerCreateTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } - WSLC_TEST_METHOD(WSLCE2E_Container_Create_NamedVolume_WriteFromContainerReadFromContainer) - { - - const std::wstring volumeName = L"wslc-test-vol-create"; - - // Cleanup any pre-existing volume - RunWslc(std::format(L"volume rm {}", volumeName)); - - // Create the named volume - auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Write data to the volume using a container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n 'named_volume_data' > /data/testfile\"", - volumeName, - AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); - - // Read data back from the volume using another container - result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"named_volume_data", .Stderr = L"", .ExitCode = 0}); - - // Cleanup - RunWslc(std::format(L"volume rm {}", volumeName)); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Create_NamedVolume_PersistsBetweenContainers) - { - - const std::wstring volumeName = L"wslc-test-vol-persist"; - - // Cleanup any pre-existing volume - RunWslc(std::format(L"volume rm {}", volumeName)); - - // Create the named volume - auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Write data using first container - result = RunWslc(std::format( - L"container create --name {} --volume {}:/data {} sh -c \"echo -n 'persist_test' > /data/testfile\"", - WslcContainerName, - volumeName, - AlpineImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); - - // Remove first container - RunWslc(std::format(L"container rm {}", WslcContainerName)); - - // Read data using second container - result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, AlpineImage.NameAndTag())); - result.Verify({.Stdout = L"persist_test", .Stderr = L"", .ExitCode = 0}); - - // Cleanup - RunWslc(std::format(L"volume rm {}", volumeName)); - } - private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index c9481dcbe..8b22bce11 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -535,61 +535,6 @@ class WSLCE2EContainerRunTests result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } - WSLC_TEST_METHOD(WSLCE2E_Container_Run_NamedVolume_WriteAndRead) - { - - const std::wstring volumeName = L"wslc-test-vol-run"; - - // Cleanup any pre-existing volume - RunWslc(std::format(L"volume rm {}", volumeName)); - - // Create the named volume - auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Write data to the named volume - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n 'run_vol_test' > /data/testfile\"", volumeName, DebianImage.NameAndTag())); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); - - // Read data back from the named volume in a new container - result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, DebianImage.NameAndTag())); - result.Verify({.Stdout = L"run_vol_test", .Stderr = L"", .ExitCode = 0}); - - // Cleanup - RunWslc(std::format(L"volume rm {}", volumeName)); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_NamedVolume_MultipleContainers) - { - - const std::wstring volumeName = L"wslc-test-vol-run-multi"; - - // Cleanup any pre-existing volume - RunWslc(std::format(L"volume rm {}", volumeName)); - - // Create the named volume - auto result = RunWslc(std::format(L"volume create {}", volumeName)); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Write data with first container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n 'first' > /data/testfile\"", volumeName, DebianImage.NameAndTag())); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); - - // Append data with second container - result = RunWslc(std::format( - L"container run --rm --volume {}:/data {} sh -c \"echo -n '_second' >> /data/testfile\"", volumeName, DebianImage.NameAndTag())); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); - - // Read combined data with third container - result = RunWslc(std::format(L"container run --rm --volume {}:/data {} cat /data/testfile", volumeName, DebianImage.NameAndTag())); - result.Verify({.Stdout = L"first_second", .Stderr = L"", .ExitCode = 0}); - - // Cleanup - RunWslc(std::format(L"volume rm {}", volumeName)); - } - private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; From 49efe4fbc85f23485f3d2ddfa1dbecc9a7dbf973 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:31:34 -0700 Subject: [PATCH 10/13] Rename delete to remove --- localization/strings/en-US/Resources.resw | 4 ++-- .../wslc/arguments/ArgumentValidation.h | 1 - src/windows/wslc/commands/VolumeCommand.cpp | 2 +- src/windows/wslc/commands/VolumeCommand.h | 6 +++--- ...eteCommand.cpp => VolumeRemoveCommand.cpp} | 21 ++++++++----------- .../wslc/e2e/WSLCE2EVolumeRemoveTests.cpp | 2 +- test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp | 2 +- 7 files changed, 17 insertions(+), 21 deletions(-) rename src/windows/wslc/commands/{VolumeDeleteCommand.cpp => VolumeRemoveCommand.cpp} (56%) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 46a561b5a..0c47c2839 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2550,10 +2550,10 @@ On first run, creates the file with all settings commented out at their defaults 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. diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 673d63edd..23208699c 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -61,6 +61,5 @@ void ValidateFormatTypeFromString(const std::vector& values, const FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {}); void ValidateVolumeMount(const std::vector& values); -void ValidateVolumeDriver(const std::vector& values, const std::wstring& argName); } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/commands/VolumeCommand.cpp b/src/windows/wslc/commands/VolumeCommand.cpp index 4e38ea574..9c171a349 100644 --- a/src/windows/wslc/commands/VolumeCommand.cpp +++ b/src/windows/wslc/commands/VolumeCommand.cpp @@ -23,7 +23,7 @@ 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())); commands.push_back(std::make_unique(FullName())); return commands; diff --git a/src/windows/wslc/commands/VolumeCommand.h b/src/windows/wslc/commands/VolumeCommand.h index 82346c760..19a2b0634 100644 --- a/src/windows/wslc/commands/VolumeCommand.h +++ b/src/windows/wslc/commands/VolumeCommand.h @@ -47,11 +47,11 @@ struct VolumeCreateCommand final : public Command void ExecuteInternal(CLIExecutionContext& context) const override; }; -// Delete Command -struct VolumeDeleteCommand final : public Command +// Remove Command +struct VolumeRemoveCommand final : public Command { constexpr static std::wstring_view CommandName = L"remove"; - VolumeDeleteCommand(const std::wstring& parent) : Command(CommandName, {L"delete", L"rm"}, parent) + VolumeRemoveCommand(const std::wstring& parent) : Command(CommandName, {L"delete", L"rm"}, parent) { } std::vector GetArguments() const override; diff --git a/src/windows/wslc/commands/VolumeDeleteCommand.cpp b/src/windows/wslc/commands/VolumeRemoveCommand.cpp similarity index 56% rename from src/windows/wslc/commands/VolumeDeleteCommand.cpp rename to src/windows/wslc/commands/VolumeRemoveCommand.cpp index 395fbd0fb..59bb57136 100644 --- a/src/windows/wslc/commands/VolumeDeleteCommand.cpp +++ b/src/windows/wslc/commands/VolumeRemoveCommand.cpp @@ -4,7 +4,7 @@ Copyright (c) Microsoft. All rights reserved. Module Name: - VolumeDeleteCommand.cpp + VolumeRemoveCommand.cpp Abstract: @@ -24,7 +24,7 @@ using namespace wsl::shared; namespace wsl::windows::wslc { // Volume Delete Command -std::vector VolumeDeleteCommand::GetArguments() const +std::vector VolumeRemoveCommand::GetArguments() const { return { Argument::Create(ArgType::VolumeName, true, NO_LIMIT), @@ -32,22 +32,19 @@ std::vector VolumeDeleteCommand::GetArguments() const }; } -std::wstring VolumeDeleteCommand::ShortDescription() const +std::wstring VolumeRemoveCommand::ShortDescription() const { - return Localization::WSLCCLI_VolumeDeleteDesc(); + return Localization::WSLCCLI_VolumeRemoveDesc(); } -std::wstring VolumeDeleteCommand::LongDescription() const +std::wstring VolumeRemoveCommand::LongDescription() const { - return Localization::WSLCCLI_VolumeDeleteLongDesc(); + return Localization::WSLCCLI_VolumeRemoveLongDesc(); } -// clang-format off -void VolumeDeleteCommand::ExecuteInternal(CLIExecutionContext& context) const +void VolumeRemoveCommand::ExecuteInternal(CLIExecutionContext& context) const { - context - << CreateSession - << DeleteVolumes; + context << CreateSession // + << DeleteVolumes; } -// clang-format on } // namespace wsl::windows::wslc diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp index 6871ff4fb..abb44a8b6 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeRemoveTests.cpp @@ -107,7 +107,7 @@ class WSLCE2EVolumeRemoveTests std::wstring GetDescription() const { - return Localization::WSLCCLI_VolumeDeleteLongDesc() + L"\r\n\r\n"; + return Localization::WSLCCLI_VolumeRemoveLongDesc() + L"\r\n\r\n"; } std::wstring GetUsage() const diff --git a/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp b/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp index a0b20be68..211a7a18a 100644 --- a/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EVolumeTests.cpp @@ -68,7 +68,7 @@ class WSLCE2EVolumeTests { std::vector> entries = { {L"create", Localization::WSLCCLI_VolumeCreateDesc()}, - {L"remove", Localization::WSLCCLI_VolumeDeleteDesc()}, + {L"remove", Localization::WSLCCLI_VolumeRemoveDesc()}, {L"inspect", Localization::WSLCCLI_VolumeInspectDesc()}, {L"list", Localization::WSLCCLI_VolumeListDesc()}, }; From 21849d3e6601be55194ce01729de89cd455a74b6 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:36:04 -0700 Subject: [PATCH 11/13] Resolve copilot comment --- src/windows/wslc/services/VolumeModel.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/windows/wslc/services/VolumeModel.h b/src/windows/wslc/services/VolumeModel.h index 5995850d8..308a91561 100644 --- a/src/windows/wslc/services/VolumeModel.h +++ b/src/windows/wslc/services/VolumeModel.h @@ -14,6 +14,7 @@ Module Name: #pragma once +#include "JsonUtils.h" #include namespace wsl::windows::wslc::models { From a5a3dbf41695154fc2a1cd76c5722db832c9f412 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:55:14 -0700 Subject: [PATCH 12/13] Resolve copilot comment --- localization/strings/en-US/Resources.resw | 4 ++++ src/windows/wslc/commands/VolumeListCommand.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 0c47c2839..1ab8a0450 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2578,4 +2578,8 @@ On first run, creates the file with all settings commented out at their defaults Set driver specific options + + Outputs the volume names only + + diff --git a/src/windows/wslc/commands/VolumeListCommand.cpp b/src/windows/wslc/commands/VolumeListCommand.cpp index 8dafd47c9..e8f336b50 100644 --- a/src/windows/wslc/commands/VolumeListCommand.cpp +++ b/src/windows/wslc/commands/VolumeListCommand.cpp @@ -29,7 +29,7 @@ std::vector VolumeListCommand::GetArguments() const { return { Argument::Create(ArgType::Format), - Argument::Create(ArgType::Quiet, false, std::nullopt, L"Outputs the volume names only"), + Argument::Create(ArgType::Quiet, false, std::nullopt, Localization::WSLCCLI_VolumeListQuietArgDesc()), Argument::Create(ArgType::Session), }; } From 912a4c546c9dd881b28e0767f0907167891c1454 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:56:45 -0700 Subject: [PATCH 13/13] Fix e2e tests --- test/windows/wslc/WSLCCLIExecutionUnitTests.cpp | 6 ++++++ test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp | 1 + 2 files changed, 7 insertions(+) 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()},