@samitouri / QOSAMI-WSL / commits / fd01acfc

Add --filter support to volume list (#41359)

Add --filter support to volume list (#41359)

beena352 committed Aug 17, 2026 at 15:34 UTC fd01acfccc9e9141c0af1bac9465061cd7986e14
5 files changed +181 -4
src/windows/wslc/commands/VolumeListCommand.cpp
+1
@@ -27,6 +27,7 @@ namespace wsl::windows::wslc {
27 std::vector<Argument> VolumeListCommand::GetArguments() const
28 {
29 return {
30 + Argument::Create(ArgType::Filter, false, Limit::Unlimited),
31 Argument::Create(ArgType::Format),
32 Argument::Create(ArgType::Quiet, false, std::nullopt, Localization::WSLCCLI_VolumeListQuietArgDesc()),
33 };
src/windows/wslc/services/VolumeService.cpp
+10 -2
@@ -60,11 +60,19 @@ void VolumeService::Delete(models::Session& session, const std::string& name)
60 THROW_IF_FAILED(session.Get()->DeleteVolume(name.c_str()));
61 }
62
63 -std::vector<WSLCVolumeInformation> VolumeService::List(models::Session& session)
63 +std::vector<WSLCVolumeInformation> VolumeService::List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters)
64 {
65 + std::vector<WSLCFilter> filterEntries;
66 + filterEntries.reserve(filters.size());
67 + for (const auto& [key, value] : filters)
68 + {
69 + filterEntries.push_back({.Key = key.c_str(), .Value = value.c_str()});
70 + }
71 +
72 wil::unique_cotaskmem_array_ptr<WSLCVolumeInformation> rawVolumes;
73 ULONG count = 0;
67 - THROW_IF_FAILED(session.Get()->ListVolumes(nullptr, 0, &rawVolumes, &count));
74 + THROW_IF_FAILED(session.Get()->ListVolumes(
75 + filterEntries.empty() ? nullptr : filterEntries.data(), static_cast<ULONG>(filterEntries.size()), &rawVolumes, &count));
76
77 std::vector<WSLCVolumeInformation> volumes;
78 volumes.reserve(count);
src/windows/wslc/services/VolumeService.h
+1 -1
@@ -24,7 +24,7 @@ struct VolumeService
24 {
25 static WSLCVolumeInformation Create(models::Session& session, const models::CreateVolumeOptions& createOptions);
26 static void Delete(models::Session& session, const std::string& name);
27 - static std::vector<WSLCVolumeInformation> List(models::Session& session);
27 + static std::vector<WSLCVolumeInformation> List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters = {});
28 static wsl::windows::common::wslc_schema::InspectVolume Inspect(models::Session& session, const std::string& name);
29 static models::PruneVolumesResult Prune(
30 Terminal& terminal, models::Session& session, bool all, const std::vector<std::pair<std::string, std::string>>& filters = {});
src/windows/wslc/tasks/VolumeTasks.cpp
+2 -1
@@ -125,7 +125,8 @@ void GetVolumes(CLIExecutionContext& context)
125 {
126 WI_ASSERT(context.Data.Contains(Data::Session));
127 auto& session = context.Data.Get<Data::Session>();
128 - context.Data.Add<Data::Volumes>(VolumeService::List(session));
128 + auto filters = context.Args.GetAllValues<ArgType::Filter>();
129 + context.Data.Add<Data::Volumes>(VolumeService::List(session, filters));
130 }
131
132 void InspectVolumes(CLIExecutionContext& context)
test/windows/wslc/e2e/WSLCE2EVolumeListTests.cpp
+167
@@ -30,6 +30,10 @@ class WSLCE2EVolumeListTests
30 {
31 EnsureVolumeDoesNotExist(TestVolumeName);
32 EnsureVolumeDoesNotExist(TestVolumeName2);
33 + for (const auto& name : FilterTestVolumeNames)
34 + {
35 + EnsureVolumeDoesNotExist(name);
36 + }
37 return true;
38 }
39
@@ -37,6 +41,10 @@ class WSLCE2EVolumeListTests
41 {
42 EnsureVolumeDoesNotExist(TestVolumeName);
43 EnsureVolumeDoesNotExist(TestVolumeName2);
44 + for (const auto& name : FilterTestVolumeNames)
45 + {
46 + EnsureVolumeDoesNotExist(name);
47 + }
48 return true;
49 }
50
@@ -94,8 +102,167 @@ class WSLCE2EVolumeListTests
102 VERIFY_ARE_NOT_EQUAL(names.end(), std::find(names.begin(), names.end(), WideToMultiByte(TestVolumeName2)));
103 }
104
105 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_MalformedValue)
106 + {
107 + const auto result = RunWslc(L"volume list --filter label");
108 + result.Verify({.Stdout = L"", .ExitCode = 1});
109 + VERIFY_IS_TRUE(result.StderrContainsSubstring(Localization::WSLCCLI_InvalidFilterError(L"label")));
110 + }
111 +
112 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_InvalidKey)
113 + {
114 + // Filter keys are validated by the Docker daemon, which rejects unknown keys.
115 + const auto result = RunWslc(L"volume list --filter color=blue");
116 + VERIFY_ARE_EQUAL(1, result.ExitCode);
117 + VERIFY_IS_TRUE(result.Stderr.has_value());
118 + VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter 'color'"));
119 + }
120 +
121 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_InvalidDanglingValue)
122 + {
123 + // Dangling values are validated by the Docker daemon, which rejects non-boolean values.
124 + const auto result = RunWslc(L"volume list --filter dangling=maybe");
125 + VERIFY_ARE_EQUAL(1, result.ExitCode);
126 + VERIFY_IS_TRUE(result.Stderr.has_value());
127 + VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter 'dangling=[maybe]'"));
128 + }
129 +
130 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_Driver)
131 + {
132 + const std::wstring alpha = L"wslc-flt-vlist-driver-alpha";
133 + const std::wstring beta = L"wslc-flt-vlist-driver-beta";
134 + auto cleanup = wil::scope_exit([&]() {
135 + EnsureVolumeDoesNotExist(alpha);
136 + EnsureVolumeDoesNotExist(beta);
137 + });
138 +
139 + auto result = RunWslc(std::format(L"volume create --driver vhd --opt SizeBytes={} {}", DefaultVolumeSizeBytes, alpha));
140 + result.Verify({.Stderr = L"", .ExitCode = 0});
141 + result = RunWslc(std::format(L"volume create --driver guest {}", beta));
142 + result.Verify({.Stderr = L"", .ExitCode = 0});
143 +
144 + {
145 + const auto names = GetFilteredVolumeNames(L"--filter driver=vhd");
146 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
147 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
148 + }
149 +
150 + {
151 + const auto names = GetFilteredVolumeNames(L"--filter driver=guest");
152 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(alpha)));
153 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
154 + }
155 +
156 + // Docker's own driver names (e.g. "local") never match wslc-managed volumes.
157 + {
158 + const auto names = GetFilteredVolumeNames(L"--filter driver=local");
159 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(alpha)));
160 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
161 + }
162 + }
163 +
164 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_Label)
165 + {
166 + const std::wstring alpha = L"wslc-flt-vlist-label-alpha";
167 + const std::wstring beta = L"wslc-flt-vlist-label-beta";
168 + const std::wstring scopeKey = L"wslc.e2e.list_filter_label";
169 + const std::wstring scopeValue = L"1";
170 +
171 + auto cleanup = wil::scope_exit([&]() {
172 + EnsureVolumeDoesNotExist(alpha);
173 + EnsureVolumeDoesNotExist(beta);
174 + });
175 +
176 + auto result = RunWslc(std::format(L"volume create --label {}={} --label env=prod {}", scopeKey, scopeValue, alpha));
177 + result.Verify({.Stderr = L"", .ExitCode = 0});
178 +
179 + result = RunWslc(std::format(L"volume create --label {}={} {}", scopeKey, scopeValue, beta));
180 + result.Verify({.Stderr = L"", .ExitCode = 0});
181 +
182 + {
183 + const auto names = GetFilteredVolumeNames(std::format(L"--filter label={}", scopeKey));
184 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
185 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
186 + }
187 +
188 + {
189 + const auto names = GetFilteredVolumeNames(std::format(L"--filter label={}={} --filter label=env=prod", scopeKey, scopeValue));
190 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
191 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
192 + }
193 +
194 + {
195 + const auto names = GetFilteredVolumeNames(std::format(L"--filter label={} --filter label=env=prod", scopeKey));
196 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
197 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
198 + }
199 + }
200 +
201 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_JsonEmptyIsExactlyEmpty)
202 + {
203 + const std::wstring alpha = L"wslc-flt-vlist-empty-alpha";
204 + auto cleanup = wil::scope_exit([&]() { EnsureVolumeDoesNotExist(alpha); });
205 +
206 + auto result = RunWslc(std::format(L"volume create {}", alpha));
207 + result.Verify({.Stderr = L"", .ExitCode = 0});
208 +
209 + // NDJSON with zero rows must be exactly empty stdout — not "[]", not "\n".
210 + result = RunWslc(L"volume list --format json --filter name=wslc-flt-vlist-no-such-volume-zzz");
211 + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
212 + }
213 +
214 + WSLC_TEST_METHOD(WSLCE2E_Volume_List_Filter_Name)
215 + {
216 + const std::wstring alpha = L"wslc-flt-vlist-name-alpha";
217 + const std::wstring beta = L"wslc-flt-vlist-name-beta";
218 + auto cleanup = wil::scope_exit([&]() {
219 + EnsureVolumeDoesNotExist(alpha);
220 + EnsureVolumeDoesNotExist(beta);
221 + });
222 +
223 + auto result = RunWslc(std::format(L"volume create {}", alpha));
224 + result.Verify({.Stderr = L"", .ExitCode = 0});
225 + result = RunWslc(std::format(L"volume create {}", beta));
226 + result.Verify({.Stderr = L"", .ExitCode = 0});
227 +
228 + {
229 + const auto names = GetFilteredVolumeNames(L"--filter name=wslc-flt-vlist-name-");
230 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
231 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
232 + }
233 +
234 + {
235 + const auto names = GetFilteredVolumeNames(L"--filter name=name-alpha");
236 + VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
237 + VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
238 + }
239 + }
240 +
241 private:
242 + static std::set<std::string> GetFilteredVolumeNames(const std::wstring& filterArgs)
243 + {
244 + auto result = RunWslc(std::format(L"volume list --format json {}", filterArgs));
245 + result.Verify({.Stderr = L"", .ExitCode = 0});
246 + const auto volumes = ParseNdjsonOutputAs<WSLCVolumeInformation>(result);
247 + std::set<std::string> names;
248 + for (const auto& v : volumes)
249 + {
250 + names.insert(v.Name);
251 + }
252 + return names;
253 + }
254 +
255 const std::wstring TestVolumeName = L"wslc-e2e-volume-list";
256 const std::wstring TestVolumeName2 = L"wslc-e2e-volume-list-2";
257 + const int DefaultVolumeSizeBytes = 3 * 1024 * 1024;
258 + const std::vector<std::wstring> FilterTestVolumeNames = {
259 + L"wslc-flt-vlist-driver-alpha",
260 + L"wslc-flt-vlist-driver-beta",
261 + L"wslc-flt-vlist-label-alpha",
262 + L"wslc-flt-vlist-label-beta",
263 + L"wslc-flt-vlist-empty-alpha",
264 + L"wslc-flt-vlist-name-alpha",
265 + L"wslc-flt-vlist-name-beta",
266 + };
267 };
268 } // namespace WSLCE2ETests