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