From c0e77fb3492733bd0c0a486a237fef17ae527683 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 24 Apr 2026 11:15:02 -0700
Subject: [PATCH 1/3] Init network commands
---
localization/strings/en-US/Resources.resw | 37 ++++
src/shared/inc/JsonUtils.h | 20 ++
.../wslc/arguments/ArgumentDefinitions.h | 1 +
src/windows/wslc/commands/NetworkCommand.cpp | 51 +++++
src/windows/wslc/commands/NetworkCommand.h | 95 ++++++++
.../wslc/commands/NetworkCreateCommand.cpp | 53 +++++
.../wslc/commands/NetworkInspectCommand.cpp | 50 +++++
.../wslc/commands/NetworkListCommand.cpp | 65 ++++++
.../wslc/commands/NetworkRemoveCommand.cpp | 50 +++++
src/windows/wslc/commands/RootCommand.cpp | 2 +
src/windows/wslc/core/ExecutionContextData.h | 2 +
src/windows/wslc/services/NetworkModel.h | 30 +++
src/windows/wslc/services/NetworkService.cpp | 82 +++++++
src/windows/wslc/services/NetworkService.h | 28 +++
src/windows/wslc/tasks/NetworkTasks.cpp | 205 ++++++++++++++++++
src/windows/wslc/tasks/NetworkTasks.h | 24 ++
test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp | 1 +
test/windows/wslc/e2e/WSLCE2EHelpers.cpp | 55 +++++
test/windows/wslc/e2e/WSLCE2EHelpers.h | 4 +
.../wslc/e2e/WSLCE2ENetworkCreateTests.cpp | 143 ++++++++++++
.../wslc/e2e/WSLCE2ENetworkInspectTests.cpp | 160 ++++++++++++++
.../wslc/e2e/WSLCE2ENetworkListTests.cpp | 134 ++++++++++++
.../wslc/e2e/WSLCE2ENetworkRemoveTests.cpp | 152 +++++++++++++
test/windows/wslc/e2e/WSLCE2ENetworkTests.cpp | 101 +++++++++
24 files changed, 1545 insertions(+)
create mode 100644 src/windows/wslc/commands/NetworkCommand.cpp
create mode 100644 src/windows/wslc/commands/NetworkCommand.h
create mode 100644 src/windows/wslc/commands/NetworkCreateCommand.cpp
create mode 100644 src/windows/wslc/commands/NetworkInspectCommand.cpp
create mode 100644 src/windows/wslc/commands/NetworkListCommand.cpp
create mode 100644 src/windows/wslc/commands/NetworkRemoveCommand.cpp
create mode 100644 src/windows/wslc/services/NetworkModel.h
create mode 100644 src/windows/wslc/services/NetworkService.cpp
create mode 100644 src/windows/wslc/services/NetworkService.h
create mode 100644 src/windows/wslc/tasks/NetworkTasks.cpp
create mode 100644 src/windows/wslc/tasks/NetworkTasks.h
create mode 100644 test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
create mode 100644 test/windows/wslc/e2e/WSLCE2ENetworkInspectTests.cpp
create mode 100644 test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
create mode 100644 test/windows/wslc/e2e/WSLCE2ENetworkRemoveTests.cpp
create mode 100644 test/windows/wslc/e2e/WSLCE2ENetworkTests.cpp
diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index de258d29d..ebf66d97f 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2690,6 +2690,43 @@ On first run, creates the file with all settings commented out at their defaults
Requested load but no input provided.
+
+ Manage networks.
+
+
+ Manage the lifecycle of WSL networks, including creating, inspecting, listing, and deleting them.
+ {Locked="WSL"}Product names should not be translated
+
+
+ Create a network.
+
+
+ Creates a new network.
+
+
+ Remove one or more networks.
+
+
+ Removes one or more networks. A network cannot be removed if it has active endpoints.
+
+
+ Display detailed information on one or more networks.
+
+
+ Display detailed information on one or more networks.
+
+
+ List networks.
+
+
+ Lists all networks in the session.
+
+
+ Network name
+
+
+ Outputs the network names only
+
Manage volumes.
diff --git a/src/shared/inc/JsonUtils.h b/src/shared/inc/JsonUtils.h
index 63df6d29b..9bd4f2fbb 100644
--- a/src/shared/inc/JsonUtils.h
+++ b/src/shared/inc/JsonUtils.h
@@ -190,6 +190,26 @@ struct adl_serializer
strncpy_s(volume.Driver, sizeof(volume.Driver), driver.c_str(), _TRUNCATE);
}
};
+
+template <>
+struct adl_serializer
+{
+ static void to_json(json& j, const WSLCNetworkInformation& network)
+ {
+ j = json{{"Name", std::string(network.Name)}, {"Id", std::string(network.Id)}, {"Driver", std::string(network.Driver)}};
+ }
+
+ static void from_json(const json& j, WSLCNetworkInformation& network)
+ {
+ std::string name = j.at("Name").get();
+ std::string id = j.at("Id").get();
+ std::string driver = j.at("Driver").get();
+
+ strncpy_s(network.Name, sizeof(network.Name), name.c_str(), _TRUNCATE);
+ strncpy_s(network.Id, sizeof(network.Id), id.c_str(), _TRUNCATE);
+ strncpy_s(network.Driver, sizeof(network.Driver), driver.c_str(), _TRUNCATE);
+ }
+};
#endif
} // namespace nlohmann
\ No newline at end of file
diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h
index a7bfe4e9b..1af70e6ad 100644
--- a/src/windows/wslc/arguments/ArgumentDefinitions.h
+++ b/src/windows/wslc/arguments/ArgumentDefinitions.h
@@ -65,6 +65,7 @@ _(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()) \
+_(NetworkName, "network-name", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_NetworkNameArgDescription()) \
/*_(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()) \
diff --git a/src/windows/wslc/commands/NetworkCommand.cpp b/src/windows/wslc/commands/NetworkCommand.cpp
new file mode 100644
index 000000000..b8a8b6ad5
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkCommand.cpp
@@ -0,0 +1,51 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+#include "CLIExecutionContext.h"
+#include "NetworkCommand.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Network Root Command
+std::vector> NetworkCommand::GetCommands() const
+{
+ std::vector> commands;
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
+ return commands;
+}
+
+std::vector NetworkCommand::GetArguments() const
+{
+ return {};
+}
+
+std::wstring NetworkCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_NetworkCommandDesc();
+}
+
+std::wstring NetworkCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_NetworkCommandLongDesc();
+}
+
+void NetworkCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ OutputHelp();
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/NetworkCommand.h b/src/windows/wslc/commands/NetworkCommand.h
new file mode 100644
index 000000000..8b4a22fb9
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkCommand.h
@@ -0,0 +1,95 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkCommand.h
+
+Abstract:
+
+ Declaration of command classes and interfaces.
+
+--*/
+#pragma once
+#include "Command.h"
+
+namespace wsl::windows::wslc {
+// Root Network Command
+struct NetworkCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"network";
+ NetworkCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+ std::vector> GetCommands() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Create Command
+struct NetworkCreateCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"create";
+ NetworkCreateCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Remove Command
+struct NetworkRemoveCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"remove";
+ NetworkRemoveCommand(const std::wstring& parent) : Command(CommandName, {L"delete", L"rm"}, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Inspect Command
+struct NetworkInspectCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"inspect";
+ NetworkInspectCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// List Command
+struct NetworkListCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"list";
+ NetworkListCommand(const std::wstring& parent) : Command(CommandName, {L"ls"}, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ValidateArgumentsInternal(const ArgMap& execArgs) const override;
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/NetworkCreateCommand.cpp b/src/windows/wslc/commands/NetworkCreateCommand.cpp
new file mode 100644
index 000000000..9f8abc5e8
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkCreateCommand.cpp
@@ -0,0 +1,53 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkCreateCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "NetworkCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "NetworkTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Network Create Command
+std::vector NetworkCreateCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::NetworkName, true),
+ Argument::Create(ArgType::Driver),
+ Argument::Create(ArgType::Options, false, NO_LIMIT),
+ Argument::Create(ArgType::Label, false, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring NetworkCreateCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_NetworkCreateDesc();
+}
+
+std::wstring NetworkCreateCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_NetworkCreateLongDesc();
+}
+
+void NetworkCreateCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << CreateNetwork;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/NetworkInspectCommand.cpp b/src/windows/wslc/commands/NetworkInspectCommand.cpp
new file mode 100644
index 000000000..422673bd4
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkInspectCommand.cpp
@@ -0,0 +1,50 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkInspectCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "NetworkCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "NetworkTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Network Inspect Command
+std::vector NetworkInspectCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::NetworkName, true, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring NetworkInspectCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_NetworkInspectDesc();
+}
+
+std::wstring NetworkInspectCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_NetworkInspectLongDesc();
+}
+
+void NetworkInspectCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << InspectNetworks;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/NetworkListCommand.cpp b/src/windows/wslc/commands/NetworkListCommand.cpp
new file mode 100644
index 000000000..5e7e06853
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkListCommand.cpp
@@ -0,0 +1,65 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkListCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "NetworkCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "NetworkTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+
+namespace wsl::windows::wslc {
+// Network List Command
+std::vector NetworkListCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::Format),
+ Argument::Create(ArgType::Quiet, false, std::nullopt, Localization::WSLCCLI_NetworkListQuietArgDesc()),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring NetworkListCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_NetworkListDesc();
+}
+
+std::wstring NetworkListCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_NetworkListLongDesc();
+}
+
+void NetworkListCommand::ValidateArgumentsInternal(const ArgMap& execArgs) const
+{
+ if (execArgs.Contains(ArgType::Format))
+ {
+ auto format = execArgs.Get();
+ if (!IsEqual(format, L"json") && !IsEqual(format, L"table"))
+ {
+ throw CommandException(Localization::WSLCCLI_InvalidFormatError());
+ }
+ }
+}
+
+void NetworkListCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << GetNetworks //
+ << ListNetworks;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/NetworkRemoveCommand.cpp b/src/windows/wslc/commands/NetworkRemoveCommand.cpp
new file mode 100644
index 000000000..567a3d719
--- /dev/null
+++ b/src/windows/wslc/commands/NetworkRemoveCommand.cpp
@@ -0,0 +1,50 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkRemoveCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "NetworkCommand.h"
+#include "CLIExecutionContext.h"
+#include "SessionTasks.h"
+#include "NetworkTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Network Remove Command
+std::vector NetworkRemoveCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::NetworkName, true, NO_LIMIT),
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring NetworkRemoveCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_NetworkRemoveDesc();
+}
+
+std::wstring NetworkRemoveCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_NetworkRemoveLongDesc();
+}
+
+void NetworkRemoveCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context << CreateSession //
+ << DeleteNetworks;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp
index 5b28bb3f4..21d949437 100644
--- a/src/windows/wslc/commands/RootCommand.cpp
+++ b/src/windows/wslc/commands/RootCommand.cpp
@@ -16,6 +16,7 @@ Module Name:
// Include all commands that parent to the root.
#include "ContainerCommand.h"
#include "ImageCommand.h"
+#include "NetworkCommand.h"
#include "RegistryCommand.h"
#include "SessionCommand.h"
#include "SettingsCommand.h"
@@ -32,6 +33,7 @@ std::vector> RootCommand::GetCommands() const
std::vector> commands;
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
diff --git a/src/windows/wslc/core/ExecutionContextData.h b/src/windows/wslc/core/ExecutionContextData.h
index a443d71a8..b43018e2c 100644
--- a/src/windows/wslc/core/ExecutionContextData.h
+++ b/src/windows/wslc/core/ExecutionContextData.h
@@ -38,6 +38,7 @@ enum class Data : size_t
ContainerOptions,
Images,
Volumes,
+ Networks,
Max
};
@@ -53,6 +54,7 @@ namespace details {
DEFINE_DATA_MAPPING(ContainerOptions, wsl::windows::wslc::models::ContainerOptions);
DEFINE_DATA_MAPPING(Images, std::vector);
DEFINE_DATA_MAPPING(Volumes, std::vector);
+ DEFINE_DATA_MAPPING(Networks, std::vector);
} // namespace details
struct DataMap : wsl::windows::wslc::EnumBasedVariantMap
diff --git a/src/windows/wslc/services/NetworkModel.h b/src/windows/wslc/services/NetworkModel.h
new file mode 100644
index 000000000..4a69a1d62
--- /dev/null
+++ b/src/windows/wslc/services/NetworkModel.h
@@ -0,0 +1,30 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkModel.h
+
+Abstract:
+
+ This file contains the NetworkModel definitions
+
+--*/
+
+#pragma once
+
+#include "JsonUtils.h"
+#include
+
+namespace wsl::windows::wslc::models {
+
+struct CreateNetworkOptions
+{
+ std::string Name;
+ std::optional Driver;
+ std::vector> DriverOpts{};
+ std::vector> Labels{};
+};
+
+} // namespace wsl::windows::wslc::models
diff --git a/src/windows/wslc/services/NetworkService.cpp b/src/windows/wslc/services/NetworkService.cpp
new file mode 100644
index 000000000..b18140d55
--- /dev/null
+++ b/src/windows/wslc/services/NetworkService.cpp
@@ -0,0 +1,82 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkService.cpp
+
+Abstract:
+
+ This file contains the NetworkService implementation
+
+--*/
+#include "NetworkService.h"
+#include
+#include
+
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+using namespace wsl::windows::common::wslutil;
+
+namespace wsl::windows::wslc::services {
+
+void NetworkService::Create(models::Session& session, const models::CreateNetworkOptions& createOptions)
+{
+ WSLCNetworkOptions options{};
+ options.Name = createOptions.Name.c_str();
+ if (createOptions.Driver.has_value())
+ {
+ options.Driver = createOptions.Driver->c_str();
+ }
+
+ // Set driver options
+ std::vector driverOpts;
+ for (const auto& option : createOptions.DriverOpts)
+ {
+ driverOpts.push_back({.Key = option.first.c_str(), .Value = option.second.c_str()});
+ }
+
+ // Set labels
+ std::vector labels;
+ for (const auto& label : createOptions.Labels)
+ {
+ labels.push_back({.Key = label.first.c_str(), .Value = label.second.c_str()});
+ }
+
+ options.DriverOpts = driverOpts.data();
+ options.DriverOptsCount = static_cast(driverOpts.size());
+ options.Labels = labels.data();
+ options.LabelsCount = static_cast(labels.size());
+
+ THROW_IF_FAILED(session.Get()->CreateNetwork(&options));
+}
+
+void NetworkService::Delete(models::Session& session, const std::string& name)
+{
+ THROW_IF_FAILED(session.Get()->DeleteNetwork(name.c_str()));
+}
+
+std::vector NetworkService::List(models::Session& session)
+{
+ wil::unique_cotaskmem_array_ptr rawNetworks;
+ ULONG count = 0;
+ THROW_IF_FAILED(session.Get()->ListNetworks(&rawNetworks, &count));
+
+ std::vector networks;
+ networks.reserve(count);
+ for (auto ptr = rawNetworks.get(), end = rawNetworks.get() + count; ptr != end; ++ptr)
+ {
+ networks.push_back(*ptr);
+ }
+
+ return networks;
+}
+
+wsl::windows::common::wslc_schema::InspectNetwork NetworkService::Inspect(models::Session& session, const std::string& name)
+{
+ wil::unique_cotaskmem_ansistring output;
+ THROW_IF_FAILED(session.Get()->InspectNetwork(name.c_str(), &output));
+ return FromJson(output.get());
+}
+} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/services/NetworkService.h b/src/windows/wslc/services/NetworkService.h
new file mode 100644
index 000000000..000312b8f
--- /dev/null
+++ b/src/windows/wslc/services/NetworkService.h
@@ -0,0 +1,28 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkService.h
+
+Abstract:
+
+ This file contains the NetworkService definition
+
+--*/
+#pragma once
+
+#include "SessionModel.h"
+#include "NetworkModel.h"
+#include
+
+namespace wsl::windows::wslc::services {
+struct NetworkService
+{
+ static void Create(models::Session& session, const models::CreateNetworkOptions& createOptions);
+ static void Delete(models::Session& session, const std::string& name);
+ static std::vector List(models::Session& session);
+ static wsl::windows::common::wslc_schema::InspectNetwork Inspect(models::Session& session, const std::string& name);
+};
+} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/tasks/NetworkTasks.cpp b/src/windows/wslc/tasks/NetworkTasks.cpp
new file mode 100644
index 000000000..f359cf7c1
--- /dev/null
+++ b/src/windows/wslc/tasks/NetworkTasks.cpp
@@ -0,0 +1,205 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkTasks.cpp
+
+Abstract:
+
+ Implementation of network command related execution logic.
+
+--*/
+#include "Argument.h"
+#include "ArgumentValidation.h"
+#include "CLIExecutionContext.h"
+#include "NetworkModel.h"
+#include "NetworkService.h"
+#include "NetworkTasks.h"
+#include "TableOutput.h"
+#include
+
+using namespace wsl::shared;
+using namespace wsl::windows::common;
+using namespace wsl::windows::common::string;
+using namespace wsl::windows::common::wslutil;
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::models;
+using namespace wsl::windows::wslc::services;
+
+namespace wsl::windows::wslc::task {
+
+static std::pair OptionsToKeyValue(const std::wstring& option)
+{
+ auto pos = option.find('=');
+ if (pos == std::wstring::npos)
+ {
+ return {WideToMultiByte(option), std::string()};
+ }
+
+ return {WideToMultiByte(option.substr(0, pos)), WideToMultiByte(option.substr(pos + 1))};
+}
+
+static bool TryInspectNetwork(Session& session, const std::string& networkName, std::optional& inspectData)
+{
+ try
+ {
+ inspectData = NetworkService::Inspect(session, networkName);
+ return true;
+ }
+ catch (const wil::ResultException& ex)
+ {
+ if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND)
+ {
+ PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr);
+ return false;
+ }
+
+ throw;
+ }
+}
+
+static bool TryDeleteNetwork(Session& session, const std::string& networkName)
+{
+ try
+ {
+ NetworkService::Delete(session, networkName);
+ return true;
+ }
+ catch (const wil::ResultException& ex)
+ {
+ if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND)
+ {
+ PrintMessage(Localization::MessageWslcNetworkNotFound(networkName.c_str()), stderr);
+ return false;
+ }
+
+ throw;
+ }
+}
+
+void CreateNetwork(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
+
+ models::CreateNetworkOptions options{};
+ options.Name = WideToMultiByte(context.Args.Get());
+
+ for (const auto& option : context.Args.GetAll())
+ {
+ options.DriverOpts.push_back(OptionsToKeyValue(option));
+ }
+
+ for (const auto& label : context.Args.GetAll())
+ {
+ options.Labels.push_back(OptionsToKeyValue(label));
+ }
+
+ if (context.Args.Contains(ArgType::Driver))
+ {
+ options.Driver = WideToMultiByte(context.Args.Get());
+ }
+
+ NetworkService::Create(context.Data.Get(), options);
+ PrintMessage(MultiByteToWide(options.Name));
+}
+
+void DeleteNetworks(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ auto networkNames = context.Args.GetAll();
+ for (const auto& name : networkNames)
+ {
+ if (TryDeleteNetwork(session, WideToMultiByte(name)))
+ {
+ PrintMessage(name);
+ }
+ else
+ {
+ context.ExitCode = 1;
+ }
+ }
+}
+
+void GetNetworks(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ context.Data.Add(NetworkService::List(session));
+}
+
+void InspectNetworks(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ auto networkNames = context.Args.GetAll();
+ std::vector result;
+ for (const auto& name : networkNames)
+ {
+ std::optional inspectData;
+ if (TryInspectNetwork(session, WideToMultiByte(name), inspectData))
+ {
+ result.push_back(*inspectData);
+ }
+ else
+ {
+ context.ExitCode = 1;
+ }
+ }
+
+ auto json = ToJson(result, c_jsonPrettyPrintIndent);
+ PrintMessage(MultiByteToWide(json));
+}
+
+void ListNetworks(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Networks));
+ auto& networks = context.Data.Get();
+
+ if (context.Args.Contains(ArgType::Quiet))
+ {
+ for (const auto& network : networks)
+ {
+ PrintMessage(MultiByteToWide(network.Name));
+ }
+
+ return;
+ }
+
+ FormatType format = FormatType::Table;
+ if (context.Args.Contains(ArgType::Format))
+ {
+ format = validation::GetFormatTypeFromString(context.Args.Get());
+ }
+
+ switch (format)
+ {
+ case FormatType::Json:
+ {
+ auto json = ToJson(networks, c_jsonPrettyPrintIndent);
+ PrintMessage(MultiByteToWide(json));
+ break;
+ }
+ case FormatType::Table:
+ {
+ auto table = wsl::windows::wslc::TableOutput<3>({L"NETWORK ID", L"NAME", L"DRIVER"});
+ for (const auto& network : networks)
+ {
+ table.OutputLine({
+ MultiByteToWide(TruncateId(network.Id)),
+ MultiByteToWide(network.Name),
+ MultiByteToWide(network.Driver),
+ });
+ }
+
+ table.Complete();
+ break;
+ }
+ default:
+ THROW_HR(E_UNEXPECTED);
+ }
+}
+} // namespace wsl::windows::wslc::task
diff --git a/src/windows/wslc/tasks/NetworkTasks.h b/src/windows/wslc/tasks/NetworkTasks.h
new file mode 100644
index 000000000..0796b1a48
--- /dev/null
+++ b/src/windows/wslc/tasks/NetworkTasks.h
@@ -0,0 +1,24 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ NetworkTasks.h
+
+Abstract:
+
+ Declaration of network command execution tasks.
+
+--*/
+#pragma once
+#include "CLIExecutionContext.h"
+
+namespace wsl::windows::wslc::task {
+
+void CreateNetwork(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void DeleteNetworks(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void GetNetworks(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void InspectNetworks(wsl::windows::wslc::execution::CLIExecutionContext& context);
+void ListNetworks(wsl::windows::wslc::execution::CLIExecutionContext& context);
+} // namespace wsl::windows::wslc::task
diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
index 3a59802a2..3367162f4 100644
--- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
@@ -525,6 +525,7 @@ class WSLCE2EGlobalTests
std::vector> entries = {
{L"container", Localization::WSLCCLI_ContainerCommandDesc()},
{L"image", Localization::WSLCCLI_ImageCommandDesc()},
+ {L"network", Localization::WSLCCLI_NetworkCommandDesc()},
{L"registry", Localization::WSLCCLI_RegistryCommandDesc()},
{L"session", Localization::WSLCCLI_SessionCommandDesc()},
{L"settings", Localization::WSLCCLI_SettingsCommandDesc()},
diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
index 7039822a5..213b4e902 100644
--- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp
@@ -244,6 +244,36 @@ void VerifyVolumeIsNotListed(const std::wstring& volumeName)
}
}
+void VerifyNetworkIsListed(const std::wstring& networkName)
+{
+ auto result = RunWslc(L"network list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto networks = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& net : networks)
+ {
+ if (net.Name == wsl::shared::string::WideToMultiByte(networkName))
+ {
+ return;
+ }
+ }
+
+ VERIFY_FAIL(std::format(L"Network '{}' not found in network list output", networkName).c_str());
+}
+
+void VerifyNetworkIsNotListed(const std::wstring& networkName)
+{
+ auto result = RunWslc(L"network list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto networks = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& net : networks)
+ {
+ if (net.Name == wsl::shared::string::WideToMultiByte(networkName))
+ {
+ VERIFY_FAIL(std::format(L"Network '{}' found in network list output", networkName).c_str());
+ }
+ }
+}
+
std::string GetHashId(const std::string& id, bool fullId)
{
return wsl::windows::common::string::TruncateId(id, !fullId);
@@ -426,6 +456,31 @@ void EnsureVolumeDoesNotExist(const std::wstring& volumeName)
}
}
+void EnsureNetworkDoesNotExist(const std::wstring& networkName)
+{
+ auto result = RunWslc(L"network list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto networks = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ for (const auto& net : networks)
+ {
+ if (net.Name == wsl::shared::string::WideToMultiByte(networkName))
+ {
+ auto deleteResult = RunWslc(std::format(L"network rm {}", networkName));
+ deleteResult.Verify({.Stderr = L"", .ExitCode = 0});
+ break;
+ }
+ }
+}
+
+wslc_schema::InspectNetwork InspectNetwork(const std::wstring& networkName)
+{
+ auto result = RunWslc(std::format(L"network inspect {}", networkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData = wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ return inspectData[0];
+}
+
wil::com_ptr OpenDefaultElevatedSession()
{
// Ensure the default elevated session exists before opening it via COM.
diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.h b/test/windows/wslc/e2e/WSLCE2EHelpers.h
index 926c8112c..28996f3b9 100644
--- a/test/windows/wslc/e2e/WSLCE2EHelpers.h
+++ b/test/windows/wslc/e2e/WSLCE2EHelpers.h
@@ -123,11 +123,14 @@ void VerifyImageIsNotUsed(const TestImage& image);
void VerifyImageIsListed(const TestImage& image);
void VerifyVolumeIsListed(const std::wstring& volumeName);
void VerifyVolumeIsNotListed(const std::wstring& volumeName);
+void VerifyNetworkIsListed(const std::wstring& networkName);
+void VerifyNetworkIsNotListed(const std::wstring& networkName);
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);
wsl::windows::common::wslc_schema::InspectVolume InspectVolume(const std::wstring& volumeName);
+wsl::windows::common::wslc_schema::InspectNetwork InspectNetwork(const std::wstring& networkName);
std::vector ListAllContainers();
void EnsureContainerDoesNotExist(const std::wstring& containerName);
@@ -136,6 +139,7 @@ void EnsureImageIsDeleted(const TestImage& image);
void EnsureImageContainersAreDeleted(const TestImage& image);
void EnsureSessionIsTerminated(const std::wstring& sessionName = L"");
void EnsureVolumeDoesNotExist(const std::wstring& volumeName);
+void EnsureNetworkDoesNotExist(const std::wstring& networkName);
void WriteTestFile(const std::filesystem::path& filePath, const std::vector& envVariableLines);
std::wstring GetPythonHttpServerScript(uint16_t port);
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
new file mode 100644
index 000000000..f367aef3e
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
@@ -0,0 +1,143 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2ENetworkCreateTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2ENetworkCreateTests
+{
+ WSLC_TEST_CLASS(WSLCE2ENetworkCreateTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_HelpCommand)
+ {
+ auto result = RunWslc(L"network create --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_MissingName)
+ {
+ auto result = RunWslc(L"network create");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'network-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_Success)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName, result.GetStdoutOneLine());
+
+ VerifyNetworkIsListed(TestNetworkName);
+ auto inspect = InspectNetwork(TestNetworkName);
+ VERIFY_ARE_EQUAL("bridge", inspect.Driver);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_WithLabels_Success)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge --label env=test --label app=wslc {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName, result.GetStdoutOneLine());
+
+ VerifyNetworkIsListed(TestNetworkName);
+ auto inspect = InspectNetwork(TestNetworkName);
+ VERIFY_ARE_EQUAL("bridge", inspect.Driver);
+ VERIFY_ARE_EQUAL("test", inspect.Labels["env"]);
+ VERIFY_ARE_EQUAL("wslc", inspect.Labels["app"]);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_InvalidDriver_Fail)
+ {
+ auto result = RunWslc(std::format(L"network create --driver invalid_driver {}", TestNetworkName));
+ result.Verify(
+ {.Stdout = L"",
+ .Stderr = std::format(L"Unsupported network driver: 'invalid_driver'\r\nError code: E_INVALIDARG\r\n"),
+ .ExitCode = 1});
+
+ VerifyNetworkIsNotListed(TestNetworkName);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Create_Duplicate_Fail)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify(
+ {.Stdout = L"",
+ .Stderr = std::format(L"Network '{}' already exists.\r\nError code: ERROR_ALREADY_EXISTS\r\n", TestNetworkName),
+ .ExitCode = 1});
+ }
+
+private:
+ const std::wstring TestNetworkName = L"wslc-e2e-network-create";
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return std::format(L"{}\r\n\r\n", Localization::WSLCCLI_NetworkCreateLongDesc());
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc network create [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" network-name Network name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" -d,--driver Specify volume driver name (default guest)\r\n" //
+ << L" -o,--opt Set driver specific options\r\n" //
+ << L" --label Volume metadata setting\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkInspectTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkInspectTests.cpp
new file mode 100644
index 000000000..0a9212134
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkInspectTests.cpp
@@ -0,0 +1,160 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2ENetworkInspectTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+
+class WSLCE2ENetworkInspectTests
+{
+ WSLC_TEST_CLASS(WSLCE2ENetworkInspectTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName1);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName1);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_HelpCommand)
+ {
+ auto result = RunWslc(L"network inspect --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_MissingNetworkName)
+ {
+ auto result = RunWslc(L"network inspect");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'network-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_Success)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName1, result.GetStdoutOneLine());
+
+ result = RunWslc(std::format(L"network inspect {}", TestNetworkName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ auto inspect = inspectData[0];
+
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestNetworkName1), inspect.Name);
+ VERIFY_ARE_EQUAL("bridge", inspect.Driver);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_InspectMultiple_Success)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName1, result.GetStdoutOneLine());
+ result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName2, result.GetStdoutOneLine());
+
+ result = RunWslc(std::format(L"network inspect {} {}", TestNetworkName1, TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(2u, inspectData.size());
+
+ auto inspect1 = inspectData[0];
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestNetworkName1), inspect1.Name);
+ VERIFY_ARE_EQUAL("bridge", inspect1.Driver);
+
+ auto inspect2 = inspectData[1];
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestNetworkName2), inspect2.Name);
+ VERIFY_ARE_EQUAL("bridge", inspect2.Driver);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_NotFound)
+ {
+ auto result = RunWslc(std::format(L"network inspect {}", TestNetworkName1));
+ result.Verify({.Stdout = L"[]\r\n", .Stderr = std::format(L"Network not found: '{}'\r\n", TestNetworkName1), .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Inspect_MixedFoundNotFound)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName1));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VERIFY_ARE_EQUAL(TestNetworkName1, result.GetStdoutOneLine());
+
+ result = RunWslc(std::format(L"network inspect {} {}", TestNetworkName1, TestNetworkName2));
+ result.Verify({.Stderr = std::format(L"Network not found: '{}'\r\n", TestNetworkName2), .ExitCode = 1});
+
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ auto inspect = inspectData[0];
+ VERIFY_ARE_EQUAL(WideToMultiByte(TestNetworkName1), inspect.Name);
+ }
+
+private:
+ const std::wstring TestNetworkName1 = L"wslc-e2e-network-inspect-1";
+ const std::wstring TestNetworkName2 = L"wslc-e2e-network-inspect-2";
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return std::format(L"{}\r\n\r\n", Localization::WSLCCLI_NetworkInspectLongDesc());
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc network inspect [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" network-name Network name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
new file mode 100644
index 000000000..937d508aa
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
@@ -0,0 +1,134 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2ENetworkListTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+using namespace wsl::shared::string;
+
+class WSLCE2ENetworkListTests
+{
+ WSLC_TEST_CLASS(WSLCE2ENetworkListTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_List_HelpCommand)
+ {
+ auto result = RunWslc(L"network list --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_List_InvalidFormatOption)
+ {
+ auto result = RunWslc(L"network list --format invalid");
+ result.Verify({.Stderr = L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table.\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_List_QuietOption_OutputsNamesOnly)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(L"network list --quiet");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto lines = result.GetStdoutLines();
+ VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestNetworkName));
+ VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestNetworkName2));
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_List_JsonFormat)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(L"network list --format json");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto networks = FromJson>(result.Stdout.value().c_str());
+ VERIFY_IS_TRUE(networks.size() >= 2U);
+
+ std::vector names;
+ names.reserve(networks.size());
+ for (const auto& network : networks)
+ {
+ names.push_back(network.Name);
+ }
+
+ VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestNetworkName)));
+ VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestNetworkName2)));
+ }
+
+private:
+ const std::wstring TestNetworkName = L"wslc-e2e-network-list";
+ const std::wstring TestNetworkName2 = L"wslc-e2e-network-list-2";
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommandAliases() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return std::format(L"{}\r\n\r\n", Localization::WSLCCLI_NetworkListLongDesc());
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc network list []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommandAliases() const
+ {
+ return L"The following command aliases are available: ls\r\n\r\n";
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --format Output formatting (json or table) (Default: table)\r\n" //
+ << L" -q,--quiet Outputs the network names only\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkRemoveTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkRemoveTests.cpp
new file mode 100644
index 000000000..8acd2df5b
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkRemoveTests.cpp
@@ -0,0 +1,152 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2ENetworkRemoveTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2ENetworkRemoveTests
+{
+ WSLC_TEST_CLASS(WSLCE2ENetworkRemoveTests)
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureNetworkDoesNotExist(TestNetworkName);
+ EnsureNetworkDoesNotExist(TestNetworkName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_HelpCommand)
+ {
+ auto result = RunWslc(L"network remove --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_MissingNetworkName)
+ {
+ auto result = RunWslc(L"network remove");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'network-name'\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_Valid)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyNetworkIsListed(TestNetworkName);
+
+ result = RunWslc(std::format(L"network remove {}", TestNetworkName));
+ result.Verify({.Stdout = std::format(L"{}\r\n", TestNetworkName), .Stderr = L"", .ExitCode = 0});
+
+ VerifyNetworkIsNotListed(TestNetworkName);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_Multiple_Valid)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyNetworkIsListed(TestNetworkName);
+ VerifyNetworkIsListed(TestNetworkName2);
+
+ result = RunWslc(std::format(L"network remove {} {}", TestNetworkName, TestNetworkName2));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyNetworkIsNotListed(TestNetworkName);
+ VerifyNetworkIsNotListed(TestNetworkName2);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_NotFound)
+ {
+ auto result = RunWslc(std::format(L"network remove {}", TestNetworkName));
+ result.Verify({.Stdout = L"", .Stderr = std::format(L"Network not found: '{}'\r\n", TestNetworkName), .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_Remove_MixedFoundNotFound)
+ {
+ auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VerifyNetworkIsListed(TestNetworkName);
+
+ result = RunWslc(std::format(L"network remove {} {}", TestNetworkName, TestNetworkName2));
+ result.Verify(
+ {.Stdout = std::format(L"{}\r\n", TestNetworkName),
+ .Stderr = std::format(L"Network not found: '{}'\r\n", TestNetworkName2),
+ .ExitCode = 1});
+ VerifyNetworkIsNotListed(TestNetworkName);
+ }
+
+private:
+ const std::wstring TestNetworkName = L"wslc-e2e-network-remove";
+ const std::wstring TestNetworkName2 = L"wslc-e2e-network-remove-2";
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommandAliases() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_NetworkRemoveLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc network remove [] \r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommandAliases() const
+ {
+ return L"The following command aliases are available: delete rm\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" //
+ << L" network-name Network name\r\n" //
+ << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkTests.cpp
new file mode 100644
index 000000000..c82279c8d
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkTests.cpp
@@ -0,0 +1,101 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2ENetworkTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+#include "Argument.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2ENetworkTests
+{
+ WSLC_TEST_CLASS(WSLCE2ENetworkTests)
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_HelpCommand)
+ {
+ auto result = RunWslc(L"network --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_NoSubcommand_ShowsHelp)
+ {
+ auto result = RunWslc(L"network");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Network_InvalidCommand_DisplaysErrorMessage)
+ {
+ auto result = RunWslc(L"network INVALID_CMD");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Unrecognized command: 'INVALID_CMD'\r\n", .ExitCode = 1});
+ }
+
+private:
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_NetworkCommandLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc network [] []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::vector> entries = {
+ {L"create", Localization::WSLCCLI_NetworkCreateDesc()},
+ {L"remove", Localization::WSLCCLI_NetworkRemoveDesc()},
+ {L"inspect", Localization::WSLCCLI_NetworkInspectDesc()},
+ {L"list", Localization::WSLCCLI_NetworkListDesc()},
+ };
+
+ size_t maxLen = 0;
+ for (const auto& [name, _] : entries)
+ {
+ maxLen = (std::max)(maxLen, name.size());
+ }
+
+ std::wstringstream commands;
+ commands << Localization::WSLCCLI_AvailableSubcommands() << L"\r\n";
+ for (const auto& [name, desc] : entries)
+ {
+ commands << L" " << name << std::wstring(maxLen - name.size() + 2, L' ') << desc << L"\r\n";
+ }
+ commands << L"\r\n" << Localization::WSLCCLI_HelpForDetails() << L" [" << WSLC_CLI_HELP_ARG_STRING << L"]\r\n\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n"
+ << L" -?,--help Shows help about the selected command\r\n"
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
From 2d3d8d1be598173ad88702a99cb86f4cd564ef1c Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 24 Apr 2026 12:42:04 -0700
Subject: [PATCH 2/3] Integrate network inspect
---
.../wslc/arguments/ArgumentValidation.cpp | 6 +-
src/windows/wslc/services/InspectModel.h | 5 +-
src/windows/wslc/tasks/InspectTasks.cpp | 11 +++
test/windows/wslc/e2e/WSLCE2EInspectTests.cpp | 68 ++++++++++++++++---
4 files changed, 78 insertions(+), 12 deletions(-)
diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp
index b96dadc4e..e301189fc 100644
--- a/src/windows/wslc/arguments/ArgumentValidation.cpp
+++ b/src/windows/wslc/arguments/ArgumentValidation.cpp
@@ -180,13 +180,17 @@ InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstri
{
return InspectType::Container;
}
+ else if (IsEqual(input, L"network"))
+ {
+ return InspectType::Network;
+ }
else if (IsEqual(input, L"volume"))
{
return InspectType::Volume;
}
else
{
- constexpr std::wstring_view supportedValues = L"image, container, volume";
+ constexpr std::wstring_view supportedValues = L"image, container, network, volume";
throw ArgumentException(Localization::WSLCCLI_InvalidInspectError(argName, input, supportedValues));
}
}
diff --git a/src/windows/wslc/services/InspectModel.h b/src/windows/wslc/services/InspectModel.h
index e82f38c8b..f24af7db4 100644
--- a/src/windows/wslc/services/InspectModel.h
+++ b/src/windows/wslc/services/InspectModel.h
@@ -18,8 +18,9 @@ typedef enum _InspectType
{
Container = 1,
Image = 2,
- Volume = 4,
+ Network = 4,
+ Volume = 8,
- All = Container | Image | Volume,
+ All = Container | Image | Network | Volume,
} InspectType;
} // namespace wsl::windows::wslc::models
diff --git a/src/windows/wslc/tasks/InspectTasks.cpp b/src/windows/wslc/tasks/InspectTasks.cpp
index d9fda6928..80d726217 100644
--- a/src/windows/wslc/tasks/InspectTasks.cpp
+++ b/src/windows/wslc/tasks/InspectTasks.cpp
@@ -16,6 +16,7 @@ Module Name:
#include "InspectTasks.h"
#include "InspectModel.h"
#include "ImageService.h"
+#include "NetworkService.h"
#include "VolumeService.h"
#include "ContainerService.h"
@@ -57,6 +58,11 @@ static bool TryInspectContainer(wsl::windows::wslc::models::Session& session, co
return TryInspect([&]() { result = services::ContainerService::Inspect(session, containerId); }, WSLC_E_CONTAINER_NOT_FOUND);
}
+static bool TryInspectNetwork(wsl::windows::wslc::models::Session& session, const std::string& networkName, std::optional& result)
+{
+ return TryInspect([&]() { result = services::NetworkService::Inspect(session, networkName); }, WSLC_E_NETWORK_NOT_FOUND);
+}
+
static bool TryInspectVolume(wsl::windows::wslc::models::Session& session, const std::string& volumeId, std::optional& result)
{
return TryInspect([&]() { result = services::VolumeService::Inspect(session, volumeId); }, WSLC_E_VOLUME_NOT_FOUND);
@@ -80,6 +86,7 @@ void Inspect(CLIExecutionContext& context)
auto id = WideToMultiByte(objectId);
std::optional container;
std::optional image;
+ std::optional network;
std::optional volume;
if (WI_IsFlagSet(type, InspectType::Container) && TryInspectContainer(session, id, container))
@@ -90,6 +97,10 @@ void Inspect(CLIExecutionContext& context)
{
array.push_back(std::move(*image));
}
+ else if (WI_IsFlagSet(type, InspectType::Network) && TryInspectNetwork(session, id, network))
+ {
+ array.push_back(std::move(*network));
+ }
else if (WI_IsFlagSet(type, InspectType::Volume) && TryInspectVolume(session, id, volume))
{
array.push_back(std::move(*volume));
diff --git a/test/windows/wslc/e2e/WSLCE2EInspectTests.cpp b/test/windows/wslc/e2e/WSLCE2EInspectTests.cpp
index da9f1a1c5..bcb117f23 100644
--- a/test/windows/wslc/e2e/WSLCE2EInspectTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EInspectTests.cpp
@@ -35,6 +35,7 @@ class WSLCE2EInspectTests
{
EnsureContainerDoesNotExist(WslcContainerName);
EnsureContainerDoesNotExist(DebianImage.Name);
+ EnsureNetworkDoesNotExist(WslcNetworkName);
EnsureImageIsDeleted(DebianImage);
return true;
}
@@ -43,6 +44,7 @@ class WSLCE2EInspectTests
{
EnsureContainerDoesNotExist(WslcContainerName);
EnsureContainerDoesNotExist(DebianImage.Name);
+ EnsureNetworkDoesNotExist(WslcNetworkName);
return true;
}
@@ -165,14 +167,14 @@ class WSLCE2EInspectTests
}
}
- WSLC_TEST_METHOD(WSLCE2E_Inspect_Image_PriorityOverVolume)
+ WSLC_TEST_METHOD(WSLCE2E_Inspect_Image_PriorityOverNetwork)
{
- // When an image and volume share the same name and no --type is specified,
- // the image should be returned (image is checked before volume in InspectTasks).
- EnsureVolumeDoesNotExist(DebianImage.Name);
- auto createResult = RunWslc(std::format(L"volume create {}", DebianImage.Name));
+ // When an image and network share the same name and no --type is specified,
+ // the image should be returned (image is checked before network in InspectTasks).
+ EnsureNetworkDoesNotExist(DebianImage.Name);
+ auto createResult = RunWslc(std::format(L"network create --driver bridge {}", DebianImage.Name));
createResult.Verify({.Stderr = L"", .ExitCode = 0});
- auto deleteVolume = wil::scope_exit([&]() { EnsureVolumeDoesNotExist(DebianImage.Name); });
+ auto deleteNetwork = wil::scope_exit([&]() { EnsureNetworkDoesNotExist(DebianImage.Name); });
// No type specified
{
@@ -199,14 +201,61 @@ class WSLCE2EInspectTests
VERIFY_ARE_EQUAL(DebianImage.NameAndTag(), wsl::shared::string::MultiByteToWide(inspectData[0].RepoTags.value()[0]));
}
+ // With --type network
+ {
+ auto result = RunWslc(std::format(L"inspect --type network {}", DebianImage.Name));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ VERIFY_ARE_EQUAL(DebianImage.Name, wsl::shared::string::MultiByteToWide(inspectData[0].Name));
+ }
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Inspect_Network_PriorityOverVolume)
+ {
+ // When a network and volume share the same name and no --type is specified,
+ // the network should be returned (network is checked before volume in InspectTasks).
+ EnsureNetworkDoesNotExist(WslcNetworkName);
+ EnsureVolumeDoesNotExist(WslcNetworkName);
+
+ auto createNetworkResult = RunWslc(std::format(L"network create --driver bridge {}", WslcNetworkName));
+ createNetworkResult.Verify({.Stderr = L"", .ExitCode = 0});
+ auto deleteNetwork = wil::scope_exit([&]() { EnsureNetworkDoesNotExist(WslcNetworkName); });
+
+ auto createVolumeResult = RunWslc(std::format(L"volume create {}", WslcNetworkName));
+ createVolumeResult.Verify({.Stderr = L"", .ExitCode = 0});
+ auto deleteVolume = wil::scope_exit([&]() { EnsureVolumeDoesNotExist(WslcNetworkName); });
+
+ // No type specified — network takes priority over volume
+ {
+ auto result = RunWslc(std::format(L"inspect {}", WslcNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ VERIFY_ARE_EQUAL(WslcNetworkName, wsl::shared::string::MultiByteToWide(inspectData[0].Name));
+ }
+
+ // With --type network
+ {
+ auto result = RunWslc(std::format(L"inspect --type network {}", WslcNetworkName));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ auto inspectData =
+ wsl::shared::FromJson>(result.Stdout.value().c_str());
+ VERIFY_ARE_EQUAL(1u, inspectData.size());
+ VERIFY_ARE_EQUAL(WslcNetworkName, wsl::shared::string::MultiByteToWide(inspectData[0].Name));
+ }
+
// With --type volume
{
- auto result = RunWslc(std::format(L"inspect --type volume {}", DebianImage.Name));
+ auto result = RunWslc(std::format(L"inspect --type volume {}", WslcNetworkName));
result.Verify({.Stderr = L"", .ExitCode = 0});
auto inspectData =
wsl::shared::FromJson>(result.Stdout.value().c_str());
VERIFY_ARE_EQUAL(1u, inspectData.size());
- VERIFY_ARE_EQUAL(DebianImage.Name, wsl::shared::string::MultiByteToWide(inspectData[0].Name));
+ VERIFY_ARE_EQUAL(WslcNetworkName, wsl::shared::string::MultiByteToWide(inspectData[0].Name));
}
}
@@ -239,7 +288,7 @@ class WSLCE2EInspectTests
WSLC_TEST_METHOD(WSLCE2E_Inspect_InvalidTypeValue)
{
auto result = RunWslc(std::format(L"inspect --type invalid {}", DebianImage.NameAndTag()));
- result.Verify({.Stderr = L"Invalid type value: invalid is not a recognized inspect type. Supported inspect types are: image, container, volume.\r\n", .ExitCode = 1});
+ result.Verify({.Stderr = L"Invalid type value: invalid is not a recognized inspect type. Supported inspect types are: image, container, network, volume.\r\n", .ExitCode = 1});
}
WSLC_TEST_METHOD(WSLCE2E_Inspect_SkipsInvalidFormatError)
@@ -261,6 +310,7 @@ class WSLCE2EInspectTests
const TestImage& DebianImage = DebianTestImage();
const TestImage& InvalidImage = InvalidTestImage();
const std::wstring WslcVolumeName = L"wslc-inspect-test-volume";
+ const std::wstring WslcNetworkName = L"wslc-inspect-test-network";
std::wstring GetHelpMessage() const
{
std::wstringstream output;
From e7efc33896b60cf3062f6c4cc0ae1021de77f1b8 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 24 Apr 2026 15:42:08 -0700
Subject: [PATCH 3/3] Clang format
---
.../wslc/e2e/WSLCE2ENetworkCreateTests.cpp | 16 +++++++---------
.../windows/wslc/e2e/WSLCE2ENetworkListTests.cpp | 8 ++++----
2 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
index f367aef3e..c7041cb1a 100644
--- a/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkCreateTests.cpp
@@ -75,9 +75,7 @@ class WSLCE2ENetworkCreateTests
{
auto result = RunWslc(std::format(L"network create --driver invalid_driver {}", TestNetworkName));
result.Verify(
- {.Stdout = L"",
- .Stderr = std::format(L"Unsupported network driver: 'invalid_driver'\r\nError code: E_INVALIDARG\r\n"),
- .ExitCode = 1});
+ {.Stdout = L"", .Stderr = std::format(L"Unsupported network driver: 'invalid_driver'\r\nError code: E_INVALIDARG\r\n"), .ExitCode = 1});
VerifyNetworkIsNotListed(TestNetworkName);
}
@@ -130,12 +128,12 @@ class WSLCE2ENetworkCreateTests
std::wstring GetAvailableOptions() const
{
std::wstringstream options;
- options << L"The following options are available:\r\n" //
- << L" -d,--driver Specify volume driver name (default guest)\r\n" //
- << L" -o,--opt Set driver specific options\r\n" //
- << L" --label Volume metadata setting\r\n" //
- << L" --session Specify the session to use\r\n" //
- << L" -?,--help Shows help about the selected command\r\n" //
+ options << L"The following options are available:\r\n" //
+ << L" -d,--driver Specify volume driver name (default guest)\r\n" //
+ << L" -o,--opt Set driver specific options\r\n" //
+ << L" --label Volume metadata setting\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
<< L"\r\n";
return options.str();
}
diff --git a/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp b/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
index 937d508aa..91548231e 100644
--- a/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2ENetworkListTests.cpp
@@ -122,11 +122,11 @@ class WSLCE2ENetworkListTests
std::wstring GetAvailableOptions() const
{
std::wstringstream options;
- options << L"The following options are available:\r\n" //
+ options << L"The following options are available:\r\n" //
<< L" --format Output formatting (json or table) (Default: table)\r\n" //
- << L" -q,--quiet Outputs the network names only\r\n" //
- << L" --session Specify the session to use\r\n" //
- << L" -?,--help Shows help about the selected command\r\n" //
+ << L" -q,--quiet Outputs the network names only\r\n" //
+ << L" --session Specify the session to use\r\n" //
+ << L" -?,--help Shows help about the selected command\r\n" //
<< L"\r\n";
return options.str();
}