Add --network-alias support to wslc container create/run (#40759)

beena352 committed Jun 11, 2026 at 15:09 UTC 0e786326c8cf98cd1c7f27200613f75c147a7e4b
17 files changed +410 -7
localization/strings/en-US/Resources.resw
+18
@@ -2346,6 +2346,17 @@ For privacy information about this product please visit https://aka.ms/privacy.<
2346 <value>Endpoint settings are not yet supported (network '{}').</value>
2347 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2348 </data>
2349 + <data name="MessageWslcAliasEmpty" xml:space="preserve">
2350 + <value>Network alias cannot be empty.</value>
2351 + </data>
2352 + <data name="MessageWslcAliasRequiresUserDefinedNetwork" xml:space="preserve">
2353 + <value>Network aliases require a user-defined network. Use --network to specify one.</value>
2354 + <comment>{Locked="--network "}Command line arguments, file names and string inserts should not be translated</comment>
2355 + </data>
2356 + <data name="MessageWslcAliasAmbiguousWithMultipleNetworks" xml:space="preserve">
2357 + <value>Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.</value>
2358 + <comment>{Locked="--network "}Command line arguments, file names and string inserts should not be translated</comment>
2359 + </data>
2360 <data name = "MessageWslcFailedToMountVolume" xml:space = "preserve" >
2361 <value>Failed to create volume '{}': {}</value>
2362 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
@@ -2813,6 +2824,13 @@ On first run, creates the file with all settings commented out at their defaults
2824 <data name="WSLCCLI_NetworkHostModeNotSupportedError" xml:space="preserve">
2825 <value>host mode networking is not supported</value>
2826 </data>
2827 + <data name="WSLCCLI_NetworkAliasArgDescription" xml:space="preserve">
2828 + <value>Add a network-scoped alias for the container</value>
2829 + </data>
2830 + <data name="WSLCCLI_NetworkAliasEmptyError" xml:space="preserve">
2831 + <value>Invalid {} value: network alias cannot be empty or whitespace</value>
2832 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2833 + </data>
2834 <data name="WSLCCLI_NoCacheArgDescription" xml:space="preserve">
2835 <value>Do not use cache when building the image</value>
2836 </data>
src/windows/common/WSLCContainerLauncher.cpp
+16
@@ -250,6 +250,11 @@ void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std
250 m_additionalNetworks.push_back(Name);
251 }
252
253 +void wsl::windows::common::WSLCContainerLauncher::AddPrimaryNetworkAlias(const std::string& Alias)
254 +{
255 + m_primaryNetworkAliases.push_back(Alias);
256 +}
257 +
258 std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::LaunchNoThrow(
259 IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
260 {
@@ -371,6 +376,17 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
376 options.ContainerNetwork.Networks = connections.empty() ? nullptr : connections.data();
377 options.ContainerNetwork.NetworksCount = static_cast<ULONG>(connections.size());
378
379 + // Aliases for the primary endpoint.
380 + std::vector<KeyValuePair> aliasKvps;
381 + aliasKvps.reserve(m_primaryNetworkAliases.size());
382 + for (const auto& alias : m_primaryNetworkAliases)
383 + {
384 + aliasKvps.push_back({.Key = "Aliases", .Value = alias.c_str()});
385 + }
386 +
387 + options.ContainerNetwork.Settings = aliasKvps.empty() ? nullptr : aliasKvps.data();
388 + options.ContainerNetwork.SettingsCount = static_cast<ULONG>(aliasKvps.size());
389 +
390 options.MemoryBytes = m_memoryBytes;
391 options.NanoCpus = m_nanoCpus;
392 options.UlimitsCount = static_cast<ULONG>(m_ulimits.size());
src/windows/common/WSLCContainerLauncher.h
+2
@@ -63,6 +63,7 @@ public:
63 void AddLabel(const std::string& Key, const std::string& Value);
64 void AddTmpfs(const std::string& ContainerPath, const std::string& Options);
65 void AddAdditionalNetwork(const std::string& Name);
66 + void AddPrimaryNetworkAlias(const std::string& Alias);
67
68 std::pair<HRESULT, std::optional<RunningWSLCContainer>> CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
69 RunningWSLCContainer Create(IWSLCSession& Session, IWarningCallback* WarningCallback = nullptr);
@@ -110,6 +111,7 @@ private:
111 std::vector<std::string> m_dnsSearchDomains;
112 std::vector<std::string> m_dnsOptions;
113 std::vector<std::string> m_additionalNetworks;
114 + std::vector<std::string> m_primaryNetworkAliases;
115 std::vector<WSLCLabel> m_labels;
116 std::deque<std::string> m_labelKeys;
117 std::deque<std::string> m_labelValues;
src/windows/inc/docker_schema.h
+17 -2
@@ -264,13 +264,28 @@ struct EndpointSettings
264 std::string Gateway;
265 std::string MacAddress;
266 int IPPrefixLen{};
267 + std::optional<std::vector<std::string>> Aliases;
268
268 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen);
269 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen, Aliases);
270 };
271
272 +struct EndpointConfig
273 +{
274 + std::optional<std::vector<std::string>> Aliases;
275 +};
276 +
277 +inline void to_json(nlohmann::json& j, const EndpointConfig& v)
278 +{
279 + j = nlohmann::json::object();
280 + if (v.Aliases.has_value() && !v.Aliases->empty())
281 + {
282 + j["Aliases"] = *v.Aliases;
283 + }
284 +}
285 +
286 struct NetworkingConfig
287 {
273 - std::map<std::string, EmptyObject> EndpointsConfig;
288 + std::map<std::string, EndpointConfig> EndpointsConfig;
289
290 NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(NetworkingConfig, EndpointsConfig);
291 };
src/windows/inc/wslc_schema.h
+2 -1
@@ -85,8 +85,9 @@ struct InspectEndpointSettings
85 std::string Gateway;
86 std::string MacAddress;
87 int IPPrefixLen{};
88 + std::vector<std::string> Aliases;
89
89 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InspectEndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen);
90 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InspectEndpointSettings, IPAddress, Gateway, MacAddress, IPPrefixLen, Aliases);
91 };
92
93 struct InspectNetworkSettings
src/windows/service/inc/wslc.idl
+5
@@ -301,6 +301,11 @@ typedef struct _WSLCContainerNetwork
301
302 [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks;
303 ULONG NetworksCount;
304 +
305 + // Settings for the primary endpoint (the network identified by NetworkMode).
306 + // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries).
307 + [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
308 + ULONG SettingsCount;
309 } WSLCContainerNetwork;
310
311 typedef enum _WSLCContainerFlags
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -76,6 +76,7 @@ _(Latest, "latest", L"l", Kind::Flag, L
76 _(Memory, "memory", L"m", Kind::Value, Localization::WSLCCLI_MemoryArgDescription()) \
77 _(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \
78 _(Network, "network", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkArgDescription()) \
79 +_(NetworkAlias, "network-alias", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NetworkAliasArgDescription()) \
80 _(NetworkName, "network-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_NetworkNameArgDescription()) \
81 /*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \
82 _(NoCache, "no-cache", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoCacheArgDescription()) \
src/windows/wslc/arguments/ArgumentValidation.cpp
+13
@@ -124,6 +124,19 @@ void Argument::Validate(const ArgMap& execArgs) const
124 break;
125 }
126
127 + case ArgType::NetworkAlias:
128 + {
129 + for (const auto& value : execArgs.GetAll<ArgType::NetworkAlias>())
130 + {
131 + if (value.empty() ||
132 + std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
133 + {
134 + throw ArgumentException(Localization::WSLCCLI_NetworkAliasEmptyError(m_name));
135 + }
136 + }
137 + break;
138 + }
139 +
140 default:
141 break;
142 }
src/windows/wslc/commands/ContainerCreateCommand.cpp
+1
@@ -49,6 +49,7 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
49 Argument::Create(ArgType::Memory),
50 Argument::Create(ArgType::Name),
51 Argument::Create(ArgType::Network, false, NO_LIMIT),
52 + Argument::Create(ArgType::NetworkAlias, false, NO_LIMIT),
53 // Argument::Create(ArgType::NoDNS),
54 // Argument::Create(ArgType::Progress),
55 Argument::Create(ArgType::Publish, false, NO_LIMIT),
src/windows/wslc/commands/ContainerRunCommand.cpp
+1
@@ -49,6 +49,7 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
49 Argument::Create(ArgType::Memory),
50 Argument::Create(ArgType::Name),
51 Argument::Create(ArgType::Network, false, NO_LIMIT),
52 + Argument::Create(ArgType::NetworkAlias, false, NO_LIMIT),
53 // Argument::Create(ArgType::NoDNS),
54 // Argument::Create(ArgType::Progress),
55 Argument::Create(ArgType::Publish, false, NO_LIMIT),
src/windows/wslc/services/ContainerModel.h
+1
@@ -51,6 +51,7 @@ struct ContainerOptions
51 std::vector<std::string> DnsSearchDomains;
52 std::vector<std::string> DnsOptions;
53 std::vector<std::string> Networks;
54 + std::vector<std::string> NetworkAliases;
55 std::vector<std::string> Tmpfs;
56 std::vector<std::pair<std::string, std::string>> Labels;
57 std::optional<std::wstring> CidFile{};
src/windows/wslc/services/ContainerService.cpp
+18
@@ -62,6 +62,24 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
62 containerLauncher.AddAdditionalNetwork(options.Networks[i]);
63 }
64
65 + if (!options.NetworkAliases.empty())
66 + {
67 + THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasRequiresUserDefinedNetwork(), options.Networks.empty());
68 +
69 + THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcAliasAmbiguousWithMultipleNetworks(), options.Networks.size() > 1);
70 +
71 + const auto& primary = options.Networks.front();
72 + THROW_HR_WITH_USER_ERROR_IF(
73 + E_INVALIDARG,
74 + Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
75 + primary == "bridge" || primary == "host" || primary == "none" || primary.starts_with("container:"));
76 +
77 + for (const auto& alias : options.NetworkAliases)
78 + {
79 + containerLauncher.AddPrimaryNetworkAlias(alias);
80 + }
81 + }
82 +
83 // Set port options if provided
84 for (const auto& port : options.Ports)
85 {
src/windows/wslc/tasks/ContainerTasks.cpp
+10
@@ -526,6 +526,16 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
526 }
527 }
528
529 + if (context.Args.Contains(ArgType::NetworkAlias))
530 + {
531 + auto aliases = context.Args.GetAll<ArgType::NetworkAlias>();
532 + options.NetworkAliases.reserve(aliases.size());
533 + for (const auto& value : aliases)
534 + {
535 + options.NetworkAliases.emplace_back(WideToMultiByte(value));
536 + }
537 + }
538 +
539 if (context.Args.Contains(ArgType::User))
540 {
541 options.User = WideToMultiByte(context.Args.Get<ArgType::User>());
src/windows/wslcsession/WSLCContainer.cpp
+54 -4
@@ -220,10 +220,45 @@ std::string ResolveNetworkMode(LPCSTR networkMode, bool hasRequestedPorts, const
220 return std::string{mode};
221 }
222
223 -std::map<std::string, EmptyObject> ResolveEndpoints(
223 +// Parses the primary endpoint's Settings KVP. Today only "Aliases" is recognised; unknown
224 +// keys are rejected to avoid silently dropping caller data (mirrors the per-connection guard).
225 +EndpointConfig ResolvePrimaryEndpointConfig(const KeyValuePair* settings, ULONG count, std::string_view networkMode)
226 +{
227 + EndpointConfig config{};
228 + if (count == 0)
229 + {
230 + return config;
231 + }
232 +
233 + THROW_HR_IF_MSG(E_INVALIDARG, settings == nullptr, "Settings is null with SettingsCount=%lu", count);
234 +
235 + auto parsed = ParseKeyMultiValuePairs(settings, count);
236 +
237 + for (const auto& [key, _] : parsed)
238 + {
239 + THROW_HR_WITH_USER_ERROR_IF(
240 + E_NOTIMPL, Localization::MessageWslcEndpointSettingsNotSupported(std::string{networkMode}), key != "Aliases");
241 + }
242 +
243 + if (auto it = parsed.find("Aliases"); it != parsed.end())
244 + {
245 + for (const auto& alias : it->second)
246 + {
247 + THROW_HR_WITH_USER_ERROR_IF(
248 + E_INVALIDARG,
249 + Localization::MessageWslcAliasEmpty(),
250 + alias.empty() || std::all_of(alias.begin(), alias.end(), [](unsigned char ch) { return std::isspace(ch); }));
251 + }
252 + config.Aliases = std::move(it->second);
253 + }
254 +
255 + return config;
256 +}
257 +
258 +std::map<std::string, EndpointConfig> ResolveEndpoints(
259 const WSLCNetworkConnection* connections, ULONG count, std::string_view resolvedMode, const std::unordered_map<std::string, NetworkEntry>& sessionNetworks)
260 {
226 - std::map<std::string, EmptyObject> resolved;
261 + std::map<std::string, EndpointConfig> resolved;
262 if (count == 0)
263 {
264 return resolved;
@@ -1314,6 +1349,7 @@ WslcInspectContainer WSLCContainerImpl::BuildInspectContainer(const DockerInspec
1349 wslcEndpoint.Gateway = endpoint.Gateway;
1350 wslcEndpoint.MacAddress = endpoint.MacAddress;
1351 wslcEndpoint.IPPrefixLen = endpoint.IPPrefixLen;
1352 + wslcEndpoint.Aliases = endpoint.Aliases.value_or(std::vector<std::string>{});
1353 wslcInspect.NetworkSettings.Networks[name] = std::move(wslcEndpoint);
1354 }
1355
@@ -1586,6 +1622,15 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1622 auto endpoints = ResolveEndpoints(
1623 containerOptions.ContainerNetwork.Networks, containerOptions.ContainerNetwork.NetworksCount, networkMode, sessionNetworks);
1624
1625 + auto primaryConfig =
1626 + ResolvePrimaryEndpointConfig(containerOptions.ContainerNetwork.Settings, containerOptions.ContainerNetwork.SettingsCount, networkMode);
1627 +
1628 + // Aliases require a user-defined endpoint. bridge/host/none/container: modes don't support them.
1629 + THROW_HR_WITH_USER_ERROR_IF(
1630 + E_INVALIDARG,
1631 + Localization::MessageWslcAliasRequiresUserDefinedNetwork(),
1632 + primaryConfig.Aliases.has_value() && (networkMode == "bridge" || !NetworkModeAllocatesVmPorts(networkMode)));
1633 +
1634 auto mappedPorts = BuildPortMappings(ports, networkMode, virtualMachine);
1635
1636 request.HostConfig.NetworkMode = networkMode;
@@ -1593,9 +1638,14 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1638
1639 // Docker API v1.44 (Docker 25.x) requires the primary network to be present in EndpointsConfig
1640 // when EndpointsConfig is non-empty. Insert it so Docker attaches all networks at create time.
1596 - if (!request.NetworkingConfig.EndpointsConfig.empty() && NetworkModeAllocatesVmPorts(networkMode))
1641 + // Also insert when the caller supplied aliases for the primary endpoint.
1642 + if (NetworkModeAllocatesVmPorts(networkMode) && (!request.NetworkingConfig.EndpointsConfig.empty() || primaryConfig.Aliases.has_value()))
1643 {
1598 - request.NetworkingConfig.EndpointsConfig.try_emplace(networkMode);
1644 + auto [it, _] = request.NetworkingConfig.EndpointsConfig.try_emplace(networkMode);
1645 + if (primaryConfig.Aliases.has_value())
1646 + {
1647 + it->second.Aliases = std::move(primaryConfig.Aliases);
1648 + }
1649 }
1650
1651 for (const auto& e : mappedPorts)
test/windows/WSLCTests.cpp
+139
@@ -6830,6 +6830,145 @@ class WSLCTests
6830 }
6831 }
6832
6833 + WSLC_TEST_METHOD(NetworkAliasCreateTest)
6834 + {
6835 + auto createNetwork = [&](const std::string& name, const char* subnet) {
6836 + LOG_IF_FAILED(m_defaultSession->DeleteNetwork(name.c_str()));
6837 + WSLCDriverOption opts[] = {{"Subnet", subnet}};
6838 + WSLCNetworkOptions netOpts{};
6839 + netOpts.Name = name.c_str();
6840 + netOpts.Driver = "bridge";
6841 + netOpts.DriverOpts = opts;
6842 + netOpts.DriverOptsCount = ARRAYSIZE(opts);
6843 + VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&netOpts, nullptr));
6844 + };
6845 +
6846 + auto launchWithAliases = [&](const std::string& containerName, const std::string& networkMode, const std::vector<std::string>& aliases) {
6847 + WSLCContainerLauncher launcher("debian:latest", containerName, {"sleep", "99999"}, {}, networkMode);
6848 + for (const auto& a : aliases)
6849 + {
6850 + launcher.AddPrimaryNetworkAlias(a);
6851 + }
6852 + return launcher.Launch(*m_defaultSession);
6853 + };
6854 +
6855 + auto expectError = [&](const std::string& containerName,
6856 + const std::string& networkMode,
6857 + const std::vector<std::string>& aliases,
6858 + HRESULT expectedResult,
6859 + const std::wstring& expectedErrorMessage) {
6860 + auto result = wil::ResultFromException([&] { launchWithAliases(containerName, networkMode, aliases); });
6861 + VERIFY_ARE_EQUAL(result, expectedResult);
6862 + ValidateCOMErrorMessage(expectedErrorMessage);
6863 + };
6864 +
6865 + // Single user-defined network + single alias — success, round-trips via inspect.
6866 + {
6867 + const std::string networkName = "alias-net-single";
6868 + createNetwork(networkName, "172.60.0.0/16");
6869 + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6870 +
6871 + auto container = launchWithAliases("alias-ctr-single", networkName, {"db"});
6872 +
6873 + auto inspect = container.Inspect();
6874 + VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
6875 + const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
6876 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
6877 + }
6878 +
6879 + // Multiple aliases on a single network — all present.
6880 + {
6881 + const std::string networkName = "alias-net-multi";
6882 + createNetwork(networkName, "172.61.0.0/16");
6883 + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6884 +
6885 + auto container = launchWithAliases("alias-ctr-multi", networkName, {"db", "primary", "backup"});
6886 +
6887 + auto inspect = container.Inspect();
6888 + VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
6889 + const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
6890 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
6891 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "primary") != endpoint.Aliases.end());
6892 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "backup") != endpoint.Aliases.end());
6893 + }
6894 +
6895 + // Alias on 'host' mode — rejected at the IDL layer.
6896 + {
6897 + expectError(
6898 + "alias-ctr-host",
6899 + "host",
6900 + {"db"},
6901 + E_INVALIDARG,
6902 + L"Network aliases require a user-defined network. Use --network to specify one.");
6903 + }
6904 +
6905 + // Alias on default 'bridge' mode — rejected (aliases require a user-defined network).
6906 + {
6907 + expectError(
6908 + "alias-ctr-bridge",
6909 + "bridge",
6910 + {"db"},
6911 + E_INVALIDARG,
6912 + L"Network aliases require a user-defined network. Use --network to specify one.");
6913 + }
6914 +
6915 + // Alias on 'none' mode — rejected.
6916 + {
6917 + expectError(
6918 + "alias-ctr-none",
6919 + "none",
6920 + {"db"},
6921 + E_INVALIDARG,
6922 + L"Network aliases require a user-defined network. Use --network to specify one.");
6923 + }
6924 +
6925 + // Alias on 'container:' mode — rejected.
6926 + {
6927 + WSLCContainerLauncher targetLauncher("debian:latest", "alias-ctr-target", {"sleep", "99999"}, {});
6928 + auto target = targetLauncher.Launch(*m_defaultSession);
6929 +
6930 + expectError(
6931 + "alias-ctr-container",
6932 + "container:alias-ctr-target",
6933 + {"db"},
6934 + E_INVALIDARG,
6935 + L"Network aliases require a user-defined network. Use --network to specify one.");
6936 + }
6937 +
6938 + // Empty alias string — rejected.
6939 + {
6940 + const std::string networkName = "alias-net-empty";
6941 + createNetwork(networkName, "172.62.0.0/16");
6942 + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6943 +
6944 + expectError("alias-ctr-empty", networkName, {""}, E_INVALIDARG, L"Network alias cannot be empty.");
6945 + }
6946 +
6947 + // Unknown KVP key on primary settings — rejected with E_NOTIMPL.
6948 + {
6949 + const std::string networkName = "alias-net-unknown";
6950 + createNetwork(networkName, "172.63.0.0/16");
6951 + auto netCleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });
6952 +
6953 + LPCSTR args[] = {"sleep", "99999"};
6954 + const KeyValuePair settings[] = {{"IPAddress", "10.0.0.5"}};
6955 + WSLCContainerOptions options{};
6956 + options.Image = "debian:latest";
6957 + options.Name = "alias-ctr-unknown";
6958 + options.InitProcessOptions.CommandLine = {.Values = args, .Count = ARRAYSIZE(args)};
6959 + options.ContainerNetwork.NetworkMode = networkName.c_str();
6960 + options.ContainerNetwork.Settings = settings;
6961 + options.ContainerNetwork.SettingsCount = ARRAYSIZE(settings);
6962 +
6963 + wil::com_ptr<IWSLCContainer> container;
6964 + VERIFY_ARE_EQUAL(E_NOTIMPL, m_defaultSession->CreateContainer(&options, nullptr, &container));
6965 + ValidateCOMErrorMessage(std::format(
6966 + L"Endpoint settings are not yet supported (network '{}').",
6967 + std::wstring(networkName.begin(), networkName.end()))
6968 + .c_str());
6969 + }
6970 + }
6971 +
6972 WSLC_TEST_METHOD(ContainerNetworkModeHappyPathTest)
6973 {
6974 // Start container A on the default (bridged) network, then start container B sharing A's
test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+58
@@ -827,6 +827,63 @@ class WSLCE2EContainerCreateTests
827 VerifyContainerIsNotListed(WslcContainerName);
828 }
829
830 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_Success)
831 + {
832 + auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
833 + result.Verify({.Stderr = L"", .ExitCode = 0});
834 + auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
835 +
836 + result = RunWslc(std::format(
837 + L"container create --name {} --network {} --network-alias db {} true", WslcContainerName, TestNetworkName, DebianImage.NameAndTag()));
838 + result.Verify({.Stderr = L"", .ExitCode = 0});
839 +
840 + const auto inspect = InspectContainer(WslcContainerName);
841 + const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
842 + VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
843 + const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
844 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
845 + }
846 +
847 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_NoNetwork_Rejected)
848 + {
849 + auto result =
850 + RunWslc(std::format(L"container create --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
851 + result.Verify(
852 + {.Stderr =
853 + L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
854 + .ExitCode = 1});
855 + VerifyContainerIsNotListed(WslcContainerName);
856 + }
857 +
858 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_NoneMode_Rejected)
859 + {
860 + auto result = RunWslc(std::format(
861 + L"container create --network none --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
862 + result.Verify(
863 + {.Stderr =
864 + L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
865 + .ExitCode = 1});
866 + VerifyContainerIsNotListed(WslcContainerName);
867 + }
868 +
869 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_MultipleNetworks_Rejected)
870 + {
871 + auto result = RunWslc(std::format(
872 + L"container create --network bridge --network bridge --network-alias db --name {} {} true",
873 + WslcContainerName,
874 + DebianImage.NameAndTag()));
875 + result.Verify({.Stderr = L"Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
876 + VerifyContainerIsNotListed(WslcContainerName);
877 + }
878 +
879 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_NetworkAlias_EmptyValue_Rejected)
880 + {
881 + auto result =
882 + RunWslc(std::format(L"container create --network-alias \"\" --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
883 + result.Verify({.Stderr = L"Invalid network-alias value: network alias cannot be empty or whitespace\r\n", .ExitCode = 1});
884 + VerifyContainerIsNotListed(WslcContainerName);
885 + }
886 +
887 WSLC_TEST_METHOD(WSLCE2E_Container_Create_Cpus)
888 {
889 auto result = RunWslc(std::format(L"container create --name {} --cpus 0.5 {} true", WslcContainerName, DebianImage.NameAndTag()));
@@ -1122,6 +1179,7 @@ private:
1179 << L" -m,--memory Memory limit (e.g. 512M, 1G)\r\n"
1180 << L" --name Name of the container\r\n"
1181 << L" --network Connect a container to a network\r\n"
1182 + << L" --network-alias Add a network-scoped alias for the container\r\n"
1183 << L" -p,--publish Publish a port from a container to host\r\n"
1184 << L" -P,--publish-all Publish all exposed ports to random host ports\r\n"
1185 << L" --rm Remove the container after it stops\r\n"
test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+54
@@ -713,6 +713,59 @@ class WSLCE2EContainerRunTests
713 result.Verify({.Stderr = L"Network not found: 'does-not-exist'\r\nError code: WSLC_E_NETWORK_NOT_FOUND\r\n", .ExitCode = 1});
714 }
715
716 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_Success)
717 + {
718 + auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
719 + result.Verify({.Stderr = L"", .ExitCode = 0});
720 + auto cleanupNetwork = wil::scope_exit([&] { EnsureNetworkDoesNotExist(TestNetworkName); });
721 +
722 + result = RunWslc(std::format(
723 + L"container run --name {} --network {} --network-alias db {} true", WslcContainerName, TestNetworkName, DebianImage.NameAndTag()));
724 + result.Verify({.Stderr = L"", .ExitCode = 0});
725 +
726 + const auto inspect = InspectContainer(WslcContainerName);
727 + const auto networkName = wsl::shared::string::WideToMultiByte(TestNetworkName);
728 + VERIFY_IS_TRUE(inspect.NetworkSettings.Networks.contains(networkName));
729 + const auto& endpoint = inspect.NetworkSettings.Networks.at(networkName);
730 + VERIFY_IS_TRUE(std::ranges::find(endpoint.Aliases, "db") != endpoint.Aliases.end());
731 + }
732 +
733 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_NoNetwork_Rejected)
734 + {
735 + auto result =
736 + RunWslc(std::format(L"container run --rm --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
737 + result.Verify(
738 + {.Stderr =
739 + L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
740 + .ExitCode = 1});
741 + }
742 +
743 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_NoneMode_Rejected)
744 + {
745 + auto result = RunWslc(std::format(
746 + L"container run --rm --network none --network-alias db --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
747 + result.Verify(
748 + {.Stderr =
749 + L"Network aliases require a user-defined network. Use --network to specify one.\r\nError code: E_INVALIDARG\r\n",
750 + .ExitCode = 1});
751 + }
752 +
753 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_MultipleNetworks_Rejected)
754 + {
755 + auto result = RunWslc(std::format(
756 + L"container run --rm --network bridge --network bridge --network-alias db --name {} {} true",
757 + WslcContainerName,
758 + DebianImage.NameAndTag()));
759 + result.Verify({.Stderr = L"Network aliases cannot be specified when multiple networks are requested. Use a single --network argument.\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
760 + }
761 +
762 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_NetworkAlias_EmptyValue_Rejected)
763 + {
764 + auto result = RunWslc(
765 + std::format(L"container run --rm --network-alias \"\" --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
766 + result.Verify({.Stderr = L"Invalid network-alias value: network alias cannot be empty or whitespace\r\n", .ExitCode = 1});
767 + }
768 +
769 WSLC_TEST_METHOD(WSLCE2E_Container_Run_Volume_NamedVolume_Success)
770 {
771 // Create a named volume
@@ -973,6 +1026,7 @@ private:
1026 << L" -m,--memory Memory limit (e.g. 512M, 1G)\r\n"
1027 << L" --name Name of the container\r\n"
1028 << L" --network Connect a container to a network\r\n"
1029 + << L" --network-alias Add a network-scoped alias for the container\r\n"
1030 << L" -p,--publish Publish a port from a container to host\r\n"
1031 << L" -P,--publish-all Publish all exposed ports to random host ports\r\n"
1032 << L" --rm Remove the container after it stops\r\n"