diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 462c7b36a..6d072ab24 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2118,6 +2118,10 @@ For privacy information about this product please visit https://aka.ms/privacy.< Failed to open '{}': {} {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Failed to write to '{}': {} + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + No Containerfile or Dockerfile found in '{}' {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated @@ -2426,7 +2430,7 @@ On first run, creates the file with all settings commented out at their defaults Working directory inside the container - Write the container ID to the provided path. + Write the container ID to the provided path IP address of the DNS nameserver in resolv.conf @@ -2491,6 +2495,10 @@ On first run, creates the file with all settings commented out at their defaults Invalid {} value: {} is out of valid range ({}-{}). {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + CID file '{}' already exists + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + Image '{}' not found, pulling {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index 3748a95cd..96729e0ec 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -36,7 +36,7 @@ Module Name: _(All, "all", L"a", Kind::Flag, Localization::WSLCCLI_AllArgDescription()) \ _(Attach, "attach", L"a", Kind::Flag, Localization::WSLCCLI_AttachArgDescription()) \ _(BuildArg, "build-arg", NO_ALIAS, Kind::Value, Localization::WSLCCLI_BuildArgDescription()) \ -/*_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription())*/ \ +_(CIDFile, "cidfile", NO_ALIAS, Kind::Value, Localization::WSLCCLI_CIDFileArgDescription()) \ _(Command, "command", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_CommandArgDescription()) \ _(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \ _(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index ea47a5f55..8729b29ee 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -58,6 +58,12 @@ void Argument::Validate(const ArgMap& execArgs) const break; } + case ArgType::CIDFile: + { + validation::ValidateCidFile(execArgs.Get()); + break; + } + default: break; } @@ -97,6 +103,21 @@ void ValidateVolumeMount(const std::vector& values) } } +void ValidateCidFile(const std::wstring& cidFile) +{ + std::error_code ec; + if (std::filesystem::exists(std::filesystem::path{cidFile}, ec)) + { + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), Localization::WSLCCLI_CIDFileAlreadyExistsError(cidFile)); + } + + if (ec) + { + const auto errorMessage = MultiByteToWide(ec.message()); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ec.value()), Localization::MessageWslcFailedToOpenFile(cidFile, errorMessage)); + } +} + // 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..b1e7d8d71 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -62,4 +62,6 @@ FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring void ValidateVolumeMount(const std::vector& values); +void ValidateCidFile(const std::wstring& cidFile); + } // namespace wsl::windows::wslc::validation \ No newline at end of file diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 4150d0af5..b2d1af395 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -31,7 +31,7 @@ std::vector ContainerCreateCommand::GetArguments() const Argument::Create(ArgType::ImageId, true), Argument::Create(ArgType::Command), Argument::Create(ArgType::ForwardArgs), - // Argument::Create(ArgType::CIDFile), + Argument::Create(ArgType::CIDFile), // Argument::Create(ArgType::DNS), // Argument::Create(ArgType::DNSDomain), // Argument::Create(ArgType::DNSOption), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index e2810cd5f..751ad92db 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -31,7 +31,7 @@ std::vector ContainerRunCommand::GetArguments() const Argument::Create(ArgType::ImageId, true), Argument::Create(ArgType::Command), Argument::Create(ArgType::ForwardArgs), - // Argument::Create(ArgType::CIDFile), + Argument::Create(ArgType::CIDFile), Argument::Create(ArgType::Detach), // Argument::Create(ArgType::DNS), // Argument::Create(ArgType::DNSDomain), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index c4da81fab..ff03c5b87 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -42,6 +42,7 @@ struct ContainerOptions std::vector Entrypoint; std::optional User{}; std::vector Tmpfs; + std::optional CidFile{}; }; struct CreateContainerResult diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 05a5cc295..737a20c6a 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -20,6 +20,8 @@ Module Name: #include #include #include +#include +#include #include #include @@ -37,6 +39,40 @@ static void SetContainerArguments(WSLCProcessOptions& options, std::vector(argsStorage.size())}; } +static void WriteContainerIdToFile(const std::optional& cidFilePath, const std::string& containerId) +{ + if (!cidFilePath.has_value()) + { + return; + } + + const auto path = std::filesystem::path(cidFilePath.value()); + HANDLE file = ::CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + { + const auto error = ::GetLastError(); + if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) + { + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::WSLCCLI_CIDFileAlreadyExistsError(*cidFilePath)); + } + + const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToOpenFile(*cidFilePath, errorMessage)); + } + + DWORD bytesWritten{}; + const bool writeSuccess = ::WriteFile(file, containerId.data(), static_cast(containerId.size()), &bytesWritten, nullptr) != FALSE; + const HRESULT closeResult = ::CloseHandle(file) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); + if (!writeSuccess || bytesWritten != containerId.size()) + { + const auto error = writeSuccess ? ERROR_WRITE_FAULT : ::GetLastError(); + const auto errorMessage = wsl::shared::string::MultiByteToWide(std::system_category().message(error)); + THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(error), Localization::MessageWslcFailedToWriteFile(*cidFilePath, errorMessage)); + } + + THROW_IF_FAILED(closeResult); +} + static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& session, const std::string& image, const ContainerOptions& options) { auto processFlags = WSLCProcessFlagsNone; @@ -294,6 +330,10 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO runningContainer.SetDeleteOnClose(false); auto& container = runningContainer.Get(); + WSLCContainerId containerId{}; + THROW_IF_FAILED(container.GetId(containerId)); + WriteContainerIdToFile(runOptions.CidFile, containerId); + // Start the created container WSLCContainerStartFlags startFlags{}; WI_SetFlagIf(startFlags, WSLCContainerStartFlagsAttach, !runOptions.Detach); @@ -306,8 +346,6 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO return consoleService.AttachToCurrentConsole(runningContainer.GetInitProcess()); } - WSLCContainerId containerId{}; - THROW_IF_FAILED(container.GetId(containerId)); PrintMessage(L"%hs", stdout, containerId); return 0; } @@ -319,6 +357,7 @@ CreateContainerResult ContainerService::Create(Session& session, const std::stri auto& container = runningContainer.Get(); WSLCContainerId id{}; THROW_IF_FAILED(container.GetId(id)); + WriteContainerIdToFile(runOptions.CidFile, id); return {.Id = id}; } diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 55e19e478..309379b51 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -197,6 +197,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) { ContainerOptions options; + if (context.Args.Contains(ArgType::CIDFile)) + { + options.CidFile = context.Args.Get(); + } + if (context.Args.Contains(ArgType::Name)) { options.Name = WideToMultiByte(context.Args.Get()); diff --git a/test/windows/wslc/CommandLineTestCases.h b/test/windows/wslc/CommandLineTestCases.h index d615328d6..48fc48020 100644 --- a/test/windows/wslc/CommandLineTestCases.h +++ b/test/windows/wslc/CommandLineTestCases.h @@ -70,6 +70,7 @@ COMMAND_LINE_TEST_CASE( true) COMMAND_LINE_TEST_CASE(L"container run ubuntu bash -c 'echo Hello World'", L"run", true) COMMAND_LINE_TEST_CASE(L"container run ubuntu", L"run", true) +COMMAND_LINE_TEST_CASE(L"container run --cidfile C:\\temp\\cidfile ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"container run -it --name foo ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"container run --rm -it --name foo ubuntu", L"run", true) COMMAND_LINE_TEST_CASE(L"stop", L"stop", true) @@ -83,6 +84,7 @@ COMMAND_LINE_TEST_CASE(L"container start --attach cont", L"start", true) COMMAND_LINE_TEST_CASE(L"container start -a cont", L"start", true) COMMAND_LINE_TEST_CASE(L"create ubuntu:latest", L"create", true) COMMAND_LINE_TEST_CASE(L"container create --name foo ubuntu", L"create", true) +COMMAND_LINE_TEST_CASE(L"container create --cidfile C:\\temp\\cidfile --name foo ubuntu", L"create", true) COMMAND_LINE_TEST_CASE(L"exec cont1 echo Hello", L"exec", true) COMMAND_LINE_TEST_CASE(L"exec cont1", L"exec", false) // Missing required command argument COMMAND_LINE_TEST_CASE(L"container exec -it cont1 sh -c \"echo a && echo b\"", L"exec", true) // docker exec example diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index ad08e4bcb..b5178225e 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -116,6 +116,36 @@ class WSLCE2EContainerCreateTests VerifyContainerIsListed(containerId, L"created"); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_CIDFile_Valid) + { + // Prepare a CID file path that does not exist + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = S_OK}); + + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath)); + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring())); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_CIDFile_AlreadyExists) + { + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container create --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); + result.Verify( + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), + .ExitCode = 1}); + + VerifyContainerIsNotListed(WslcContainerName); + } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_DuplicateContainerName) { VerifyContainerIsNotListed(WslcContainerName); @@ -1028,10 +1058,8 @@ class WSLCE2EContainerCreateTests result.Verify({.Stdout = L"unable to find group badgid: no matching entries in group file\r\n", .ExitCode = 126}); } - TEST_METHOD(WSLCE2E_Container_Create_Tmpfs) + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container create --name {} --tmpfs /wslc-tmpfs {} sh -c \"echo -n 'tmpfs_test' > /wslc-tmpfs/data && cat " L"/wslc-tmpfs/data\"", @@ -1043,10 +1071,8 @@ class WSLCE2EContainerCreateTests result.Verify({.Stdout = L"tmpfs_test", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_With_Options) + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_With_Options) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container create --name {} --tmpfs /wslc-tmpfs:size=64k {} sh -c \"mount | grep -q ' on /wslc-tmpfs type tmpfs ' " L"&& echo mounted\"", @@ -1058,10 +1084,8 @@ class WSLCE2EContainerCreateTests result.Verify({.Stdout = L"mounted\n", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_Multiple_With_Options) + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_Multiple_With_Options) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container create --name {} --tmpfs /wslc-tmpfs1:size=64k --tmpfs /wslc-tmpfs2:size=128k {} sh -c \"mount | grep -q " L"' on /wslc-tmpfs1 type tmpfs ' && mount | grep -q ' on /wslc-tmpfs2 type tmpfs ' && echo mounted\"", @@ -1073,19 +1097,15 @@ class WSLCE2EContainerCreateTests result.Verify({.Stdout = L"mounted\n", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_RelativePath_Fails) + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_RelativePath_Fails) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format(L"container create --name {} --tmpfs wslc-tmpfs {}", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"invalid mount path: 'wslc-tmpfs' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } - TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_EmptyDestination_Fails) + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs_EmptyDestination_Fails) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format(L"container create --name {} --tmpfs :size=64k {}", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); @@ -1156,6 +1176,7 @@ class WSLCE2EContainerCreateTests { std::wstringstream options; options << L"The following options are available:\r\n" // + << L" --cidfile Write the container ID to the provided path\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n" << L" --env-file File containing key=value pairs of env variables\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index bf0ebc36d..a25e83488 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -58,6 +58,39 @@ class WSLCE2EContainerRunTests VerifyContainerIsListed(WslcContainerName, L"exited"); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_CIDFile_Valid) + { + // Prepare a CID file path that does not exist + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container run -d --cidfile \"{}\" --name {} {} sleep infinity", + EscapePath(cidFilePath.wstring()), + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_TRUE(std::filesystem::exists(cidFilePath)); + VERIFY_ARE_EQUAL(containerId, ReadFileContent(cidFilePath.wstring())); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_CIDFile_AlreadyExists) + { + const auto cidFilePath = wsl::windows::common::filesystem::GetTempFilename(); + auto deleteCidFile = wil::scope_exit([&]() { VERIFY_IS_TRUE(DeleteFileW(cidFilePath.c_str())); }); + + auto result = RunWslc(std::format( + L"container run --cidfile \"{}\" --name {} {}", EscapePath(cidFilePath.wstring()), WslcContainerName, DebianImage.NameAndTag())); + result.Verify( + {.Stderr = std::format(L"CID file '{}' already exists\r\nError code: ERROR_ALREADY_EXISTS\r\n", EscapePath(cidFilePath.wstring())), + .ExitCode = 1}); + + VerifyContainerIsNotListed(WslcContainerName); + } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Entrypoint) { auto result = RunWslc(std::format(L"container run --rm --entrypoint /bin/whoami {}", DebianImage.NameAndTag())); @@ -131,10 +164,8 @@ class WSLCE2EContainerRunTests result.Verify({.Stdout = L"nobody\n65534\n65534\n", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Run_Tmpfs) + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container run --rm --tmpfs /wslc-tmpfs {} sh -c \"echo -n 'tmpfs_test' > /wslc-tmpfs/data && cat " L"/wslc-tmpfs/data\"", @@ -142,10 +173,8 @@ class WSLCE2EContainerRunTests result.Verify({.Stdout = L"tmpfs_test", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_With_Options) + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_With_Options) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container run --rm --tmpfs /wslc-tmpfs:size=64k {} sh -c \"mount | grep -q ' on /wslc-tmpfs type tmpfs ' && echo " L"mounted\"", @@ -153,10 +182,8 @@ class WSLCE2EContainerRunTests result.Verify({.Stdout = L"mounted\n", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_Multiple_With_Options) + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_Multiple_With_Options) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format( L"container run --rm --tmpfs /wslc-tmpfs1:size=64k --tmpfs /wslc-tmpfs2:size=128k {} sh -c \"mount | grep -q ' on " L"/wslc-tmpfs1 type tmpfs ' && mount | grep -q ' on /wslc-tmpfs2 type tmpfs ' && echo mounted\"", @@ -164,18 +191,14 @@ class WSLCE2EContainerRunTests result.Verify({.Stdout = L"mounted\n", .Stderr = L"", .ExitCode = 0}); } - TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_RelativePath_Fails) + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_RelativePath_Fails) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format(L"container run --rm --tmpfs wslc-tmpfs {}", DebianImage.NameAndTag())); result.Verify({.Stderr = L"invalid mount path: 'wslc-tmpfs' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } - TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_EmptyDestination_Fails) + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs_EmptyDestination_Fails) { - WSL2_TEST_ONLY(); - auto result = RunWslc(std::format(L"container run --rm --tmpfs :size=64k {}", DebianImage.NameAndTag())); result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } @@ -221,6 +244,7 @@ class WSLCE2EContainerRunTests { std::wstringstream options; options << L"The following options are available:\r\n" + << L" --cidfile Write the container ID to the provided path\r\n" << L" -d,--detach Run container in detached mode\r\n" << L" --entrypoint Specifies the container init process executable\r\n" << L" -e,--env Key=Value pairs for environment variables\r\n"