diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index 4cb7b83f8..46fa1ab9f 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -38,6 +38,7 @@ struct ContainerOptions bool TTY = false; std::vector Ports; std::vector Volumes; + std::vector Entrypoint; }; struct CreateContainerResult diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 363847359..18ce58917 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -129,6 +129,12 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal( containerLauncher.SetContainerFlags(containerFlags); + if (!options.Entrypoint.empty()) + { + auto entrypoints = options.Entrypoint; + containerLauncher.SetEntrypoint(std::move(entrypoints)); + } + auto [result, runningContainer] = containerLauncher.CreateNoThrow(*session.Get()); if (result == WSLC_E_IMAGE_NOT_FOUND) { diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 823c6a508..c00dd357a 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -258,6 +258,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) } } + if (context.Args.Contains(ArgType::Entrypoint)) + { + options.Entrypoint.push_back(WideToMultiByte(context.Args.Get())); + } + if (context.Args.Contains(ArgType::ForwardArgs)) { auto const& forwardArgs = context.Args.Get(); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index aadb8d292..d770b7253 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -31,6 +31,7 @@ class WSLCE2EContainerRunTests TEST_CLASS_CLEANUP(ClassCleanup) { EnsureContainerDoesNotExist(WslcContainerName); + EnsureImageIsDeleted(DebianImage); return true; } @@ -61,6 +62,43 @@ class WSLCE2EContainerRunTests VerifyContainerIsListed(WslcContainerName, L"exited"); } + TEST_METHOD(WSLCE2E_Container_Run_Entrypoint) + { + WSL2_TEST_ONLY(); + + auto result = RunWslc(std::format(L"container run --rm --entrypoint /bin/whoami {}", DebianImage.NameAndTag())); + result.Verify({.Stdout = L"root\n", .Stderr = L"", .ExitCode = 0}); + } + + TEST_METHOD(WSLCE2E_Container_Run_Entrypoint_And_Arguments) + { + WSL2_TEST_ONLY(); + + auto result = RunWslc( + std::format(L"container run --rm --entrypoint /bin/echo {} hello from entrypoint with args", DebianImage.NameAndTag())); + result.Verify({.Stdout = L"hello from entrypoint with args\n", .Stderr = L"", .ExitCode = 0}); + } + + TEST_METHOD(WSLCE2E_Container_Run_Entrypoint_Invalid_Path) + { + WSL2_TEST_ONLY(); + + auto result = RunWslc(std::format(L"container run --rm --entrypoint /bin/does-not-exist {}", DebianImage.NameAndTag())); + result.Verify( + {.Stdout = L"", .Stderr = L"failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: \"/bin/does-not-exist\": stat /bin/does-not-exist: no such file or directory: unknown\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + } + + TEST_METHOD(WSLCE2E_Container_Run_Entrypoint_Detach_Lifecycle) + { + WSL2_TEST_ONLY(); + + auto result = RunWslc(std::format( + L"container run --name {} -d --entrypoint /bin/sleep {} infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VerifyContainerIsListed(WslcContainerName, L"running"); + } + private: const std::wstring WslcContainerName = L"wslc-test-container"; const TestImage& DebianImage = DebianTestImage(); diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp index 13f2d92c3..a353a3668 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp @@ -177,6 +177,28 @@ void EnsureContainerDoesNotExist(const std::wstring& containerName) } } +std::vector ListAllContainers() +{ + auto result = RunWslc(L"container list --all --format json"); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto jsonOutput = result.GetStdoutOneLine(); + return wsl::shared::FromJson>(jsonOutput.c_str()); +} + +void EnsureImageContainersAreDeleted(const TestImage& image) +{ + auto containers = ListAllContainers(); + for (const auto& container : containers) + { + auto nameAndTag = wsl::shared::string::WideToMultiByte(image.NameAndTag()); + if (container.Image.find(nameAndTag) != std::string::npos) + { + auto result = RunWslc(std::format(L"container remove --force {}", container.Id)); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); + } + } +} + void EnsureImageIsDeleted(const TestImage& image) { auto result = RunWslc(L"image list"); @@ -187,6 +209,7 @@ void EnsureImageIsDeleted(const TestImage& image) { if (line.find(image.NameAndTag()) != std::wstring::npos) { + EnsureImageContainersAreDeleted(image); auto deleteResult = RunWslc(std::format(L"image delete --force {}", image.NameAndTag())); deleteResult.Verify({.Stderr = L"", .ExitCode = 0}); break; diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.h b/test/windows/wslc/e2e/WSLCE2EHelpers.h index c2a43c2b0..26b05767e 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.h +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.h @@ -17,6 +17,7 @@ Module Name: #include #include #include +#include namespace WSLCE2ETests { @@ -103,10 +104,12 @@ void VerifyImageIsNotUsed(const TestImage& image); 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); +std::vector ListAllContainers(); void EnsureContainerDoesNotExist(const std::wstring& containerName); void EnsureImageIsLoaded(const TestImage& image); void EnsureImageIsDeleted(const TestImage& image); +void EnsureImageContainersAreDeleted(const TestImage& image); // Default timeout of 0 will execute once. template