diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 6eb41abef..387b37493 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2125,6 +2125,10 @@ Usage: Container '{}' is not running. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Container '{}' was created with --rm and cannot be restarted. + {FixedPlaceholder="{}"}{Locked="--rm "}Command line arguments, file names and string inserts should not be translated + Container '{}' is running. {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -2515,6 +2519,12 @@ For privacy information about this product please visit https://aka.ms/privacy.< Removes containers. + + Restart containers. + + + Restarts one or more containers. Running containers are stopped and started again; stopped containers are started. + Run a container. diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 7639c4517..f57cdde4d 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -492,6 +492,7 @@ interface IWSLCContainer : IUnknown HRESULT Stats([out] LPSTR* Output); HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options); HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName); + HRESULT Restart([in] WSLCSignal Signal, [in] LONG TimeoutSeconds, [in, unique] IWarningCallback* WarningCallback); } typedef struct _WSLCDeletedImageInformation diff --git a/src/windows/wslc/commands/ContainerCommand.cpp b/src/windows/wslc/commands/ContainerCommand.cpp index 9d5002f3f..c8ce8070e 100644 --- a/src/windows/wslc/commands/ContainerCommand.cpp +++ b/src/windows/wslc/commands/ContainerCommand.cpp @@ -32,6 +32,7 @@ std::vector> ContainerCommand::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/ContainerCommand.h b/src/windows/wslc/commands/ContainerCommand.h index 74f6c5629..2fe1ccca8 100644 --- a/src/windows/wslc/commands/ContainerCommand.h +++ b/src/windows/wslc/commands/ContainerCommand.h @@ -168,6 +168,21 @@ struct ContainerRemoveCommand final : public Command void ExecuteInternal(CLIExecutionContext& context) const override; }; +// Restart Command +struct ContainerRestartCommand final : public Command +{ + constexpr static std::wstring_view CommandName = L"restart"; + ContainerRestartCommand(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; +}; + // Run Command struct ContainerRunCommand final : public Command { diff --git a/src/windows/wslc/commands/ContainerRestartCommand.cpp b/src/windows/wslc/commands/ContainerRestartCommand.cpp new file mode 100644 index 000000000..199a7ed46 --- /dev/null +++ b/src/windows/wslc/commands/ContainerRestartCommand.cpp @@ -0,0 +1,54 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + ContainerRestartCommand.cpp + +Abstract: + + Implementation of command execution logic. + +--*/ + +#include "ContainerCommand.h" +#include "CLIExecutionContext.h" +#include "ContainerTasks.h" +#include "SessionTasks.h" +#include "Task.h" + +using namespace wsl::windows::wslc::execution; +using namespace wsl::windows::wslc::task; +using namespace wsl::shared; + +namespace wsl::windows::wslc { +// Container Restart Command +std::vector ContainerRestartCommand::GetArguments() const +{ + return { + Argument::Create(ArgType::ContainerId, std::nullopt, NO_LIMIT), + Argument::Create(ArgType::Signal), + Argument::Create(ArgType::Time), + }; +} + +std::wstring ContainerRestartCommand::ShortDescription() const +{ + return Localization::WSLCCLI_ContainerRestartDesc(); +} + +std::wstring ContainerRestartCommand::LongDescription() const +{ + return Localization::WSLCCLI_ContainerRestartLongDesc(); +} + +// clang-format off +void ContainerRestartCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + context + << ResolveSession + << RestartContainers; +} +// clang-format on +} // namespace wsl::windows::wslc diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 9a85ebf98..6fe332785 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -492,6 +492,14 @@ void ContainerService::Stop(Session& session, const std::string& id, StopContain THROW_IF_FAILED_EXCEPT(container->Stop(options.Signal, options.Timeout), WSLC_E_CONTAINER_NOT_RUNNING); } +void ContainerService::Restart(Session& session, const std::string& id, StopContainerOptions options) +{ + wil::com_ptr container; + THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container)); + auto warningCallback = Microsoft::WRL::Make(); + THROW_IF_FAILED(container->Restart(options.Signal, options.Timeout, warningCallback.Get())); +} + void ContainerService::Kill(Session& session, const std::string& id, WSLCSignal signal) { wil::com_ptr container; diff --git a/src/windows/wslc/services/ContainerService.h b/src/windows/wslc/services/ContainerService.h index fccdf2056..1bbfe4926 100644 --- a/src/windows/wslc/services/ContainerService.h +++ b/src/windows/wslc/services/ContainerService.h @@ -28,6 +28,7 @@ struct ContainerService static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options); static int Start(models::Session& session, const std::string& id, bool attach = false); static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options); + static void Restart(models::Session& session, const std::string& id, models::StopContainerOptions options); static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL); static void Delete(models::Session& session, const std::string& id, bool force); static std::vector List( diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index ad7d112d8..027f77af9 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -767,6 +767,29 @@ void StopContainers(CLIExecutionContext& context) } } +void RestartContainers(CLIExecutionContext& context) +{ + WI_ASSERT(context.Data.Contains(Data::Session)); + auto& session = context.Data.Get(); + auto containersToRestart = context.Args.GetAll(); + StopContainerOptions options; + if (context.Args.Contains(ArgType::Signal)) + { + options.Signal = validation::GetWSLCSignalFromString(context.Args.Get()); + } + + if (context.Args.Contains(ArgType::Time)) + { + options.Timeout = validation::GetIntegerFromString(context.Args.Get()); + } + + for (const auto& id : containersToRestart) + { + ContainerService::Restart(session, WideToMultiByte(id), options); + PrintMessage(id); + } +} + void ViewContainerLogs(CLIExecutionContext& context) { WI_ASSERT(context.Data.Contains(Data::Session)); diff --git a/src/windows/wslc/tasks/ContainerTasks.h b/src/windows/wslc/tasks/ContainerTasks.h index 7055f5e37..458fc946f 100644 --- a/src/windows/wslc/tasks/ContainerTasks.h +++ b/src/windows/wslc/tasks/ContainerTasks.h @@ -39,6 +39,7 @@ void KillContainers(CLIExecutionContext& context); void ListContainers(CLIExecutionContext& context); void PruneContainers(CLIExecutionContext& context); void RemoveContainers(CLIExecutionContext& context); +void RestartContainers(CLIExecutionContext& context); void RunContainer(CLIExecutionContext& context); void SetContainerOptionsFromArgs(CLIExecutionContext& context); void ShowContainerStats(CLIExecutionContext& context); diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 674b0b0cd..3be3d00fe 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -2524,6 +2524,40 @@ void WSLCContainerImpl::DisconnectFromNetwork(LPCSTR NetworkName) TraceLoggingValue(NetworkName, "NetworkName")); } +void WSLCContainerImpl::Restart(WSLCSignal Signal, LONG TimeoutSeconds) +{ + // N.B. Snapshot under a brief shared lock and release before composing Stop/Start. + // m_lock is a non-recursive srwlock; Stop and Start each take it exclusively. + WSLCContainerState snapshotState{}; + WSLCContainerFlags snapshotFlags{}; + { + auto lock = m_lock.lock_shared(); + snapshotState = m_state; + snapshotFlags = m_containerFlags; + } + + // Refuse --rm before stopping: Stop -> OnStopped would delete the container. + THROW_HR_WITH_USER_ERROR_IF( + HRESULT_FROM_WIN32(ERROR_INVALID_STATE), + Localization::MessageWslcContainerRestartAutoRemove(m_id), + WI_IsFlagSet(snapshotFlags, WSLCContainerFlagsRm)); + + THROW_HR_IF_MSG( + HRESULT_FROM_WIN32(ERROR_INVALID_STATE), + snapshotState != WslcContainerStateRunning && snapshotState != WslcContainerStateExited && snapshotState != WslcContainerStateCreated, + "Cannot restart container '%hs', state %i", + m_id.c_str(), + snapshotState); + + if (snapshotState == WslcContainerStateRunning) + { + // N.B. If the container exits on its own before Stop acquires m_lock, Stop no-ops on Exited. + Stop(Signal, TimeoutSeconds, /*Kill*/ false); + } + + Start(WSLCContainerStartFlagsNone, nullptr); +} + HRESULT WSLCContainer::GetLabels(WSLCLabelInformation** Labels, ULONG* Count) try { @@ -2553,6 +2587,15 @@ try } CATCH_RETURN(); +HRESULT WSLCContainer::Restart(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) +try +{ + // N.B. WarningCallback is ambient here so EMIT_USER_WARNING reaches the client from both the Stop and Start phases. + WSLCExecutionContext context(&m_session, WarningCallback); + return CallImpl(&WSLCContainerImpl::Restart, Signal, TimeoutSeconds); +} +CATCH_RETURN(); + HRESULT WSLCContainer::InterfaceSupportsErrorInfo(REFIID riid) { return riid == __uuidof(IWSLCContainer) || riid == __uuidof(IWSLCCompatContainer) ? S_OK : S_FALSE; diff --git a/src/windows/wslcsession/WSLCContainer.h b/src/windows/wslcsession/WSLCContainer.h index 10fd57fd3..df2632c08 100644 --- a/src/windows/wslcsession/WSLCContainer.h +++ b/src/windows/wslcsession/WSLCContainer.h @@ -110,6 +110,7 @@ class WSLCContainerImpl void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const; void ConnectToNetwork(const WSLCNetworkConnectionOptions* Options); void DisconnectFromNetwork(LPCSTR NetworkName); + void Restart(WSLCSignal Signal, LONG TimeoutSeconds); void CopyTo(IWSLCContainer** Container) const; @@ -247,6 +248,7 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override; IFACEMETHOD(ConnectToNetwork)(_In_ const WSLCNetworkConnectionOptions* Options) override; IFACEMETHOD(DisconnectFromNetwork)(_In_ LPCSTR NetworkName) override; + IFACEMETHOD(Restart)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) override; // IWSLCCompatContainer. IFACEMETHOD(Start)(_In_ WSLCContainerStartFlags Flags) override; diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 80881d63e..272bd02de 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -6192,6 +6192,63 @@ class WSLCTests } } + WSLC_TEST_METHOD(ContainerRestart) + { + // Restart a running container. + { + WSLCContainerLauncher launcher("debian:latest", "test-restart-running", {"sleep", "99999"}); + auto container = launcher.Launch(*m_defaultSession); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + + VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + } + + // Restart of an exited container just starts it. + { + WSLCContainerLauncher launcher("debian:latest", "test-restart-exited", {"sleep", "99999"}); + auto container = launcher.Launch(*m_defaultSession); + VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateExited); + + VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + } + + // Restart of a created (never-started) container starts it. + { + WSLCContainerLauncher launcher("debian:latest", "test-restart-created", {"sleep", "99999"}); + auto container = launcher.Create(*m_defaultSession); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateCreated); + + VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + } + + // --rm containers are refused; Stop is not invoked so the container stays Running. + { + WSLCContainerLauncher launcher("debian:latest", "test-restart-autorm", {"sleep", "99999"}); + launcher.SetContainerFlags(WSLCContainerFlagsRm); + auto container = launcher.Launch(*m_defaultSession); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + + auto id = container.Id(); + VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + ValidateCOMErrorMessage(std::format(L"Container '{}' was created with --rm and cannot be restarted.", id)); + VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning); + } + + // Deleted containers can't be restarted. + { + WSLCContainerLauncher launcher("debian:latest", "test-restart-deleted", {"sleep", "99999"}); + auto container = launcher.Launch(*m_defaultSession); + VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0)); + VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone)); + + VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), RPC_E_DISCONNECTED); + } + } + WSLC_TEST_METHOD(OpenContainer) { auto expectOpen = [&](const char* Id, HRESULT expectedResult = S_OK) { diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp new file mode 100644 index 000000000..20a647158 --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp @@ -0,0 +1,224 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLCE2EContainerRestartTests.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 WSLCE2EContainerRestartTests +{ + WSLC_TEST_CLASS(WSLCE2EContainerRestartTests) + + TEST_CLASS_SETUP(ClassSetup) + { + EnsureImageIsLoaded(DebianImage); + return true; + } + + TEST_CLASS_CLEANUP(ClassCleanup) + { + EnsureContainerDoesNotExist(WslcContainerName); + EnsureContainerDoesNotExist(WslcContainerName2); + EnsureImageIsDeleted(DebianImage); + return true; + } + + TEST_METHOD_SETUP(TestMethodSetup) + { + EnsureContainerDoesNotExist(WslcContainerName); + EnsureContainerDoesNotExist(WslcContainerName2); + return true; + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_HelpCommand) + { + auto result = RunWslc(L"container restart --help"); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_RunningContainer) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + VerifyContainerIsListed(containerId, L"running"); + + result = RunWslc(std::format(L"container restart {} -t 0", containerId)); + result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0}); + + // Should be running again after restart. + VerifyContainerIsListed(containerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_StoppedContainer) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + result = RunWslc(std::format(L"container stop {} -t 0", containerId)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VerifyContainerIsListed(containerId, L"exited"); + + // Restart of a stopped container should just start it. + result = RunWslc(std::format(L"container restart {} -t 0", containerId)); + result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0}); + VerifyContainerIsListed(containerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_ByName) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + VerifyContainerIsListed(containerId, L"running"); + + result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName)); + result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0}); + + VerifyContainerIsListed(containerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_NotFound) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName)); + result.Verify( + {.Stderr = std::format(L"Container '{}' not found.\r\nError code: WSLC_E_CONTAINER_NOT_FOUND\r\n", WslcContainerName), + .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_TargetedContainerOnly) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto firstContainerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(firstContainerId.empty()); + + result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto secondContainerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(secondContainerId.empty()); + + VerifyContainerIsListed(firstContainerId, L"running"); + VerifyContainerIsListed(secondContainerId, L"running"); + + // Restart only the first container. + result = RunWslc(std::format(L"container restart {} -t 0", firstContainerId)); + result.Verify({.Stdout = std::format(L"{}\r\n", firstContainerId), .Stderr = L"", .ExitCode = 0}); + + VerifyContainerIsListed(firstContainerId, L"running"); + VerifyContainerIsListed(secondContainerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_AutoRemoveRefused) + { + // --rm containers cannot be restarted (Stop would delete them). + auto result = + RunWslc(std::format(L"container run -d --rm --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + VerifyContainerIsListed(containerId, L"running"); + + result = RunWslc(std::format(L"container restart {} -t 0", containerId)); + const auto expectedStderr = + std::format(L"Container '{}' was created with --rm and cannot be restarted.\r\nError code: ERROR_INVALID_STATE\r\n", containerId); + result.Verify({.Stderr = expectedStderr, .ExitCode = 1}); + + // Container should still be running after the refusal. + VerifyContainerIsListed(containerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidSignal) + { + auto result = RunWslc(std::format(L"container run --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container restart {} -s 0 -t 0", WslcContainerName)); + result.Verify({.Stderr = L"Invalid signal value: 0 is out of valid range (1-31).\r\n", .ExitCode = 1}); + + result = RunWslc(std::format(L"container restart {} -s 32 -t 0", WslcContainerName)); + result.Verify({.Stderr = L"Invalid signal value: 32 is out of valid range (1-31).\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidTimeout) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + result = RunWslc(std::format(L"container restart {} -t abc", containerId)); + result.Verify({.Stderr = L"Invalid time argument value: abc\r\n", .ExitCode = 1}); + + // Container is unaffected by the parse failure. + VerifyContainerIsListed(containerId, L"running"); + } + +private: + const std::wstring WslcContainerName = L"wslc-test-container"; + const std::wstring WslcContainerName2 = L"wslc-test-container-2"; + const TestImage& DebianImage = DebianTestImage(); + + std::wstring GetHelpMessage() const + { + std::wstringstream output; + output << GetWslcHeader() // + << GetDescription() // + << GetUsage() // + << GetAvailableCommands() // + << GetAvailableOptions(); + return output.str(); + } + + std::wstring GetDescription() const + { + return Localization::WSLCCLI_ContainerRestartLongDesc() + L"\r\n\r\n"; + } + + std::wstring GetUsage() const + { + return L"Usage: wslc container restart [] []\r\n\r\n"; + } + + std::wstring GetAvailableCommands() const + { + std::wstringstream commands; + commands << L"The following arguments are available:\r\n" << L" container-id Container ID\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" -s,--signal Signal to send\r\n" + << L" -t,--time Time in seconds to wait before executing (default 5)\r\n" + << L" -?,--help Shows help about the selected command\r\n" + << L"\r\n"; + return options.str(); + } +}; +} // namespace WSLCE2ETests diff --git a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp index 09caf623c..a7e5ca810 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp @@ -79,6 +79,7 @@ class WSLCE2EContainerTests {L"list", Localization::WSLCCLI_ContainerListDesc()}, {L"prune", Localization::WSLCCLI_ContainerPruneDesc()}, {L"remove", Localization::WSLCCLI_ContainerRemoveDesc()}, + {L"restart", Localization::WSLCCLI_ContainerRestartDesc()}, {L"run", Localization::WSLCCLI_ContainerRunDesc()}, {L"start", Localization::WSLCCLI_ContainerStartDesc()}, {L"stats", Localization::WSLCCLI_ContainerStatsDesc()},