From a7a9a5030ada4629310972ba6723353eeb3e0989 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:20:22 -0700 Subject: [PATCH 1/2] Init network option --- localization/strings/en-US/Resources.resw | 3 ++ .../wslc/arguments/ArgumentDefinitions.h | 5 +-- .../wslc/arguments/ArgumentValidation.cpp | 33 +++++++++++++++++++ .../wslc/arguments/ArgumentValidation.h | 3 ++ .../wslc/commands/ContainerCreateCommand.cpp | 1 + .../wslc/commands/ContainerRunCommand.cpp | 1 + src/windows/wslc/services/ContainerModel.h | 1 + .../wslc/services/ContainerService.cpp | 2 +- src/windows/wslc/tasks/ContainerTasks.cpp | 5 +++ .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 27 +++++++++++++++ .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 22 +++++++++++++ 11 files changed, 100 insertions(+), 3 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index de258d29d..af48af5df 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2565,6 +2565,9 @@ On first run, creates the file with all settings commented out at their defaults Remove the container after it stops + + Connect a container to a network (none, host, bridge) + Session ID diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index a7bfe4e9b..bd47f1bcf 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -65,7 +65,8 @@ _(Input, "input", L"i", Kind::Value, L _(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \ _(Label, "label", NO_ALIAS, Kind::Value, L"Volume metadata setting") \ _(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \ -/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \ +_(Network, "network", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkArgDescription()) \ +/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/\ _(NoCache, "no-cache", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoCacheArgDescription()) \ _(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoPruneArgDescription()) \ _(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoTruncArgDescription()) \ @@ -85,9 +86,9 @@ _(Remove, "rm", NO_ALIAS, Kind::Flag, L _(Server, "server", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_LoginServerArgDescription()) \ _(Session, "session", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SessionIdArgDescription()) \ _(SessionId, "session-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SessionIdPositionalArgDescription()) \ -_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \ _(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription(L"SIGKILL")) \ _(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \ +_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \ _(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \ _(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \ _(Time, "time", L"t", Kind::Value, Localization::WSLCCLI_TimeArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index b96dadc4e..6dc410377 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -40,6 +40,10 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name); break; + case ArgType::Network: + validation::ValidateNetworkType(execArgs.GetAll(), m_name); + break; + case ArgType::Time: validation::ValidateIntegerFromString(execArgs.GetAll(), m_name); break; @@ -191,4 +195,33 @@ InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstri } } +void ValidateNetworkType(const std::vector& values, const std::wstring& argName) +{ + for (const auto& value : values) + { + std::ignore = GetNetworkTypeFromString(value, argName); + } +} + +WSLCContainerNetworkType GetNetworkTypeFromString(const std::wstring& input, const std::wstring& argName) +{ + if (IsEqual(input, L"none")) + { + return WSLCContainerNetworkTypeNone; + } + else if (IsEqual(input, L"host")) + { + return WSLCContainerNetworkTypeHost; + } + else if (IsEqual(input, L"bridge")) + { + return WSLCContainerNetworkTypeBridged; + } + else + { + throw ArgumentException( + std::format(L"Invalid {} argument value: '{}'. Expected one of: none, host, bridge", argName, input)); + } +} + } // namespace wsl::windows::wslc::validation diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 879582558..9497b0f20 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -58,6 +58,9 @@ T GetIntegerFromString(const std::wstring& value, const std::wstring& argName = void ValidateWSLCSignalFromString(const std::vector& values, const std::wstring& argName); WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName = {}); +void ValidateNetworkType(const std::vector& values, const std::wstring& argName); +WSLCContainerNetworkType GetNetworkTypeFromString(const std::wstring& input, const std::wstring& argName = {}); + void ValidateFormatTypeFromString(const std::vector& values, const std::wstring& argName); FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {}); diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 2135c0197..c5cbd83ae 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -44,6 +44,7 @@ std::vector ContainerCreateCommand::GetArguments() const Argument::Create(ArgType::Hostname), Argument::Create(ArgType::Interactive), Argument::Create(ArgType::Name), + Argument::Create(ArgType::Network), // Argument::Create(ArgType::NoDNS), // Argument::Create(ArgType::Progress), Argument::Create(ArgType::Publish, false, NO_LIMIT), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index 7899d3f79..a5484a48d 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -44,6 +44,7 @@ std::vector ContainerRunCommand::GetArguments() const Argument::Create(ArgType::Hostname), Argument::Create(ArgType::Interactive), Argument::Create(ArgType::Name), + Argument::Create(ArgType::Network), // Argument::Create(ArgType::NoDNS), // Argument::Create(ArgType::Progress), Argument::Create(ArgType::Publish, false, NO_LIMIT), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index 5997a2736..9aa72bbb6 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -37,6 +37,7 @@ struct ContainerOptions bool Remove = false; bool TTY = false; bool PublishAll = false; + WSLCContainerNetworkType NetworkType = WSLCContainerNetworkTypeBridged; std::vector Ports; std::vector Volumes; std::string WorkingDirectory; diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 90af89e87..888cf4e8f 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -48,7 +48,7 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio WI_SetFlagIf(containerFlags, WSLCContainerFlagsPublishAll, options.PublishAll); wsl::windows::common::WSLCContainerLauncher containerLauncher( - image, options.Name, options.Arguments, options.EnvironmentVariables, WSLCContainerNetworkTypeBridged, processFlags); + image, options.Name, options.Arguments, options.EnvironmentVariables, options.NetworkType, processFlags); // Set port options if provided for (const auto& port : options.Ports) diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 804f54282..faf186891 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -247,6 +247,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) options.Remove = true; } + if (context.Args.Contains(ArgType::Network)) + { + options.NetworkType = validation::GetNetworkTypeFromString(context.Args.Get()); + } + if (context.Args.Contains(ArgType::Command)) { options.Arguments.emplace_back(WideToMultiByte(context.Args.Get())); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index aa80ddd8e..9ded9a228 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -651,6 +651,32 @@ class WSLCE2EContainerCreateTests VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5 timeout:3") != std::wstring::npos); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Network_None) + { + auto result = RunWslc(std::format( + L"container create --network none --name {} {} cat /etc/resolv.conf", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + std::wstring containerId = result.GetStdoutOneLine(); + VerifyContainerIsListed(containerId, L"created"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Network_Host) + { + auto result = RunWslc(std::format( + L"container create --network host --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + std::wstring containerId = result.GetStdoutOneLine(); + VerifyContainerIsListed(containerId, L"created"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_Network_Invalid) + { + auto result = RunWslc(std::format( + L"container create --network invalid --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.ExitCode = 1}); + VERIFY_IS_TRUE(result.Stderr->find(L"Invalid") != std::wstring::npos); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; @@ -720,6 +746,7 @@ class WSLCE2EContainerCreateTests << L" -h,--hostname Container host name\r\n" << L" -i,--interactive Attach to stdin and keep it open\r\n" << L" --name Name of the container\r\n" + << L" --network Connect a container to a network (none, host, bridge)\r\n" << L" -p,--publish Publish a port from a container to host\r\n" << L" -P,--publish-all Publish all exposed ports to random host ports\r\n" << L" --rm Remove the container after it stops\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 506c2e81b..8f4d5a473 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -641,6 +641,27 @@ class WSLCE2EContainerRunTests result.Verify({.Stderr = std::format(L"Volume not found: '{}'\r\nError code: WSLC_E_VOLUME_NOT_FOUND\r\n", WslcVolumeName), .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Network_None) + { + auto result = RunWslc(std::format(L"container run --rm --network none {} echo hello", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_ARE_EQUAL(*result.Stdout, L"hello\r\n"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Network_Host) + { + auto result = RunWslc(std::format(L"container run --rm --network host {} echo hello", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_ARE_EQUAL(*result.Stdout, L"hello\r\n"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Network_Invalid) + { + auto result = RunWslc(std::format(L"container run --rm --network invalid {} echo hello", DebianImage.NameAndTag())); + result.Verify({.ExitCode = 1}); + VERIFY_IS_TRUE(result.Stderr->find(L"Invalid") != std::wstring::npos); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; @@ -716,6 +737,7 @@ class WSLCE2EContainerRunTests << L" -h,--hostname Container host name\r\n" << L" -i,--interactive Attach to stdin and keep it open\r\n" << L" --name Name of the container\r\n" + << L" --network Connect a container to a network (none, host, bridge)\r\n" << L" -p,--publish Publish a port from a container to host\r\n" << L" -P,--publish-all Publish all exposed ports to random host ports\r\n" << L" --rm Remove the container after it stops\r\n" From 6cf64dca8dafe35493ff4c2ab057f828946a378f Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:46:10 -0700 Subject: [PATCH 2/2] Clang format --- src/windows/wslc/arguments/ArgumentValidation.cpp | 3 +-- test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 6dc410377..f2a53bb9d 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -219,8 +219,7 @@ WSLCContainerNetworkType GetNetworkTypeFromString(const std::wstring& input, con } else { - throw ArgumentException( - std::format(L"Invalid {} argument value: '{}'. Expected one of: none, host, bridge", argName, input)); + throw ArgumentException(std::format(L"Invalid {} argument value: '{}'. Expected one of: none, host, bridge", argName, input)); } } diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 9ded9a228..0dc24e325 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -662,8 +662,8 @@ class WSLCE2EContainerCreateTests WSLC_TEST_METHOD(WSLCE2E_Container_Create_Network_Host) { - auto result = RunWslc(std::format( - L"container create --network host --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + auto result = + RunWslc(std::format(L"container create --network host --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); std::wstring containerId = result.GetStdoutOneLine(); VerifyContainerIsListed(containerId, L"created"); @@ -671,8 +671,8 @@ class WSLCE2EContainerCreateTests WSLC_TEST_METHOD(WSLCE2E_Container_Create_Network_Invalid) { - auto result = RunWslc(std::format( - L"container create --network invalid --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + auto result = RunWslc( + std::format(L"container create --network invalid --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.ExitCode = 1}); VERIFY_IS_TRUE(result.Stderr->find(L"Invalid") != std::wstring::npos); }