Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2505,4 +2505,8 @@ On first run, creates the file with all settings commented out at their defaults
<data name="WSLCCLI_ImageLoadNoInputError" xml:space="preserve">
<value>Requested load but no input provided.</value>
</data>
<data name="WSLCCLI_NoDNSConflictError" xml:space="preserve">
<value>Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search</value>
<comment>{Locked="--no-dns"}{Locked="--dns"}{Locked="--dns-domain"}{Locked="--dns-option"}{Locked="--dns-search"}Command line arguments should not be translated</comment>
</data>
</root>
10 changes: 5 additions & 5 deletions src/windows/wslc/arguments/ArgumentDefinitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ _(Command, "command", NO_ALIAS, Kind::Positional, L
_(ContainerId, "container-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ContainerIdArgDescription()) \
_(Force, "force", L"f", Kind::Flag, Localization::WSLCCLI_ForceArgDescription()) \
_(Detach, "detach", L"d", Kind::Flag, Localization::WSLCCLI_DetachArgDescription()) \
/*_(DNS, "dns", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSArgDescription())*/ \
/*_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription())*/ \
/*_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription())*/ \
/*_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription())*/ \
_(DNS, "dns", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSArgDescription()) \
_(DNSDomain, "dns-domain", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSDomainArgDescription()) \
_(DNSOption, "dns-option", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSOptionArgDescription()) \
_(DNSSearch, "dns-search", NO_ALIAS, Kind::Value, Localization::WSLCCLI_DNSSearchArgDescription()) \
_(Entrypoint, "entrypoint", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EntrypointArgDescription()) \
_(Env, "env", L"e", Kind::Value, Localization::WSLCCLI_EnvArgDescription()) \
_(EnvFile, "env-file", NO_ALIAS, Kind::Value, Localization::WSLCCLI_EnvFileArgDescription()) \
Expand All @@ -59,7 +59,7 @@ _(ImageId, "image", NO_ALIAS, Kind::Positional, L
_(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription()) \
_(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \
_(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \
/*_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription())*/ \
_(NoDNS, "no-dns", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoDNSArgDescription()) \
_(NoPrune, "no-prune", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoPruneArgDescription()) \
_(NoTrunc, "no-trunc", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_NoTruncArgDescription()) \
_(Output, "output", L"o", Kind::Value, Localization::WSLCCLI_OutputArgDescription()) \
Expand Down
10 changes: 10 additions & 0 deletions src/windows/wslc/arguments/ArgumentValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ void Argument::Validate(const ArgMap& execArgs) const
validation::ValidateVolumeMount(execArgs.GetAll<ArgType::Volume>());
break;

case ArgType::NoDNS:
{
if (execArgs.Contains(ArgType::DNS) || execArgs.Contains(ArgType::DNSDomain) ||
execArgs.Contains(ArgType::DNSOption) || execArgs.Contains(ArgType::DNSSearch))
{
throw ArgumentException(Localization::WSLCCLI_NoDNSConflictError());
}
break;
}

case ArgType::WorkDir:
{
const auto& value = execArgs.Get<ArgType::WorkDir>();
Expand Down
10 changes: 5 additions & 5 deletions src/windows/wslc/commands/ContainerCreateCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
Argument::Create(ArgType::Command),
Argument::Create(ArgType::ForwardArgs),
// Argument::Create(ArgType::CIDFile),
// Argument::Create(ArgType::DNS),
// Argument::Create(ArgType::DNSDomain),
// Argument::Create(ArgType::DNSOption),
// Argument::Create(ArgType::DNSSearch),
Argument::Create(ArgType::DNS, false, NO_LIMIT),
Argument::Create(ArgType::DNSDomain),
Argument::Create(ArgType::DNSOption, false, NO_LIMIT),
Argument::Create(ArgType::DNSSearch, false, NO_LIMIT),
Argument::Create(ArgType::Entrypoint),
Argument::Create(ArgType::Env, false, NO_LIMIT),
Argument::Create(ArgType::EnvFile, false, NO_LIMIT),
// Argument::Create(ArgType::GroupId),
Argument::Create(ArgType::Interactive),
Argument::Create(ArgType::Name),
// Argument::Create(ArgType::NoDNS),
Argument::Create(ArgType::NoDNS),
// Argument::Create(ArgType::Progress),
Argument::Create(ArgType::Publish, false, NO_LIMIT),
Argument::Create(ArgType::Remove),
Expand Down
10 changes: 5 additions & 5 deletions src/windows/wslc/commands/ContainerRunCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
Argument::Create(ArgType::ForwardArgs),
// Argument::Create(ArgType::CIDFile),
Argument::Create(ArgType::Detach),
// Argument::Create(ArgType::DNS),
// Argument::Create(ArgType::DNSDomain),
// Argument::Create(ArgType::DNSOption),
// Argument::Create(ArgType::DNSSearch),
Argument::Create(ArgType::DNS, false, NO_LIMIT),
Argument::Create(ArgType::DNSDomain),
Argument::Create(ArgType::DNSOption, false, NO_LIMIT),
Argument::Create(ArgType::DNSSearch, false, NO_LIMIT),
Argument::Create(ArgType::Entrypoint),
Argument::Create(ArgType::Env, false, NO_LIMIT),
Argument::Create(ArgType::EnvFile, false, NO_LIMIT),
Argument::Create(ArgType::Interactive),
Argument::Create(ArgType::Name),
// Argument::Create(ArgType::NoDNS),
Argument::Create(ArgType::NoDNS),
// Argument::Create(ArgType::Progress),
Argument::Create(ArgType::Publish, false, NO_LIMIT),
// Argument::Create(ArgType::Pull),
Expand Down
5 changes: 5 additions & 0 deletions src/windows/wslc/services/ContainerModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ struct ContainerOptions
std::vector<std::string> Entrypoint;
std::optional<std::string> User{};
std::vector<std::string> Tmpfs;
std::vector<std::string> DnsServers;
std::string DnsDomain;
std::vector<std::string> DnsOptions;
std::vector<std::string> DnsSearchDomains;
bool NoDns = false;
};

struct CreateContainerResult
Expand Down
24 changes: 24 additions & 0 deletions src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio
containerLauncher.AddTmpfs(tmpfsMount.ContainerPath(), tmpfsMount.Options());
}

if (!options.DnsServers.empty())
{
auto dnsServers = options.DnsServers;
containerLauncher.SetDnsServers(std::move(dnsServers));
}

if (!options.DnsDomain.empty())
{
auto domainname = options.DnsDomain;
containerLauncher.SetDomainname(std::move(domainname));
}

if (!options.DnsOptions.empty())
{
auto dnsOptions = options.DnsOptions;
containerLauncher.SetDnsOptions(std::move(dnsOptions));
}

if (!options.DnsSearchDomains.empty())
{
auto dnsSearchDomains = options.DnsSearchDomains;
Comment on lines +116 to +130
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--dns-domain is wired to WSLCContainerLauncher::SetDomainname() (Docker Domainname field), which sets the container domainname/hostname domain rather than resolv.conf DNS search/default-domain configuration. This appears inconsistent with the option name/description (“default DNS Domain”). Consider renaming the option to match Docker (--domainname) or changing the implementation to affect DNS resolution (e.g., via DnsSearchDomains/resolv.conf semantics) so behavior matches the CLI surface.

Suggested change
if (!options.DnsDomain.empty())
{
auto domainname = options.DnsDomain;
containerLauncher.SetDomainname(std::move(domainname));
}
if (!options.DnsOptions.empty())
{
auto dnsOptions = options.DnsOptions;
containerLauncher.SetDnsOptions(std::move(dnsOptions));
}
if (!options.DnsSearchDomains.empty())
{
auto dnsSearchDomains = options.DnsSearchDomains;
if (!options.DnsOptions.empty())
{
auto dnsOptions = options.DnsOptions;
containerLauncher.SetDnsOptions(std::move(dnsOptions));
}
auto dnsSearchDomains = options.DnsSearchDomains;
if (!options.DnsDomain.empty())
{
dnsSearchDomains.emplace_back(options.DnsDomain);
}
if (!dnsSearchDomains.empty())
{

Copilot uses AI. Check for mistakes.
containerLauncher.SetDnsSearchDomains(std::move(dnsSearchDomains));
}

auto [result, runningContainer] = containerLauncher.CreateNoThrow(*session.Get());
if (result == WSLC_E_IMAGE_NOT_FOUND)
{
Expand Down
40 changes: 40 additions & 0 deletions src/windows/wslc/tasks/ContainerTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,46 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
}
}

if (context.Args.Contains(ArgType::DNS))
{
auto dnsServers = context.Args.GetAll<ArgType::DNS>();
options.DnsServers.reserve(options.DnsServers.size() + dnsServers.size());
for (const auto& value : dnsServers)
{
options.DnsServers.emplace_back(WideToMultiByte(value));
}
}

if (context.Args.Contains(ArgType::DNSDomain))
{
options.DnsDomain = WideToMultiByte(context.Args.Get<ArgType::DNSDomain>());
}

if (context.Args.Contains(ArgType::DNSOption))
{
auto dnsOptions = context.Args.GetAll<ArgType::DNSOption>();
options.DnsOptions.reserve(options.DnsOptions.size() + dnsOptions.size());
for (const auto& value : dnsOptions)
{
options.DnsOptions.emplace_back(WideToMultiByte(value));
}
}

if (context.Args.Contains(ArgType::DNSSearch))
{
auto dnsSearchDomains = context.Args.GetAll<ArgType::DNSSearch>();
options.DnsSearchDomains.reserve(options.DnsSearchDomains.size() + dnsSearchDomains.size());
for (const auto& value : dnsSearchDomains)
{
options.DnsSearchDomains.emplace_back(WideToMultiByte(value));
}
}

if (context.Args.Contains(ArgType::NoDNS))
{
options.NoDns = true;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--no-dns is parsed into ContainerOptions::NoDns here, but that flag is never consumed when creating/running the container (no references to options.NoDns elsewhere). As a result, --no-dns currently has no behavioral effect beyond conflict validation. Either plumb this through to the container create request (e.g., an explicit runtime/HostConfig setting) or remove the flag until it’s supported.

Suggested change
options.NoDns = true;
THROW_HR_MSG(E_NOTIMPL, "--no-dns is not currently supported.");

Copilot uses AI. Check for mistakes.
}

if (context.Args.Contains(ArgType::ForwardArgs))
{
auto const& forwardArgs = context.Args.Get<ArgType::ForwardArgs>();
Expand Down
81 changes: 81 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,82 @@ class WSLCE2EContainerCreateTests
result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1});
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_SingleServer)
{

auto result = RunWslc(std::format(
L"container create --name {} --dns 8.8.8.8 {} cat /etc/resolv.conf", WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_MultipleServers)
{

auto result = RunWslc(std::format(
L"container create --name {} --dns 8.8.8.8 --dns 8.8.4.4 {} cat /etc/resolv.conf",
WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.4.4") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_Search)
{

auto result = RunWslc(std::format(
L"container create --name {} --dns-search example.com {} cat /etc/resolv.conf",
WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"search example.com") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_Option)
{

auto result = RunWslc(std::format(
L"container create --name {} --dns-option ndots:5 {} cat /etc/resolv.conf",
WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_DNS_AllOptions)
{

auto result = RunWslc(std::format(
L"container create --name {} --dns 1.1.1.1 --dns-search test.local --dns-option ndots:3 {} cat /etc/resolv.conf",
WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 1.1.1.1") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"search test.local") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:3") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_NoDNS_ConflictWithDNS_Fails)
{

auto result = RunWslc(std::format(
L"container create --name {} --no-dns --dns 8.8.8.8 {} echo test",
WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search\r\n", .ExitCode = 1});
}

private:
// Test container name
const std::wstring WslcContainerName = L"wslc-test-container";
Expand Down Expand Up @@ -1146,11 +1222,16 @@ class WSLCE2EContainerCreateTests
{
std::wstringstream options;
options << L"The following options are available:\r\n" //
<< L" --dns IP address of the DNS nameserver in resolv.conf\r\n"
<< L" --dns-domain Set the default DNS Domain\r\n"
<< L" --dns-option Set DNS options\r\n"
<< L" --dns-search Set DNS search domains\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"
<< L" -i,--interactive Attach to stdin and keep it open\r\n"
<< L" --name Name of the container\r\n"
<< L" --no-dns No configuration of DNS in the container\r\n"
<< L" -p,--publish Publish a port from a container to host\r\n"
<< L" --rm Remove the container after it stops\r\n"
<< L" --session Specify the session to use\r\n"
Expand Down
62 changes: 62 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,63 @@ class WSLCE2EContainerRunTests
result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1});
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_SingleServer)
{

auto result = RunWslc(std::format(
L"container run --rm --dns 8.8.8.8 {} cat /etc/resolv.conf", DebianImage.NameAndTag()));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_MultipleServers)
{

auto result = RunWslc(std::format(
L"container run --rm --dns 8.8.8.8 --dns 8.8.4.4 {} cat /etc/resolv.conf", DebianImage.NameAndTag()));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.8.8") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 8.8.4.4") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_Search)
{

auto result = RunWslc(std::format(
L"container run --rm --dns-search example.com {} cat /etc/resolv.conf", DebianImage.NameAndTag()));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"search example.com") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_Option)
{

auto result = RunWslc(std::format(
L"container run --rm --dns-option ndots:5 {} cat /etc/resolv.conf", DebianImage.NameAndTag()));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_DNS_AllOptions)
{

auto result = RunWslc(std::format(
L"container run --rm --dns 1.1.1.1 --dns-search test.local --dns-option ndots:3 {} cat /etc/resolv.conf",
DebianImage.NameAndTag()));
result.Verify({.ExitCode = 0});
VERIFY_IS_TRUE(result.Stdout->find(L"nameserver 1.1.1.1") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"search test.local") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:3") != std::wstring::npos);
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_NoDNS_ConflictWithDNS_Fails)
{

auto result = RunWslc(std::format(
L"container run --rm --no-dns --dns 8.8.8.8 {} echo test", DebianImage.NameAndTag()));
result.Verify({.Stderr = L"Cannot use --no-dns with --dns, --dns-domain, --dns-option, or --dns-search\r\n", .ExitCode = 1});
}

private:
const std::wstring WslcContainerName = L"wslc-test-container";
const TestImage& DebianImage = DebianTestImage();
Expand Down Expand Up @@ -212,11 +269,16 @@ class WSLCE2EContainerRunTests
std::wstringstream options;
options << L"The following options are available:\r\n"
<< L" -d,--detach Run container in detached mode\r\n"
<< L" --dns IP address of the DNS nameserver in resolv.conf\r\n"
<< L" --dns-domain Set the default DNS Domain\r\n"
<< L" --dns-option Set DNS options\r\n"
<< L" --dns-search Set DNS search domains\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"
<< L" -i,--interactive Attach to stdin and keep it open\r\n"
<< L" --name Name of the container\r\n"
<< L" --no-dns No configuration of DNS in the container\r\n"
<< L" -p,--publish Publish a port from a container to host\r\n"
<< L" --rm Remove the container after it stops\r\n"
<< L" --session Specify the session to use\r\n"
Expand Down