CLI: Added volumes option to container remove command#40308
CLI: Added volumes option to container remove command#40308AmelBawa-msft wants to merge 1 commit intofeature/wsl-for-appsfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for deleting a container’s associated volumes from the container remove CLI command.
Changes:
- Introduces a new
--volumesflag oncontainer removeand wires it through tasks/services to the underlying delete flags. - Adds E2E coverage for removal with and without the new volumes flag.
- Adds a localized description string and updates the command help text expectations.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/windows/wslc/e2e/WSLCE2EContainerRemoveTests.cpp | Adds E2E tests + help-text expectations for container remove --volumes. |
| src/windows/wslc/tasks/ContainerTasks.cpp | Reads --volumes and forwards it into container deletion. |
| src/windows/wslc/services/ContainerService.h | Extends Delete API to accept removeVolumes (defaulted). |
| src/windows/wslc/services/ContainerService.cpp | Converts force/removeVolumes into WSLCDeleteFlags and calls Delete(flags). |
| src/windows/wslc/commands/ContainerRemoveCommand.cpp | Adds ArgType::Volumes to the command’s argument list. |
| src/windows/wslc/arguments/ArgumentDefinitions.h | Defines Volumes argument (currently with short alias -v). |
| localization/strings/en-US/Resources.resw | Adds localized string for the --volumes description. |
| _(Version, "version", L"v", Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \ | ||
| /*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \ | ||
| _(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \ | ||
| _(Volumes, "volumes", L"v", Kind::Flag, Localization::WSLCCLI_RemoveVolumesArgDescription()) \ |
There was a problem hiding this comment.
The short alias -v is now assigned to multiple arguments (Version, Volume, and Volumes). Even if parsing is command-scoped, this can become ambiguous (especially if --version is treated as a global flag). Consider removing the short alias from at least one of these or choosing distinct short aliases so -v has a single meaning in any parsing context.
| _(Version, "version", L"v", Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \ | |
| /*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \ | |
| _(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \ | |
| _(Volumes, "volumes", L"v", Kind::Flag, Localization::WSLCCLI_RemoveVolumesArgDescription()) \ | |
| _(Version, "version", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \ | |
| /*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \ | |
| _(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \ | |
| _(Volumes, "volumes", NO_ALIAS, Kind::Flag, Localization::WSLCCLI_RemoveVolumesArgDescription()) \ |
There was a problem hiding this comment.
Since this comment comes up a lot by copilot I have created a PR to address this specific issue and ensure we have no collisions in the command definition and guard against one being introduced later.
| // Create a named volume and a container that uses it | ||
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| auto cleanup = wil::scope_exit([&]() { | ||
| EnsureContainerDoesNotExist(WslcContainerName); | ||
| EnsureVolumeDoesNotExist(WslcVolumeName); | ||
| }); | ||
|
|
||
| auto result = RunWslc( | ||
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | ||
| result.Verify({.Stderr = L"", .ExitCode = 0}); | ||
| const auto containerId = result.GetStdoutOneLine(); | ||
| VERIFY_IS_FALSE(containerId.empty()); | ||
|
|
||
| VerifyContainerIsListed(containerId, L"created"); | ||
| VerifyVolumeIsListed(WslcVolumeName); | ||
|
|
||
| // Remove with --volumes should remove the container and its volumes | ||
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | ||
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| VerifyContainerIsNotListed(containerId); | ||
| VerifyVolumeIsNotListed(WslcVolumeName); |
There was a problem hiding this comment.
This test uses a named volume (volume create --name ...) and asserts container remove --volumes deletes it. However, the new help text/description says it removes anonymous volumes. If the intended semantics are 'anonymous only' (Docker-like), this test will be asserting the wrong behavior—switch the test to use an anonymous volume. If the intended semantics are 'delete named volumes too', then the user-facing description should be updated to match.
| // Create a named volume and a container that uses it | |
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| EnsureVolumeDoesNotExist(WslcVolumeName); | |
| }); | |
| auto result = RunWslc( | |
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(WslcVolumeName); | |
| // Remove with --volumes should remove the container and its volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(WslcVolumeName); | |
| // Capture the current volume list so we can verify an anonymous volume is created and then removed. | |
| auto result = RunWslc(L"volume list"); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto volumesBeforeCreate = result.Stdout; | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| }); | |
| result = RunWslc( | |
| std::format(L"container create --name {} -v /data {}", WslcContainerName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| result = RunWslc(L"volume list"); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto volumesAfterCreate = result.Stdout; | |
| VERIFY_ARE_NOT_EQUAL(volumesBeforeCreate, volumesAfterCreate); | |
| // Remove with --volumes should remove the container and its anonymous volume. | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| result = RunWslc(L"volume list"); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| VERIFY_ARE_EQUAL(volumesBeforeCreate, result.Stdout); |
| // Create a named volume and a container that uses it | ||
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| auto cleanup = wil::scope_exit([&]() { | ||
| EnsureContainerDoesNotExist(WslcContainerName); | ||
| EnsureVolumeDoesNotExist(WslcVolumeName); | ||
| }); | ||
|
|
||
| auto result = RunWslc( | ||
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | ||
| result.Verify({.Stderr = L"", .ExitCode = 0}); | ||
| const auto containerId = result.GetStdoutOneLine(); | ||
| VERIFY_IS_FALSE(containerId.empty()); | ||
|
|
||
| VerifyContainerIsListed(containerId, L"created"); | ||
| VerifyVolumeIsListed(WslcVolumeName); | ||
|
|
||
| // Remove with --volumes should remove the container and its volumes | ||
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | ||
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| VerifyContainerIsNotListed(containerId); | ||
| VerifyVolumeIsNotListed(WslcVolumeName); |
There was a problem hiding this comment.
This test uses a named volume (volume create --name ...) and asserts container remove --volumes deletes it. However, the new help text/description says it removes anonymous volumes. If the intended semantics are 'anonymous only' (Docker-like), this test will be asserting the wrong behavior—switch the test to use an anonymous volume. If the intended semantics are 'delete named volumes too', then the user-facing description should be updated to match.
| // Create a named volume and a container that uses it | |
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| EnsureVolumeDoesNotExist(WslcVolumeName); | |
| }); | |
| auto result = RunWslc( | |
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(WslcVolumeName); | |
| // Remove with --volumes should remove the container and its volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(WslcVolumeName); | |
| // Create a container that uses an anonymous volume | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| }); | |
| auto result = | |
| RunWslc(std::format(L"container create --name {} -v /data {}", WslcContainerName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| // Remove with --volumes should remove the container and its anonymous volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); |
| // Create a named volume and a container that uses it | ||
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| auto cleanup = wil::scope_exit([&]() { | ||
| EnsureContainerDoesNotExist(WslcContainerName); | ||
| EnsureVolumeDoesNotExist(WslcVolumeName); | ||
| }); | ||
|
|
||
| auto result = RunWslc( | ||
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | ||
| result.Verify({.Stderr = L"", .ExitCode = 0}); | ||
| const auto containerId = result.GetStdoutOneLine(); | ||
| VERIFY_IS_FALSE(containerId.empty()); | ||
|
|
||
| VerifyContainerIsListed(containerId, L"created"); | ||
| VerifyVolumeIsListed(WslcVolumeName); | ||
|
|
||
| // Remove with --volumes should remove the container and its volumes | ||
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | ||
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| VerifyContainerIsNotListed(containerId); | ||
| VerifyVolumeIsNotListed(WslcVolumeName); |
There was a problem hiding this comment.
This test uses a named volume (volume create --name ...) and asserts container remove --volumes deletes it. However, the new help text/description says it removes anonymous volumes. If the intended semantics are 'anonymous only' (Docker-like), this test will be asserting the wrong behavior—switch the test to use an anonymous volume. If the intended semantics are 'delete named volumes too', then the user-facing description should be updated to match.
| // Create a named volume and a container that uses it | |
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| EnsureVolumeDoesNotExist(WslcVolumeName); | |
| }); | |
| auto result = RunWslc( | |
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(WslcVolumeName); | |
| // Remove with --volumes should remove the container and its volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(WslcVolumeName); | |
| std::wstring anonymousVolumeName; | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| if (!anonymousVolumeName.empty()) | |
| { | |
| EnsureVolumeDoesNotExist(anonymousVolumeName); | |
| } | |
| }); | |
| // Create a container with an anonymous volume mounted at /data | |
| auto result = RunWslc(std::format(L"container create --name {} -v /data {}", WslcContainerName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| result = RunWslc(std::format( | |
| L"container inspect --format \"{{range .Mounts}}{{if eq .Destination \\\"/data\\\"}}{{.Name}}{{end}}{{end}}\" {}", | |
| containerId)); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| anonymousVolumeName = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(anonymousVolumeName.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(anonymousVolumeName); | |
| // Remove with --volumes should remove the container and its anonymous volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(anonymousVolumeName); |
| // Create a named volume and a container that uses it | ||
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| auto cleanup = wil::scope_exit([&]() { | ||
| EnsureContainerDoesNotExist(WslcContainerName); | ||
| EnsureVolumeDoesNotExist(WslcVolumeName); | ||
| }); | ||
|
|
||
| auto result = RunWslc( | ||
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | ||
| result.Verify({.Stderr = L"", .ExitCode = 0}); | ||
| const auto containerId = result.GetStdoutOneLine(); | ||
| VERIFY_IS_FALSE(containerId.empty()); | ||
|
|
||
| VerifyContainerIsListed(containerId, L"created"); | ||
| VerifyVolumeIsListed(WslcVolumeName); | ||
|
|
||
| // Remove with --volumes should remove the container and its volumes | ||
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | ||
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | ||
|
|
||
| VerifyContainerIsNotListed(containerId); | ||
| VerifyVolumeIsNotListed(WslcVolumeName); |
There was a problem hiding this comment.
This test uses a named volume (volume create --name ...) and asserts container remove --volumes deletes it. However, the new help text/description says it removes anonymous volumes. If the intended semantics are 'anonymous only' (Docker-like), this test will be asserting the wrong behavior—switch the test to use an anonymous volume. If the intended semantics are 'delete named volumes too', then the user-facing description should be updated to match.
| // Create a named volume and a container that uses it | |
| RunWslc(std::format(L"volume create --name {}", WslcVolumeName)).Verify({.Stderr = L"", .ExitCode = 0}); | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| EnsureVolumeDoesNotExist(WslcVolumeName); | |
| }); | |
| auto result = RunWslc( | |
| std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, WslcVolumeName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(WslcVolumeName); | |
| // Remove with --volumes should remove the container and its volumes | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(WslcVolumeName); | |
| auto getVolumes = [&]() { | |
| auto listResult = RunWslc(L"volume list --quiet"); | |
| listResult.Verify({.Stderr = L"", .ExitCode = 0}); | |
| std::vector<std::wstring> volumes; | |
| std::wistringstream output(listResult.Stdout); | |
| std::wstring volumeName; | |
| while (std::getline(output, volumeName)) | |
| { | |
| if (!volumeName.empty()) | |
| { | |
| volumes.push_back(volumeName); | |
| } | |
| } | |
| return volumes; | |
| }; | |
| const auto volumesBeforeCreate = getVolumes(); | |
| auto cleanup = wil::scope_exit([&]() { | |
| EnsureContainerDoesNotExist(WslcContainerName); | |
| }); | |
| auto result = | |
| RunWslc(std::format(L"container create --name {} -v /data {}", WslcContainerName, DebianImage.NameAndTag())); | |
| result.Verify({.Stderr = L"", .ExitCode = 0}); | |
| const auto containerId = result.GetStdoutOneLine(); | |
| VERIFY_IS_FALSE(containerId.empty()); | |
| const auto volumesAfterCreate = getVolumes(); | |
| std::wstring anonymousVolumeName; | |
| for (const auto& volumeName : volumesAfterCreate) | |
| { | |
| if (std::find(volumesBeforeCreate.begin(), volumesBeforeCreate.end(), volumeName) == volumesBeforeCreate.end()) | |
| { | |
| anonymousVolumeName = volumeName; | |
| break; | |
| } | |
| } | |
| VERIFY_IS_FALSE(anonymousVolumeName.empty()); | |
| VerifyContainerIsListed(containerId, L"created"); | |
| VerifyVolumeIsListed(anonymousVolumeName); | |
| // Remove with --volumes should remove the container and its anonymous volume | |
| result = RunWslc(std::format(L"container remove --volumes {}", containerId)); | |
| result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); | |
| VerifyContainerIsNotListed(containerId); | |
| VerifyVolumeIsNotListed(anonymousVolumeName); |
| <value>Bind mount a volume to the container</value> | ||
| </data> | ||
| <data name="WSLCCLI_RemoveVolumesArgDescription" xml:space="preserve"> | ||
| <value>Remove anonymous volumes associated with the container</value> |
There was a problem hiding this comment.
The description explicitly says 'anonymous volumes', but the added E2E test expects a named volume to be removed with --volumes. Please align the description with the actual behavior (either clarify it removes only anonymous volumes, or broaden the text if named volumes are deleted as well).
| <value>Remove anonymous volumes associated with the container</value> | |
| <value>Remove volumes associated with the container</value> |
| _(Version, "version", L"v", Kind::Flag, Localization::WSLCCLI_VersionArgDescription()) \ | ||
| /*_(Virtual, "virtualization", NO_ALIAS, Kind::Value, Localization::WSLCCLI_VirtualArgDescription())*/ \ | ||
| _(Volume, "volume", L"v", Kind::Value, Localization::WSLCCLI_VolumeArgDescription()) \ | ||
| _(Volumes, "volumes", L"v", Kind::Flag, Localization::WSLCCLI_RemoveVolumesArgDescription()) \ |
There was a problem hiding this comment.
Since this comment comes up a lot by copilot I have created a PR to address this specific issue and ensure we have no collisions in the command definition and guard against one being introduced later.
|
Volumes arg uses -v alias, which already belongs to Version and Volume. Three-way collision. Use NO_ALIAS here. Also description says "anonymous volumes" but tests only cover named ones. |
Summary of the Pull Request
--volumesremoves associated volumes and that omitting it keeps them intact.PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed