Skip to content

CLI: Added volumes option to container remove command#40308

Draft
AmelBawa-msft wants to merge 1 commit intofeature/wsl-for-appsfrom
user/amelbawa/container-remove-volumes
Draft

CLI: Added volumes option to container remove command#40308
AmelBawa-msft wants to merge 1 commit intofeature/wsl-for-appsfrom
user/amelbawa/container-remove-volumes

Conversation

@AmelBawa-msft
Copy link
Copy Markdown

@AmelBawa-msft AmelBawa-msft commented Apr 24, 2026

Summary of the Pull Request

  • Added --volumes (-v) flag to wslc container remove
  • E2E tests verifying that --volumes removes associated volumes and that omitting it keeps them intact.

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

Copilot AI review requested due to automatic review settings April 24, 2026 17:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --volumes flag on container remove and 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.

Comment on lines 100 to +103
_(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()) \
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
_(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()) \

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

#40312

Comment on lines +163 to +185
// 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);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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);

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +185
// 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);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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);

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +185
// 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);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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);

Copilot uses AI. Check for mistakes.
Comment on lines +163 to +185
// 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);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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);

Copilot uses AI. Check for mistakes.
<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>
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
<value>Remove anonymous volumes associated with the container</value>
<value>Remove volumes associated with the container</value>

Copilot uses AI. Check for mistakes.
Comment on lines 100 to +103
_(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()) \
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

#40312

@benhillis
Copy link
Copy Markdown
Member

benhillis commented Apr 25, 2026

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants