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