master
cpp 349 lines 10.9 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 NetworkTasks.cpp
8
9 Abstract:
10
11 Implementation of network command related execution logic.
12
13 --*/
14 #include "Argument.h"
15 #include "ArgumentConvertedTypes.h"
16 #include "CLIExecutionContext.h"
17 #include "NetworkModel.h"
18 #include "NetworkService.h"
19 #include "NetworkTasks.h"
20 #include "TableOutput.h"
21 #include <wslc_schema.h>
22
23 using namespace wsl::shared;
24 using namespace wsl::windows::common;
25 using namespace wsl::windows::common::string;
26 using namespace wsl::windows::common::timestamp;
27 using namespace wsl::windows::common::wslutil;
28 using namespace wsl::windows::wslc::execution;
29 using namespace wsl::windows::wslc::models;
30 using namespace wsl::windows::wslc::services;
31
32 namespace wsl::windows::wslc::task {
33
34 namespace {
35
36 // Shared by the table and json output so the two cannot drift. The id is truncated unless --no-trunc is passed.
37 NetworkOutputInformation ToNetworkOutput(const wslc_schema::NetworkListEntry& network, bool truncate)
38 {
39 NetworkOutputInformation entry;
40 entry.CreatedAt = Rfc3339ToUtcDisplayTime(network.Created);
41 entry.Driver = network.Driver;
42 entry.ID = TruncateId(network.Id, truncate);
43 entry.IPv4 = network.EnableIPv4 ? "true" : "false";
44 entry.IPv6 = network.EnableIPv6 ? "true" : "false";
45 entry.Internal = network.Internal ? "true" : "false";
46 entry.Name = network.Name;
47 entry.Scope = network.Scope;
48
49 for (const auto& [key, value] : network.Labels)
50 {
51 if (!entry.Labels.empty())
52 {
53 entry.Labels += ",";
54 }
55
56 entry.Labels += std::format("{}={}", key, value);
57 }
58
59 return entry;
60 }
61
62 } // namespace
63
64 static bool TryInspectNetwork(Terminal& terminal, Session& session, const std::string& networkName, std::optional<wslc_schema::Network>& inspectData)
65 {
66 try
67 {
68 inspectData = NetworkService::Inspect(session, networkName);
69 return true;
70 }
71 catch (const wil::ResultException& ex)
72 {
73 if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND)
74 {
75 terminal.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str()));
76 return false;
77 }
78
79 throw;
80 }
81 }
82
83 static bool TryDeleteNetwork(Terminal& terminal, Session& session, const std::string& networkName, bool force)
84 {
85 try
86 {
87 NetworkService::Delete(session, networkName);
88 return true;
89 }
90 catch (const wil::ResultException& ex)
91 {
92 if (ex.GetErrorCode() == WSLC_E_NETWORK_NOT_FOUND)
93 {
94 if (!force)
95 {
96 terminal.Error(L"{}\n", Localization::MessageWslcNetworkNotFound(networkName.c_str()));
97 }
98
99 return false;
100 }
101
102 throw;
103 }
104 }
105
106 void CreateNetwork(CLIExecutionContext& context)
107 {
108 WI_ASSERT(context.Data.Contains(Data::Session));
109 WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
110
111 models::CreateNetworkOptions options{};
112 options.Name = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
113
114 for (const auto& option : context.Args.GetAllValues<ArgType::Options>())
115 {
116 options.DriverOpts.push_back(option);
117 }
118
119 for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
120 {
121 options.Labels.push_back(label);
122 }
123
124 if (context.Args.Contains(ArgType::Driver))
125 {
126 options.Driver = WideToMultiByte(context.Args.GetValue<ArgType::Driver>());
127 }
128
129 options.Internal = context.Args.GetValue<ArgType::Internal>();
130
131 if (context.Args.Contains(ArgType::Subnet))
132 {
133 options.Subnet = WideToMultiByte(context.Args.GetValue<ArgType::Subnet>());
134 }
135
136 if (context.Args.Contains(ArgType::Gateway))
137 {
138 options.Gateway = WideToMultiByte(context.Args.GetValue<ArgType::Gateway>());
139 }
140
141 if (context.Args.Contains(ArgType::IpRange))
142 {
143 options.IpRange = WideToMultiByte(context.Args.GetValue<ArgType::IpRange>());
144 }
145
146 NetworkService::Create(context.Terminal, context.Data.Get<Data::Session>(), options);
147 context.Terminal.Output(L"{}\n", MultiByteToWide(options.Name));
148 }
149
150 void DeleteNetworks(CLIExecutionContext& context)
151 {
152 WI_ASSERT(context.Data.Contains(Data::Session));
153 auto& session = context.Data.Get<Data::Session>();
154 auto networkNames = context.Args.GetAllValues<ArgType::NetworkName>();
155 const bool force = context.Args.GetValue<ArgType::Force>();
156 for (const auto& name : networkNames)
157 {
158 if (TryDeleteNetwork(context.Terminal, session, WideToMultiByte(name), force))
159 {
160 context.Terminal.Output(L"{}\n", name);
161 }
162 else if (!force)
163 {
164 context.ExitCode = 1;
165 }
166 }
167 }
168
169 void GetNetworks(CLIExecutionContext& context)
170 {
171 WI_ASSERT(context.Data.Contains(Data::Session));
172 auto& session = context.Data.Get<Data::Session>();
173
174 auto filters = context.Args.GetAllValues<ArgType::Filter>();
175 context.Data.Add<Data::Networks>(NetworkService::List(session, filters));
176 }
177
178 void InspectNetworks(CLIExecutionContext& context)
179 {
180 WI_ASSERT(context.Data.Contains(Data::Session));
181 auto& session = context.Data.Get<Data::Session>();
182 auto networkNames = context.Args.GetAllValues<ArgType::NetworkName>();
183 std::vector<wsl::windows::common::wslc_schema::Network> result;
184 for (const auto& name : networkNames)
185 {
186 std::optional<wslc_schema::Network> inspectData;
187 if (TryInspectNetwork(context.Terminal, session, WideToMultiByte(name), inspectData))
188 {
189 result.push_back(*inspectData);
190 }
191 else
192 {
193 context.ExitCode = 1;
194 }
195 }
196
197 auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
198 context.Terminal.Output(L"{}\n", MultiByteToWide(json));
199 }
200
201 void ListNetworks(CLIExecutionContext& context)
202 {
203 WI_ASSERT(context.Data.Contains(Data::Networks));
204 auto& networks = context.Data.Get<Data::Networks>();
205
206 // Networks are reported in name order regardless of how the daemon returns them.
207 std::ranges::sort(networks, {}, &wslc_schema::NetworkListEntry::Name);
208
209 const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
210 const bool quiet = context.Args.GetValue<ArgType::Quiet>();
211 const bool trunc = !context.Args.GetValue<ArgType::NoTrunc>();
212 if (format == FormatType::Table && quiet)
213 {
214 for (const auto& network : networks)
215 {
216 context.Terminal.Output(L"{}\n", MultiByteToWide(TruncateId(network.Id, trunc)));
217 }
218
219 return;
220 }
221
222 switch (format)
223 {
224 case FormatType::Json:
225 {
226 for (const auto& network : networks)
227 {
228 context.Terminal.Output(L"{}\n", ToJsonW(ToNetworkOutput(network, trunc), c_jsonCompactIndent));
229 }
230
231 break;
232 }
233 case FormatType::Table:
234 {
235 // Every column has a minimum total width of ten characters, including the padding that follows it.
236 constexpr size_t c_minimumColumnWidth = 7;
237 auto table = wsl::windows::wslc::TableOutput<4>(
238 context.Terminal,
239 {L"NETWORK ID", L"NAME", L"DRIVER", L"SCOPE"},
240 {ColumnWidthConfig{.MinWidth = c_minimumColumnWidth},
241 ColumnWidthConfig{.MinWidth = c_minimumColumnWidth},
242 ColumnWidthConfig{.MinWidth = c_minimumColumnWidth},
243 ColumnWidthConfig{.MinWidth = c_minimumColumnWidth}});
244 for (const auto& network : networks)
245 {
246 const auto entry = ToNetworkOutput(network, trunc);
247 table.WriteRow({
248 MultiByteToWide(entry.ID),
249 MultiByteToWide(entry.Name),
250 MultiByteToWide(entry.Driver),
251 MultiByteToWide(entry.Scope),
252 });
253 }
254
255 table.Complete();
256 break;
257 }
258 default:
259 THROW_HR(E_UNEXPECTED);
260 }
261 }
262
263 void PruneNetworks(CLIExecutionContext& context)
264 {
265 WI_ASSERT(context.Data.Contains(Data::Session));
266 auto& session = context.Data.Get<Data::Session>();
267
268 // Filter values are parsed and cached during argument validation.
269 auto filters = context.Args.GetAllValues<ArgType::Filter>();
270
271 auto result = NetworkService::Prune(session, filters);
272
273 if (result.PrunedNetworks.empty())
274 {
275 return;
276 }
277
278 context.Terminal.Output(L"{}\n", Localization::WSLCCLI_NetworkPruneDeletedHeader());
279 for (const auto& networkName : result.PrunedNetworks)
280 {
281 context.Terminal.Output(L"{}\n", MultiByteToWide(networkName));
282 }
283
284 context.Terminal.Output(L"\n");
285 }
286
287 void ConnectNetwork(CLIExecutionContext& context)
288 {
289 WI_ASSERT(context.Data.Contains(Data::Session));
290 WI_ASSERT(context.Data.Contains(Data::NetworkEndpointOptions));
291 WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
292 WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
293
294 const auto& endpoint = context.Data.Get<Data::NetworkEndpointOptions>();
295 models::ConnectNetworkOptions options{};
296 options.NetworkName = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
297 options.ContainerId = WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>());
298 options.Aliases = endpoint.Aliases;
299 options.IpAddress = endpoint.IpAddress;
300 options.Links = endpoint.Links;
301 options.LinkLocalIps = endpoint.LinkLocalIps;
302 options.DriverOpts = endpoint.DriverOpts;
303
304 NetworkService::Connect(context.Data.Get<Data::Session>(), options);
305 }
306
307 void DisconnectNetwork(CLIExecutionContext& context)
308 {
309 WI_ASSERT(context.Data.Contains(Data::Session));
310 WI_ASSERT(context.Args.Contains(ArgType::NetworkName));
311 WI_ASSERT(context.Args.Contains(ArgType::ContainerId));
312
313 const auto networkName = WideToMultiByte(context.Args.GetValue<ArgType::NetworkName>());
314 const auto containerId = WideToMultiByte(context.Args.GetValue<ArgType::ContainerId>());
315 NetworkService::Disconnect(context.Data.Get<Data::Session>(), networkName, containerId);
316 }
317
318 void SetNetworkEndpointOptionsFromArgs(CLIExecutionContext& context)
319 {
320 models::NetworkEndpointOptions options{};
321
322 for (const auto& alias : context.Args.GetAllValues<ArgType::NetworkAlias>())
323 {
324 options.Aliases.emplace_back(WideToMultiByte(alias));
325 }
326
327 if (context.Args.Contains(ArgType::IpAddress))
328 {
329 options.IpAddress = WideToMultiByte(context.Args.GetValue<ArgType::IpAddress>());
330 }
331
332 for (const auto& link : context.Args.GetAllValues<ArgType::Link>())
333 {
334 options.Links.emplace_back(WideToMultiByte(link));
335 }
336
337 for (const auto& linkLocalIp : context.Args.GetAllValues<ArgType::LinkLocalIp>())
338 {
339 options.LinkLocalIps.emplace_back(WideToMultiByte(linkLocalIp));
340 }
341
342 for (const auto& driverOpt : context.Args.GetAllValues<ArgType::DriverOpt>())
343 {
344 options.DriverOpts.emplace_back(WideToMultiByte(driverOpt));
345 }
346
347 context.Data.Add<Data::NetworkEndpointOptions>(std::move(options));
348 }
349 } // namespace wsl::windows::wslc::task