master
cpp 263 lines 7.52 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 VolumeTasks.cpp
8
9 Abstract:
10
11 Implementation of volume command related execution logic.
12
13 --*/
14 #include "Argument.h"
15 #include "ArgumentConvertedTypes.h"
16 #include "CLIExecutionContext.h"
17 #include "VolumeModel.h"
18 #include "VolumeService.h"
19 #include "VolumeTasks.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::wslutil;
27 using namespace wsl::windows::wslc::execution;
28 using namespace wsl::windows::wslc::models;
29 using namespace wsl::windows::wslc::services;
30 using wsl::windows::common::string::FormatHumanReadableSize;
31
32 namespace wsl::windows::wslc::task {
33
34 namespace {
35
36 // Reported for the fields that only carry a value when volume usage data or swarm cluster
37 // information is available, neither of which applies here.
38 constexpr std::string_view c_volumeNotAvailable = "N/A";
39
40 // Converts session volume entries into the all-string shape used for "volume list --format json".
41 VolumeOutputInformation ToVolumeOutput(const wslc_schema::VolumeListEntry& volume)
42 {
43 VolumeOutputInformation entry;
44 entry.Availability = c_volumeNotAvailable;
45 entry.Driver = volume.Driver;
46 entry.Group = c_volumeNotAvailable;
47 entry.Links = c_volumeNotAvailable;
48 entry.Mountpoint = volume.Mountpoint;
49 entry.Name = volume.Name;
50 entry.Scope = volume.Scope;
51 entry.Size = c_volumeNotAvailable;
52 entry.Status = c_volumeNotAvailable;
53
54 for (const auto& [key, value] : volume.Labels)
55 {
56 if (!entry.Labels.empty())
57 {
58 entry.Labels += ",";
59 }
60
61 entry.Labels += std::format("{}={}", key, value);
62 }
63
64 return entry;
65 }
66
67 } // namespace
68
69 static bool TryInspectVolume(Terminal& terminal, Session& session, const std::string& volumeName, std::optional<wslc_schema::InspectVolume>& inspectData)
70 {
71 try
72 {
73 inspectData = VolumeService::Inspect(session, volumeName);
74 return true;
75 }
76 catch (const wil::ResultException& ex)
77 {
78 if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND)
79 {
80 terminal.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str()));
81 return false;
82 }
83
84 throw;
85 }
86 }
87
88 static bool TryDeleteVolume(Terminal& terminal, Session& session, const std::string& volumeName, bool force)
89 {
90 try
91 {
92 VolumeService::Delete(session, volumeName);
93 return true;
94 }
95 catch (const wil::ResultException& ex)
96 {
97 if (ex.GetErrorCode() == WSLC_E_VOLUME_NOT_FOUND)
98 {
99 if (!force)
100 {
101 terminal.Error(L"{}\n", Localization::MessageWslcVolumeNotFound(volumeName.c_str()));
102 }
103
104 return false;
105 }
106
107 throw;
108 }
109 }
110
111 void CreateVolume(CLIExecutionContext& context)
112 {
113 WI_ASSERT(context.Data.Contains(Data::Session));
114
115 models::CreateVolumeOptions options{};
116 if (context.Args.Contains(ArgType::VolumeName))
117 {
118 options.Name = WideToMultiByte(context.Args.GetValue<ArgType::VolumeName>());
119 }
120
121 for (const auto& option : context.Args.GetAllValues<ArgType::Options>())
122 {
123 options.DriverOpts.push_back(option);
124 }
125
126 for (const auto& label : context.Args.GetAllValues<ArgType::Label>())
127 {
128 options.Labels.push_back(label);
129 }
130
131 if (context.Args.Contains(ArgType::Driver))
132 {
133 options.Driver = WideToMultiByte(context.Args.GetValue<ArgType::Driver>());
134 }
135
136 auto result = VolumeService::Create(context.Data.Get<Data::Session>(), options);
137 context.Terminal.Output(L"{}\n", MultiByteToWide(result.Name));
138 }
139
140 void DeleteVolumes(CLIExecutionContext& context)
141 {
142 WI_ASSERT(context.Data.Contains(Data::Session));
143 auto& session = context.Data.Get<Data::Session>();
144 auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>();
145 const bool force = context.Args.GetValue<ArgType::Force>();
146 for (const auto& name : volumeNames)
147 {
148 if (TryDeleteVolume(context.Terminal, session, WideToMultiByte(name), force))
149 {
150 context.Terminal.Output(L"{}\n", name);
151 }
152 else if (!force)
153 {
154 context.ExitCode = 1;
155 }
156 }
157 }
158
159 void GetVolumes(CLIExecutionContext& context)
160 {
161 WI_ASSERT(context.Data.Contains(Data::Session));
162 auto& session = context.Data.Get<Data::Session>();
163 auto filters = context.Args.GetAllValues<ArgType::Filter>();
164 context.Data.Add<Data::Volumes>(VolumeService::List(session, filters));
165 }
166
167 void InspectVolumes(CLIExecutionContext& context)
168 {
169 WI_ASSERT(context.Data.Contains(Data::Session));
170 auto& session = context.Data.Get<Data::Session>();
171 auto volumeNames = context.Args.GetAllValues<ArgType::VolumeName>();
172 std::vector<wsl::windows::common::wslc_schema::InspectVolume> result;
173 for (const auto& name : volumeNames)
174 {
175 std::optional<wslc_schema::InspectVolume> inspectData;
176 if (TryInspectVolume(context.Terminal, session, WideToMultiByte(name), inspectData))
177 {
178 result.push_back(*inspectData);
179 }
180 else
181 {
182 context.ExitCode = 1;
183 }
184 }
185
186 auto json = ToJson(result, context.Args.GetValue<ArgType::InspectFormat>(c_jsonPrettyPrintIndent));
187 context.Terminal.Output(L"{}\n", MultiByteToWide(json));
188 }
189
190 void ListVolumes(CLIExecutionContext& context)
191 {
192 WI_ASSERT(context.Data.Contains(Data::Volumes));
193 auto& volumes = context.Data.Get<Data::Volumes>();
194
195 if (context.Args.GetValue<ArgType::Quiet>())
196 {
197 for (const auto& volume : volumes)
198 {
199 context.Terminal.Output(L"{}\n", MultiByteToWide(volume.Name));
200 }
201
202 return;
203 }
204
205 const auto format = context.Args.GetValue<ArgType::Format>(FormatType::Table);
206
207 switch (format)
208 {
209 case FormatType::Json:
210 {
211 for (const auto& volume : volumes)
212 {
213 context.Terminal.Output(L"{}\n", ToJsonW(ToVolumeOutput(volume), c_jsonCompactIndent));
214 }
215
216 break;
217 }
218 case FormatType::Table:
219 {
220 auto table = wsl::windows::wslc::TableOutput<2>(context.Terminal, {L"DRIVER", L"VOLUME NAME"});
221 for (const auto& volume : volumes)
222 {
223 table.WriteRow({
224 MultiByteToWide(volume.Driver),
225 MultiByteToWide(volume.Name),
226 });
227 }
228
229 table.Complete();
230 break;
231 }
232 default:
233 THROW_HR(E_UNEXPECTED);
234 }
235 }
236
237 void PruneVolumes(CLIExecutionContext& context)
238 {
239 WI_ASSERT(context.Data.Contains(Data::Session));
240 auto& session = context.Data.Get<Data::Session>();
241
242 const bool all = context.Args.GetValue<ArgType::All>();
243
244 // Filter values are parsed and cached during argument validation.
245 auto filters = context.Args.GetAllValues<ArgType::Filter>();
246
247 auto result = VolumeService::Prune(context.Terminal, session, all, filters);
248
249 if (!result.PrunedVolumes.empty())
250 {
251 context.Terminal.Output(L"{}\n", Localization::WSLCCLI_VolumePruneDeletedHeader());
252 for (const auto& volumeName : result.PrunedVolumes)
253 {
254 context.Terminal.Output(L"{}\n", MultiByteToWide(volumeName));
255 }
256
257 context.Terminal.Output(L"\n");
258 }
259
260 context.Terminal.Output(
261 L"{}\n", Localization::WSLCCLI_VolumePruneSpaceReclaimed(FormatHumanReadableSize(result.SpaceReclaimed, c_reclaimedSpacePrecision)));
262 }
263 } // namespace wsl::windows::wslc::task